Using the filesystem

As you have seen, Android has a filesystem that is based on Linux and supports mode-based permissions. There are several ways you can access this filesystem. You can create and read files from within applications, you can access raw files that are included as resources, and you can work with specially compiled custom XML files. In this section we will take a tour of each approach.

5.2.1 Creating files

You can easily create files in Android and have them stored in the filesystem under the data path for the application in which you are working. Listing 5.4 demonstrates how you get a FileOutputStream handle and how you write to it to create a file.

Listing 5.4 Creating a file in Android from an Activity public class CreateFile extends Activity {

private EditText createInput; private Button createButton;

@Override public void onCreate(Bundle icicle) { super.onCreate(icicle);

this.setContentView(R.layout.create_file);

this.createInput =

(EditText) this.findViewById(R.id.create_input); this.createButton =

(Button) this.findViewById(R.id.create_button);

this.createButton.setOnClickListener(new OnClickListener() { public void onClick(final View v) { FileOutputStream fos = null;

try { B Use fos = openFileOutput (" filename . txt", I openFileOutput

fos.write(createInput.getText().toString().getBytes()); <■ } catch (FileNotFoundException e) {

Log. e ("CreateFile", e .getLocalizedMessage () ) ; Write data

Log.e("CreateFile", e.getLocalizedMessage()); } finally {

fos . flush () ; D Flush and f os. close (); y close stream

startActivity( new Intent(CreateFile.this, ReadFile.class));

Android provides a convenience method on Context to get a FileOutputStream reference, openFileOutput(String name, int mode) O. Using this method you can create a stream to a file. That file will ultimately be stored at the data/data/ [PACKAGE_NAME]/files/file.name path on the platform. Once you have the stream, you can write to it as you would with typical Java ©. After you have finished with a stream you have to remember to flush it and close it to cleanup ©.

Reading from a file within an application context (that is, within the package path of the application) is also very simple; in the next section we will show how this can be done.

5.2.2 Accessing files

Similarly to openFileOutput, the Context also has a convenience openFileInput method. This method can be used to access a file on the filesystem and read it in, as shown in listing 5.5.

Listing 5.5 Accessing an existing file in Android from an Activity public class ReadFile extends Activity {

private TextView readOutput; private Button gotoReadResource;

@Override public void onCreate(Bundle icicle) { super.onCreate(icicle) ;

this.setContentView(R.layout.read_file);

this.readOutput =

(TextView) this.findViewById(R.id.read_output);

FileInputStream fis = null;

try { O Use openFileInput fis = this .openFileInput ("filename, txt") ; <1-' for stream byte[] reader = new byte[fis.available()];

while (fis. read (reader) != -1) {} <-1 Read data this . readOutput. setText (new String (reader)) ; from stream

Log.e("ReadFile", e.getMessage(), e); } finally {

try { D Clean up when f is. close (); <-1 finished

// swallow

. . . goto next Activity via startActivity omitted for brevity

Getting a FileInputStream, in order to read in a file from the filesystem, is the mirror opposite of getting a FileOutputStream. For input you use openFileInput(String name, int mode) to get the stream O, and then you read in the file as with standard Java © (in this case we are filling the byte reader byte array). Once you have finished, you need to close the stream properly to avoid hanging onto resources D.

With openFileOutput and openFileInput you can write to and read from any file within the files directory of the application package within which you are working. Also, much like the access modes and permissions we discussed in the previous sections, you can access files across different applications if the permissions allow it and if you know the full path to the file (you know the package to establish the path from the other application's context).

Running a bundle of apps with the same user ID

Though it is the exception rather than rule, there are times when setting the user ID your application runs as can be extremely useful (most of the time it's fine to allow the platform to select a unique ID for you). For instance, if you have multiple applications that need to store data among one another, but you also want that data to not be accessible outside that group of applications, you may want to set the permissions to private and share the UID to allow access. You can allow a shared UID by using the sharedUserId attribute in your manifest: android:sharedUserId="YourFancyID".

Along with creating files from within your application, you can push and pull files to the platform, using the adb (Android Debug Bridge) tool (which you met in chapters 1 and 2). You can optionally put such files in the directory for your application; once they are there you can read these files just like you would any other file. Keep in mind, though, outside of development-related use you won't usually be pushing and pulling files. Rather you will be creating and reading files from within the application or working with files that are included with an application as a raw resource, as you will see next.

5.2.3 Files as raw resources

If you want to include raw files with your application of any form, you can do so using the res/raw resources location. We discussed resources in general in chapter 3, but we did not drill down into raw files there, so we could group this data storage and access approach with others here. When you place a file in the res/raw location, it is not compiled by the platform but is available as a raw resource, as shown in listing 5.6.

Listing 5.6 Accessing a noncompiled raw file from res/raw public class ReadRawResourceFile extends Activity {

private TextView readOutput; private Button gotoReadXMLResource;

@Override public void onCreate(Bundle icicle) { super.onCreate(icicle);

this.setContentView(R.layout.read_rawresource_file);

this.readOutput =

(TextView) this.findViewByld(R.id.readrawres_output);

Resources resources = this . getResources () ; O Hold raw resource

InputStream is = null; <-1 with InputStream try {

is = resources. openRawResource (R. raw. people) ; <—n Use getResources()

byte [] reader = new byte [is .available () ] ; C openRawResource()

this.readOutput.setText(new String(reader));

Log.e("ReadRawResourceFile", e.getMessage(), e) ;

// swallow

. goto next Activity via startActivity omitted for brevity

Getting raw resources is very similar to getting files. You get a handle to an Input-Stream, and you can use that stream to assign to a raw reference later O. You call Context.getResources() to get the Resources reference for your current application's context, and then you call openRawResource(int id) to link to the particular item you want ©. The id will automatically be available from the R class if you place your asset in the res/raw directory. Raw resources don't have to be text files, even though that's what we are using here. They can be images, documents—you name it.

The significance with raw resources is that they are not precompiled by the platform, and they can refer to any type of raw file. The last type of file resource we need to discuss is the res/xml type—which is compiled by the platform into an efficient binary type that you need to access in a special manner.

5.2.4 XML file resources

The terms can get confusing when talking about XML resources in Android circles. This is because XML resources can mean resources in general that are defined in XML, such as layout files, styles, arrays, and the like, or it can specifically mean res/xml XML files.

File SCO rage

Reading from reyxml/people.xml file:

In this section we will be dealing with res/xml XML files. These files are treated a bit differently than other Android resources. They are different from raw files in that you don't use a stream to access them because they are compiled into an efficient binary form when deployed, and they are different from other resources in that they can be of any custom XML structure that you desire.

To demonstrate this concept we are going to use an XML file that defines multiple <person> elements and uses attributes for firstname and last-name—people.xml. We will then grab this resource and display the elements within it on screen in last-name, firstname order, as shown in figure 5.3.

Our data file for this process, which we will place in res/xml in source, is shown in listing 5.7.

File SCO rage

Reading from reyxml/people.xml file:

Ford, John Hitchcock, Alfred Kubrick, Stanfey Anderson, Wes

Figure 5.3 The example ReadXMLResource-File Activity created in listing 5.8, which reads a res/xml resource file

Listing 5.7 A custom XML file included in res/xml

<people>

<person firstname="John" lastname="Ford" /> <person firstname="Alfred" lastname="Hitchcock" /> <person firstname="Stanley" lastname="Kubrick" /> <person firstname="Wes" lastname="Anderson" /> </people>

Once a file is in the res/xml path, it will be automatically picked up by the platform (if you are using Eclipse) and compiled into a resource asset. This asset can then be accessed in code by parsing the binary XML format Android supports, as shown in listing 5.8.

Listing 5.8 Accessing a compiled XML resource from res/xml public class ReadXMLResourceFile extends Activity { private TextView readOutput; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle);

this.setContentView(R.layout.read_xmlresource_file); this.readOutput = (TextView)

Parse XML with this . f indViewById (R. id. readxmlres_output); XMLPullParser ith B

"J

XmlPullParser parser = this.getResources().getXml(R.xml.people); StringBuffer sb = new StringBuffer();

try { C Walking the while (parser. next () != XmlPullParser. END_DOCUMENT) { <1-1 XML tree

String name = parser.getName(); String first = null; String last = null;

if ((name != null) && name . equals ("person") ) { D Get attributeCount

int size = parser.getAttributeCount () ; <-1 for element for (int i = 0; i < size; i++) {

String attrName =

parser.getAttributeName(i); <-1

1 Get attribute

/ x I name and value parser.getAttributeValue(i); <-1

if ((attrName != null) && attrName.equals("firstname")) { first = attrValue; } else if ((attrName != null) && attrName.equals("lastname")) { last = attrValue;

if ((first != null) && (last != null)) { sb.append(last + ", " + first + "\n");

this.readOutput.setText(sb.toString()); } catch (Exception e) {

Log.e("ReadXMLResourceFile", e.getMessage(), e) ;

. . . goto next Activity via startActivity omitted for brevity

To process a binary XML resource you use an XmlPullParser O- This class can walk though the XML tree SAX style. The parser provides an event type represented by an int for each element it encounters, such as DOCDECL, COMMENT, START_DOCUMENT, START_TAG, END_TAG, END_DOCUMENT, and so on. Using the next() method you can retrieve the current event type value and compare it to event constants in the class ©. Each element encountered has a name, a text value, and an optional set of attributes. You can walk through the document as we are here by getting the attributeCount G for each item and grabbing the name and value Q. We are traversing the nodes of a resource-based XML file here with a pull parser; you will see more types of XML parsing in later examples. (SAX is specifically covered in chapter 13.)

Apart from local file storage on the device filesystem, you have another option that is more appropriate for certain types of content, writing to an external SD card filesystem.

5.2.5 External storage via an SD card

One of the advantages the Android platform provides over some other similar device competitors is that it offers access to an available Secure Digital (SD) flash memory card. Ultimately, it is possible that not every Android device will have an SD card, but the good news is that if the device does have it, the platform supports it and provides an easy way for you to use it.

SD cards and the emulator

In order to work with an SD card image in the Android Emulator, you will first need to use the mksdcard tool provided to set up your SD image file (you will find this executable in the tools directory of the SDK). After you have created the file, you will need to start the emulator with the -sdcard <path_to_file> option in order to have the SD image mounted.

Using the SD card makes a lot of sense if you are dealing with large files or when you don't necessarily need to have permanent secure access to certain files. Obviously, if you are working with image data, audio files, or the like, you will want to store these on the SD card. The built-in internal filesystem is stored on the system memory, which is limited on a small mobile device—you don't typically want to throw snapshots of Grandma on the device itself if you have other options (like an SD card). On the other hand, for application-specialized data that you do need to be permanent and for which you are concerned about secure access, you should use the internal filesystem (or an internal database).

The SD card is impermanent (the user can remove it), and SD card support on most devices, including Android-powered devices, supports the FAT (File Allocation Table) filesystem. That's important to remember because it will help you keep in mind that the SD card doesn't have the access modes and permissions that come from the Linux filesystem.

Using the SD card when you need it is fairly basic. The standard java.io.File and related objects can be used to create and read (and remove) files on the /sdcard path (assuming that path is available, which you do need to check, also using the standard File methods). Listing 5.9 is an example of checking that the /sdcard path is present, creating another subdirectory therein, then writing and subsequently reading file data at that location.

Listing 5.9 Using standard java.io.File techniques with an SD card public class ReadWriteSDCardFile extends Activity { private TextView readOutput; @Override public void onCreate(Bundle icicle) {

super.onCreate(icicle) ;

this . setContentView (R. layout. read_write_sdcard_file);

this.readOutput = (TextView)

this.findViewById(R.id.readwritesd_output);

String fileName = "testfile-" + System. currentTimeMillis () + ".txt"; <—© Establish filename

Get /sdcard directory reference

File sdDir = new File ("/sdcard/") ; <1-1

if (sdDir.exists () && sdDir.canWrite()) { C

File uadDir = new File(sdDir.getAbsolutePath()

+ "/unlocking_android") ; <—A Instantiate File for path uadDir.mkdir();

if (uadDir.exists() && uadDir.canWrite()) {

File file = new File(uadDir.getAbsolutePath()

file.createNewFile(); } catch (IOException e) { // log and or handle

Create file

Get reference to File o

Use mkdir() to create directory if (file.exists () && file.canWrite()) { FileOutputStream fos = null; try {

fos = new FileOutputStream(file); fos.write("I fear you speak upon the rack," + "where men enforced do speak " + "anything.".getBytes()); } catch (FileNotFoundException e) {

Log.e(ReadWriteSDCardFile.LOGTAG, "ERROR", e) } catch (IOException e) {

Log.e(ReadWriteSDCardFile.LOGTAG, "ERROR", e) } finally {

fos.flush(); fos.close (); } catch (IOException e) { // swallow

H Write with <5_I FileIi

FileInputStream

// log and or handle - error writing to file

// unable to write to /sdcard/unlocking_android

Log.e("ReadWriteSDCardFile.LOGTAG",

"ERROR /sdcard path not available (did you create + " an SD image with the mksdcard tool," + " and start emulator with -sdcard " + <path_to_file> option?");

File rFile =

new File("/sdcard/unlocking_android/" + fileName); if (rFile.exists() && rFile.canRead()) { FileInputStream fis = null; try {

fis = new FileInputStream(rFile) ;

Use new File object for reading

byte [] reader = new byte [fis .available () ] ; <1-1 Read with while (fis_read(reader) != -1) { J FileOutputStream this.readOutput.setText(new String(reader)); } catch (IOException e) {

fis.close ( ) ; } catch (IOException e) { // swallow

this.readOutput.setText(

"Unable to read/write sdcard file, see logcat output");

The first thing we need to do in the ReadWriteSDCardFile class is to establish a filename for the file we want to create O. We have done this by appending a timestamp so as to create a unique file each time this example application is run. After we have the filename, we create a File object reference to the /sdcard directory ©. From there we create a File reference to a new subdirectory, /sdcard/unlocking_android © (in Java both files and directories can be represented by the File object). After we have the subdirectory reference we call mkdir() to ensure it is created if it does not already exist ©.

With the structure we need in place, we follow a similar pattern for the actual file. We instantiate a reference File object ©, and we call createFile() to create a file on the filesystem ©. Once we have the File, and we know it exists and we are allowed to write to it (recall files on the sdcard will be world writable by default because it's using a FAT filesystem), we then use a FileInputStream to write some data into the file ©.

After we create the file and have data in it, we create another File object with the full path to read the data back ©. Yes, we could use the same File object handle that we had when creating the file, but for the purposes of the example we wanted to explicitly demonstrate starting with a fresh File. With the File reference we then create a FileOutputStream and read back the data that was earlier stored in the file ©.

As you can see, working with files on the SD card is pretty much standard java.io.File fare. This does entail a good bit of boilerplate Java code to make a robust solution, with permissions and error checking every step of the way and logging about what is happening, but it is still simple and powerful. If you need to do a lot of File handling, you will probably want to create some simple local utilities for wrapping the mundane tasks so you don't have to repeat them over and over again (opening files, writing to them, creating them, and so on). You may want to look at using or porting something like the Apache commons.io package, which includes a FileUtils class that handles these types of tasks and more.

The SD card example completes our exploration in this section, where we have seen that there are various ways to store different types of file data on the Android platform. If you have static elements that are predefined you can use res/raw, if you have XML files you can use res/xml. You can also work directly with the filesystem by creating, modifying, and retrieving data in files (either in the local internal filesystem or on the SD card if available.

Another way to deal with data, one that may be more appropriate for many situations (such as when you need to share relational data across applications), is through the use of a database.

0 0

Post a comment