Introduction to Android Application Development, Fourth Edition (2014)

Part IV. Android Application Design Essentials

Chapter 12. Working with Files and Directories

Android applications can store raw files on a device using a variety of methods. The Android SDK includes a number of helpful APIs for working with private application and cache files as well as for accessing external files on removable storage such as SD cards. Developers who need to store information safely and persistently will find the available file management APIs familiar and easy to use. In this chapter, we show you how to use the Android file system to read, write, and delete application data.

Working with Application Data on a Device

As discussed in the previous chapter, shared preferences provide a simple mechanism for storing simple application data persistently. However, many applications require a more robust solution that allows for any type of data to be stored and accessed in a persistent fashion. Some types of data that an application might want to store include the following:

Image Multimedia content such as images, sounds, video, and other complex information: These types of data structures are not supported as shared preferences. You may, however, store a shared preference that includes the file path or URI to the multimedia and store the multimedia on the device file system or download it just when needed.

Image Content downloaded from a network: As mobile devices, Android devices are not guaranteed to have persistent network connections. Ideally, an application will download content from the network once and keep it as long as necessary. Sometimes data should be kept indefinitely, whereas other circumstances simply require data to be cached for a certain amount of time.

Image Complex content generated by the application: Android devices function under stricter memory and storage constraints than desktop computers and servers do. Therefore, if your application has taken a long time to process data and come up with a result, that result should be stored for reuse, as opposed to re-creating it on demand.

Android applications can create and use directories and files to store their data in a variety of ways. The most common ways include the following:

Image Storing private application data under the application directory

Image Caching data under the application’s cache directory

Image Storing shared application data on external storage devices or shared device directory areas


Image Note

You can use Dalvik Debug Monitor Service (DDMS) to copy files to and from a device. For more information about using DDMS and the File Explorer, please see Appendix C, “Quick-Start Guide: Android DDMS.”


Practicing Good File Management

You should follow a number of best practices when working with files on the Android file system. Here are a few of the most important ones:

Image Anytime you read or write data to disk, you are performing intensive blocking operations and using valuable device resources. Therefore, in most cases, file access functionality of your applications should not be performed on the main UI thread of the application. Instead, these operations should be handled asynchronously using threads, AsyncTask objects, or other asynchronous methods. Even working with small files can slow down the UI thread due to the nature of the underlying file system and hardware.

Image Android devices have limited storage capacity. Therefore, store only what you need to store, and clean up old data when it is no longer needed to free up space on the device. Use external storage whenever it is appropriate to give the user more flexibility.

Image Be a good citizen: be sure to check for availability of resources such as disk space and external storage opportunities prior to using them and causing errors or crashes. Also, don’t forget to set appropriate file permissions for new files, and to release resources when you’re not using them (in other words, if you open them, close them, and so on).

Image Implement efficient file access algorithms for reading, writing, and parsing file contents. Use the many profiling tools available as part of the Android SDK to identify and improve the performance of your code. A good place to start is with the StrictMode API (android.os.StrictMode).

Image If the data the application needs to store is well structured, you may want to consider using a SQLite database to store application data.

Image Test your application on real devices. Different devices have different processor speeds. Do not assume that because your application runs smoothly on the emulator it will run as such on real devices. If you’re using external storage, test when external storage is not available.

Let’s explore how file management is achieved on the Android platform.

Understanding Android File Permissions

Remember from Chapter 1, “Introducing Android,” that each Android application is its own user on the underlying Linux operating system. It has its own application directory and files. Files created in the application’s directory are private to that application by default.

Files can be created on the Android file system with different permissions. These permissions specify how a file is accessed. Permission modes are most commonly used when creating files. These permission modes are defined in the Context class (android.content.Context):

Image MODE_PRIVATE (the default) is used to create a file that can be accessed only by the “owner” application itself. From a Linux perspective, this means the specific user identifier. The constant value of MODE_PRIVATE is 0, so you may see this used in legacy code.

Image MODE_APPEND is used to append data to the end of an existing file. The constant value of MODE_APPEND is 32768.


Image Warning

Up until API Level 17, MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE constants were viable options for exposing data to other applications, but they have now been deprecated. Using these constants in your applications may present security vulnerabilities when exposing data to other applications. Exposing your application’s data using these two file permission settings is now discouraged. The new and recommended approach is to use API components specifically designed for exposing application data for reading and writing by others, which include using the Service, ContentProvider, and/or BroadcastReceiver APIs.


An application does not need any special Android manifest file permissions to access its own private file system area. However, in order for your application to access external storage, it will need to register for the WRITE_EXTERNAL_STORAGE permission.

Working with Files and Directories

Within the Android SDK, you can also find a variety of standard Java file utility classes (such as java.io) for handling different types of files, such as text files, binary files, and XML files. In Chapter 6, “Managing Application Resources,” you learned that Android applications can also include raw and XML files as resources. Retrieving the file handle to a resource file is performed slightly differently from accessing files on the device file system, but once you have a file handle, either method allows you to perform read operations and the like in the same fashion. After all, a file is a file.

Clearly, Android application file resources are part of the application package and are therefore accessible only to the application itself. But what about file system files? Android application files are stored in a standard directory hierarchy on the Android file system.

Generally speaking, applications access the Android device file system using methods within the Context class (android.content.Context). The application, or any Activity class, can use the application Context to access its private application file directory or cache directory. From there, you can add, remove, and access files associated with your application. By default, these files are private to the application and cannot be accessed by other applications or by the user.


Image Tip

Many of the code examples provided in this section are taken from the SimpleFiles and FileStreamOfConsciousness applications. The SimpleFiles application demonstrates basic file and directory operations; it has no user interface (just LogCat output). TheFileStreamOfConsciousness application demonstrates how to log strings to a file as a chat stream; this application is multithreaded. The source code for these applications is provided for download on the book’s website.


Exploring with the Android Application Directories

Android application data is stored on the Android file system in the following top-level directory:

/data/data/<package name>/

Several default subdirectories are created for storing databases, preferences, and files as necessary. The actual location of these directories varies by device. You can also create other custom directories as needed. All file operations begin by interacting with the application Context object.Table 12.1 lists some important methods available for application file management. You can use all the standard java.io package utilities to work with FileStream objects and such.

Image

Table 12.1 Important android.content.Context File Management Methods

Creating and Writing to Files in the Default Application Directory

Android applications that require only the occasional file to be created should rely upon the helpful Context class method called openFileOutput(). Use this method to create files in the default location under the application data directory:

/data/data/<package name>/files/

For example, the following code snippet creates and opens a file called Filename.txt. We write a single line of text to the file and then close the file:

import java.io.FileOutputStream;
...
FileOutputStream fos;
String strFileContents = "Some text to write to the file.";
fos = openFileOutput("Filename.txt", MODE_PRIVATE);
fos.write(strFileContents.getBytes());
fos.close();

We can append data to the file by opening it with the mode set to MODE_APPEND:

import java.io.FileOutputStream;
...
FileOutputStream fos;
String strFileContents = "More text to write to the file.";
fos = openFileOutput("Filename.txt", MODE_APPEND);
fos.write(strFileContents.getBytes());
fos.close();

The file we created has the following path on the Android file system:

/data/data/<package name>/files/Filename.txt

Figure 12.1 shows a screen capture of an Activity that is configured to collect text input from a user, and then writes the information to a text file when Send is clicked.

Image

Figure 12.1 A screen capture of an Activity that is capable of writing to a text file.

Reading from Files in the Default Application Directory

Again we have a shortcut for reading files stored in the default /files subdirectory. The following code snippet opens a file called Filename.txt for read operations:

import java.io.FileInputStream;
...
String strFileName = "Filename.txt";
FileInputStream fis = openFileInput(strFileName);

Figure 12.2 shows a screen capture of an Activity that is configured to read the information from a text file upon launching of the Activity.

Image

Figure 12.2 A screen capture of an Activity reading from a text file and displaying the contents.

Reading Raw Files Byte by Byte

You handle file-reading and file-writing operations using standard Java methods. The java.io.InputStreamReader and java.io.BufferedReader are used for reading bytes and characters from different types of primitive file types. Here’s a simple example of how to read a text file, line by line, and store it in a StringBuffer:

FileInputStream fis = openFileInput(filename);
StringBuffer sBuffer = new StringBuffer();
BufferedReader dataIO = new BufferedReader (new InputStreamReader(fis));
String strLine = null;

while ((strLine = dataIO.readLine()) != null) {
    sBuffer.append(strLine + "\n");
}

dataIO.close();
fis.close();

Reading XML Files

The Android SDK includes several utilities for working with XML files, including SAX, an XML Pull Parser, and limited DOM, Level 2 Core support. Table 12.2 lists the packages helpful for XML parsing on the Android platform.

Image

Table 12.2 Important XML Utilities

Your XML parsing implementation will depend on which parser you choose to use. Back in Chapter 6, “Managing Application Resources,” we discussed including raw XML resource files in your application package. Here is a simple example of how to load an XML resource file and parse it using an XmlPullParser.

The XML resource file contents, as defined in the /res/xml/my_pets.xml file, are as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Our pet list -->
<pets>
   <pet type="Bunny" name="Bit"/>
   <pet type="Bunny" name="Nibble"/>
   <pet type="Bunny" name="Stack"/>
   <pet type="Bunny" name="Queue"/>
   <pet type="Bunny" name="Heap"/>
   <pet type="Bunny" name="Null"/>
   <pet type="Fish" name="Nigiri"/>
   <pet type="Fish" name="Sashimi II"/>
   <pet type="Lovebird" name="Kiwi"/>
</pets>

The following code illustrates how to parse the preceding XML using a special pull parser designed for XML resource files:

XmlResourceParser myPets = getResources().getXml(R.xml.my_pets);
int eventType = -1;
while (eventType != XmlResourceParser.END_DOCUMENT) {
    if(eventType == XmlResourceParser.START_DOCUMENT) {
        Log.d(DEBUG_TAG, "Document Start");
    } else if(eventType == XmlResourceParser.START_TAG) {
        String strName = myPets.getName();
        if(strName.equals("pet")) {
            Log.d(DEBUG_TAG, "Found a PET");
            Log.d(DEBUG_TAG,
                "Name: "+myPets.getAttributeValue(null, "name"));
            Log.d(DEBUG_TAG,
                "Species: "+myPets.
                getAttributeValue(null, "type"));
        }
    }
    eventType = myPets.next();
}
Log.d(DEBUG_TAG, "Document End");


Image Tip

You can review the complete implementation of this parser in the ResourceRoundup project found in the Chapter 6 code directory.


Working with Other Directories and Files on the Android File System

Using Context.openFileOutput() and Context.openFileInput() method calls is great if you have a few files and you want them stored in the application’s private /files subdirectory, but if you have more sophisticated file management needs, you need to set up your own directory structure. To do this, you must interact with the Android file system using the standard java.io.File class methods.

The following code retrieves the File object for the /files application subdirectory and retrieves a list of all filenames in that directory:

import java.io.File;
...
File pathForAppFiles = getFilesDir();
String[] fileList = pathForAppFiles.list();

Here is a more generic method to create a file on the file system. This method works anywhere on the Android file system you have permission to access, not just the /files subdirectory:

import java.io.File;
import java.io.FileOutputStream;
...
File fileDir = getFilesDir();
String strNewFileName = "myFile.dat";
String strFileContents = "Some data for our file";

File newFile = new File(fileDir, strNewFileName);
newFile.createNewFile();

FileOutputStream fo =
    new FileOutputStream(newFile.getAbsolutePath());
fo.write(strFileContents.getBytes());
fo.close();

You can use File objects to manage files within a desired directory and create subdirectories. For example, you might want to store “track” files within “album” directories. Or perhaps you want to create a file in a directory other than the default. Let’s say you want to cache some data to speed up your application’s performance and how often it accesses the network. In this instance, you might want to create a cache file. There is also a special application directory for storing cache files. Cache files are stored in the following location on the Android file system, retrievable with a call to the getCacheDir() method:

/data/data/<package name>/cache/

The external cache directory, found via a call to the getExternalCacheDir() method, is not treated the same in that files are not automatically removed from it.


Image Warning

Applications are responsible for managing their own cache directory and keeping it to a reasonable size (1MB is commonly recommended). The system places no limit on the number of files in a cache directory. The Android file system deletes cache files from the internal cache directory (getCacheDir()) as needed when internal storage space is low, or when the user uninstalls the application.


The following code gets a File object for the /cache application subdirectory, creates a new file in that specific directory, writes some data to the file, closes the file, and then deletes it:

File pathCacheDir = getCacheDir();
String strCacheFileName = "myCacheFile.cache";
String strFileContents = "Some data for our file";

File newCacheFile = new File(pathCacheDir, strCacheFileName);
newCacheFile.createNewFile();

FileOutputStream foCache =
    new FileOutputStream(newCacheFile.getAbsolutePath());
foCache.write(strFileContents.getBytes());
foCache.close();

newCacheFile.delete();

Creating and Writing Files to External Storage

Applications should store large amounts of data on external storage (using the SD card) rather than on limited internal storage. You can access external file storage, such as the SD card, from within your application as well. This is a little trickier than working within the confines of the application directory, as SD cards are removable, and so you need to check to see if the storage is mounted before use.


Image Tip

You can monitor file and directory activity on the Android file system using the FileObserver class (android.os.FileObserver). You can monitor storage capacity using the StatFs class (android.os.StatFs).


You can access external storage on the device using the Environment class (android.os.Environment). Begin by using the getExternalStorageState() method to check the mount status of external storage. You can store private application files on external storage, or you can store public shared files such as media. If you want to store private application files, use the getExternalFilesDir() method of the Context class because these files will be cleaned up if the application is uninstalled later. The external cache is accessed using the similar getExternalCacheDir() method. However, if you want to store shared files such as pictures, movies, music, ringtones, or podcasts on external storage, you can use the getExternalStoragePublicDirectory() method of the Environment class to get the top-level directory used to store a specific file type.


Image Tip

Applications that use external storage are best tested on real hardware, as opposed to the emulator. You’ll want to make sure you thoroughly test your application with various external storage states, including mounted, unmounted, and read-only modes. Each device may have different physical paths, so directory names should not be hard-coded.


Summary

There are a variety of ways to store and manage application data on the Android platform. The method you use depends on what kind of data you need to store. Applications have access to the underlying Android file system, where they can store their own private files, as well as limited access to the file system at large. It is important to follow best practices, such as performing disk operations asynchronously, when working on the Android file system, because mobile devices have limited storage and computing power.

Quiz Questions

1. What are the constant values of the Android file permission modes of MODE_PRIVATE and MODE_APPEND?

2. True or false: Storing files using the MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE permissions is the new recommended way for exposing your application’s data to others.

3. What is the top-level directory for storing Android application data on the Android file system?

4. What is the name of the Context class method call for creating files under the application data directory?

5. What is the name of the Context class method call for retrieving the external cache directory?

6. True or false: The android.os.Environment.getExternalStorageMountStatus() method is used for determining the mount state of a device’s external storage.

Exercises

1. Using the Android documentation, describe how to hide media files saved to a public external file directory to prevent Android’s media scanner from including the media files in other applications.

2. Create an application that is able to display the result of whether the SD card is available or not prior to accessing the external storage system.

3. Create an application for storing image files to the Pictures/ directory of external storage.

References and More Information

Android SDK Reference regarding the java.io package:

http://d.android.com/reference/java/io/package-summary.html

Android SDK Reference regarding the Context interface:

http://d.android.com/reference/android/content/Context.html

Android SDK Reference regarding the File class:

http://d.android.com/reference/java/io/File.html

Android SDK Reference regarding the Environment class:

http://d.android.com/reference/android/os/Environment.html

Android API Guides: “Using the Internal Storage”:

http://d.android.com/guide/topics/data/data-storage.html#filesInternal

Android API Guides: “Using the External Storage”:

http://d.android.com/guide/topics/data/data-storage.html#filesExternal