Previous  | Next  | Home

Accessing the File System


 

Android devices are computers, with file systems that can be accessed using many of the same methods as for standard computers (see Transferring Files). In this project we give two examples of using the Android file system.

  1. Writing to files on the SD card or other external media.

  2. Reading data at runtime from static (read-only) files that are packaged with an application.

In later projects we shall use these methods and others to give additional examples of storing and retrieving information.

 

Creating the Project

Create a new project in Eclipse by selecting New > Android Application Project (or File > Project > Android > Android Application Project > Next) and filling in the following information on the screen that results.

Click Next and then accept the defaults on the remaining screens to create the project WriteSDCard.

 

Filling out the Code

We first insert all the Java and XML that we shall need and then we shall explain its functionality.

 

The XML Files

Edit the res/values/strings.xml file to read

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
      <string name="app_name">WriteSDCard</string>
      <string name="action_settings">Settings</string>
      <string name="hello">FILE SYSTEM I/O</string>
    </resources>

and edit the res/layout/activity_main.xml file so that it reads:

   <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:paddingBottom="@dimen/activity_vertical_margin"
      android:paddingLeft="@dimen/activity_horizontal_margin"
      android:paddingRight="@dimen/activity_horizontal_margin"
      android:paddingTop="@dimen/activity_vertical_margin"
      tools:context=".MainActivity" >
   
      <TextView
         android:id="@+id/TextView01"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:padding="5sp"
         android:text="@string/hello" />
   
   </RelativeLayout>

 

The Manifest File

Next, we must modify the manifest file because we are going to need explicit permission to write to external files. Open AndroidManifest.xml and edit it to read

   <?xml version="1.0" encoding="utf-8"?>
   <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="<YourNamespace>.writesdcard"
      android:versionCode="1"
      android:versionName="1.0" >
   
      <uses-sdk
         android:minSdkVersion="8"
         android:targetSdkVersion="18" /> 
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   
      <application
         android:allowBackup="true"
         android:icon="@drawable/ic_launcher"
         android:label="@string/app_name"
         android:theme="@style/AppTheme" >
         <activity
               android:name="<YourNamespace>.writesdcard.MainActivity"
               android:label="@string/app_name" >
               <intent-filter>
                  <action android:name="android.intent.action.MAIN" />
   
                  <category android:name="android.intent.category.LAUNCHER" />
               </intent-filter>
         </activity>
      </application>
   
   </manifest>

where the added permission WRITE_EXTERNAL_STORAGE is highlighted in red.


To read files on the external file system the READ_EXTERNAL_STORAGE permission is required, and to write to external storage the WRITE_EXTERNAL_STORAGE is required. (If you need to do both, the read permission is implicit in the write permission, so it is only necessary to acquire the write permission in that case.)

Beginning with Android 4.4, these permissions are not required if you are are reading or writing only files that are private to your app (but you should see Saving files that are app-private for the details of what that implies).

 

The Java Class Files

Next, open the file src/<YourNamespace>.writesdcard/MainActivity.java and edit it to read

   package <YourNamespace>.writesdcard;
   
   import android.os.Bundle;
   import android.app.Activity;
   import android.view.Menu;
   import java.io.BufferedReader;
   import java.io.File;
   import java.io.FileNotFoundException;
   import java.io.FileOutputStream;
   import java.io.IOException;
   import java.io.InputStream;
   import java.io.InputStreamReader;
   import java.io.PrintWriter;
   import android.os.Environment;
   import android.util.Log;
   import android.widget.TextView;
   
   public class MainActivity extends Activity {
   
         private static final String TAG = "MEDIA";
         private TextView tv;
   
         @Override
         protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.activity_main);
   
                  tv = (TextView) findViewById(R.id.TextView01);
                  checkExternalMedia();
                  writeToSDFile();
                  readRaw();
   
         }
   
         @Override
         public boolean onCreateOptionsMenu(Menu menu) {
                  // Inflate the menu; this adds items to the action bar if it is present.
                  getMenuInflater().inflate(R.menu.main, menu);
                  return true;
         }
   
         /** Method to check whether external media available and writable. This is adapted from
      http://developer.android.com/guide/topics/data/data-storage.html#filesExternal */
   
         private void checkExternalMedia(){
                  boolean mExternalStorageAvailable = false;
                  boolean mExternalStorageWriteable = false;
                  String state = Environment.getExternalStorageState();
   
                  if (Environment.MEDIA_MOUNTED.equals(state)) {
                           // Can read and write the media
                           mExternalStorageAvailable = mExternalStorageWriteable = true;
                  } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
                           // Can only read the media
                           mExternalStorageAvailable = true;
                           mExternalStorageWriteable = false;
                  } else {
                           // Can't read or write
                           mExternalStorageAvailable = mExternalStorageWriteable = false;
                  }   
                  tv.append("\n\nExternal Media: readable="
                                 +mExternalStorageAvailable+" writable="+mExternalStorageWriteable);
         }
   
         /** Method to write ascii text characters to file on SD card. Note that you must add a 
      WRITE_EXTERNAL_STORAGE permission to the manifest file or this method will throw
      a FileNotFound Exception because you won't have write permission. */
   
         private void writeToSDFile(){
   
                  // Find the root of the external storage.
                  // See http://developer.android.com/guide/topics/data/data-storage.html#filesExternal
   
                  File root = android.os.Environment.getExternalStorageDirectory(); 
                  tv.append("\nExternal file system root: "+root);
   
                  // See http://stackoverflow.com/questions/3551821/android-write-to-sd-card-folder
   
                  File dir = new File (root.getAbsolutePath() + "/download");
                  dir.mkdirs();
                  File file = new File(dir, "myData.txt");
   
                  try {
                           FileOutputStream f = new FileOutputStream(file);
                           PrintWriter pw = new PrintWriter(f);
                           pw.println("Howdy do to you.");
                           pw.println("Here is a second line.");
                           pw.flush();
                           pw.close();
                           f.close();
                  } catch (FileNotFoundException e) {
                           e.printStackTrace();
                           Log.i(TAG, "File not found. Did you" +
                           " add a WRITE_EXTERNAL_STORAGE permission to the manifest?");
                  } catch (IOException e) {
                           e.printStackTrace();
                  }	
                  tv.append("\n\nFile written to:\n"+file);
         }
   
         /** Method to read in a text file placed in the res/raw directory of the application. The
         method reads in all lines of the file sequentially. */
   
         private void readRaw(){
                  tv.append("\n\nData read from res/raw/textfile.txt:\n");
                  InputStream is = this.getResources().openRawResource(R.raw.textfile);
                  InputStreamReader isr = new InputStreamReader(is);
                  BufferedReader br = new BufferedReader(isr, 8192);    // 2nd arg is buffer size
   
                  // More efficient (less readable) implementation of above is the composite expression
                  /*BufferedReader br = new BufferedReader(new InputStreamReader(
                           this.getResources().openRawResource(R.raw.textfile)), 8192);*/
   
                  try {
                           String test;	
                           while (true){				
                                 test = br.readLine();   
                                 // readLine() returns null if no more lines in the file
                                 if(test == null) break;
                                 tv.append("\n"+"    "+test);
                           }
                           isr.close();
                           is.close();
                           br.close();
                  } catch (IOException e) {
                           e.printStackTrace();
                  }
                  tv.append("\n\nThat is all");
         }
   }

 

Adding Resources

Finally, to test part of our application we need to create the res/raw directory and place a file containing some lines of text in it.

  1. If res/raw does not exist in the project, right click on the res directory in the left panel of Eclipse and select New > Folder. In the resulting window, give the folder the name raw and click Finish.

  2. Then, right-click on the new res/raw folder (if it does not appear immediately, you may have to right-click on res and select Refresh) and select New > File. In the resulting window give the new file the name textfile.txt and click Finish.

  3. Double click on the new res/raw/textfile.txt to open it in the Eclipse editor and add several lines of text to it for testing purposes. For this example, I inserted into res/raw/textfile.txt
        Now is the time
        for all good men
        to come to the aid
        of their country.
    
    but you can put whatever you wish.

That completes our application. Now we test it and explain what it does.

 

Trying it Out

If you execute the application on a phone or emulator you should obtain a display like the following figure



indicating that several tasks have been carried out:

  1. The external file system (generically the SD card) has been checked for whether it is mounted and readable/writable.

  2. The root of the external file system has been identified.

  3. A data file was written to /storage/emulated/0/download/myData.txt on the external file system.

  4. Data were read in and displayed from a file res/raw/textfile.txt.

Let us now explain how the code in our XML and Java files carries this out.

 

Overview of Basic Functionality

First, we set up the ability to output results to our main display:

  1. In a manner that should by now be familiar, we define a TextView object tv by using FindViewByID().

  2. We then, at various places, use the append(CharSequence text) method of TextView to append additional information to that already displayed on the screen.

Besides the onCreate() method, there are three methods in MainActivity.java. Each implements a basic functionality:

  1. The method checkExternalMedia() checks whether the device has external media installed and whether it is readable and writable.

  2. The method writeToSDFile() writes some output to a file on the external SD card.

  3. The method readRaw() reads input from a file installed with the application in the res/raw directory.

We now explain in turn how each of these methods works.

 

Checking External Media

To check the status of external storage media in the method checkExternalMedia(), we first invoke the getExternalStorageState() method of Environment to return a string that we store in the variable state. We then compare this string, using the equals() method of the String class, with various String constants of the Environment class to determine the state of the external media.


Notice that in Android, as in Java more generally, we cannot use the logical comparison operator == to compare two strings. Instead we must use an operator like the equals() method to return a boolean indicating whether two strings are equivalent.

If our check is successful, we display the line on the screen

   External Media: readable=true writable=true

Assuming this to be the case, in the next section we shall write to a file on the external storage medium.


"External" means media/shared storage. It is a filesystem with relatively large capacity that does not enforce permissions (so it can be shared across applications). Traditionally this is an SD card, but it might also be built-in storage that is distinct from the protected internal storage and can be mounted as a filesystem on the device. For a more extensive discussion of data storage in Android, see the Storage Options document.

 

Writing to a File on the SD Card

Assuming that we have a writable external medium, we illustrate in the method writeSDCard() how to output a file to that storage.

 

Finding the Root of External Storage

First, we use the getExternalStorageDirectory() method of Environment to return the root of the file system for the external medium, assigning the returned string to the Java File object root.


A File is an abstract representation of a filesystem entity that is identified by a path. It can be a normal file, but it can also be a directory or some other entity. For our usage it will typically be a file or directory that either exists or that we wish to create.

For example, when executed on a Motorola Moto-X phone running Android 4.4.2, the line

   External file system root: /storage/emulated/0

was written to the screen, indicating that the external medium was mounted and writable, with the root of the filesystem at /storage/emulated/0.


Some discussions of Android programming assume that the root of the external storage will be /sdcard (since it commonly was on earlier devices) and give examples of hardwiring that into an application. It is more bullet-proof to use the getExternalStorageDirectory() method, as described above, to have the device itself tell you the filesystem root. This is particularly true for the Android file system beginning with Jelly Bean (4.2) and following, which introduced the option of multiple users for the same device. This required the introduction of various Linux symbolic links (symlinks) that produce symbolic directories pointing to real physical directories. For the most part this is transparent to the ordinary user, but for applications such as those discussed here where we are programatically manipulating the file system it becomes more relevant. We shall discuss this further below.

 

Setting Up the Path to the File

Now suppose that we wish to write to a file that we shall call myData.txt in a subdirectory download of the root directory on the SD card. The statements in MainActivity.java that set up the path up for that output are

         File dir = new File (root.getAbsolutePath() + "/download");
         dir.mkdirs();
         File file = new File(dir, "myData.txt");

Thus file now specifies the absolute path /storage/emulated/0/download/myData.txt to the file into which we wish to write.


In the discussion above we used two different forms of the constructor for File that have the same name, but different argument lists:
  1. File(String path)

  2. File(File directory, String filename)
This is an example of what is called method overloading in object oriented programming, where two or more definitions of a method exist having the same name but different numbers and/or data types for method arguments.

The name and list of argument types for a method is called its signature. Thus the first method above has the signature File(String) while the second has the signature File(File, String). Even though the overloaded methods share the same name, they have different signatures and the compiler can distinguish among them by examining their argument lists.

NOTE: Java does not consider the return type in distinguishing methods, so you cannot define an overloaded method having the same signature but different return types.

 

Writing Using Output Streams and Writers

To write to the file we shall use standard Java i/o stream capability, implemented in terms of the classes

  1. FileOutputStream, which subclasses OutputStream and generates an output stream of bytes.

  2. PrintWriter, which is used to wrap the FileOutputStream and provide friendly user i/o (capability to deal with lines of ascii text rather than a byte stream).

The relevant code excerpted from the method writeToSDFile() is

    try {
        FileOutputStream f = new FileOutputStream(file);
        PrintWriter pw = new PrintWriter(f);
        pw.println("Howdy do to you.");
        pw.println("Here is a second line.");
        pw.flush();
        pw.close();
        f.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        Log.i(TAG, "******* File not found. Did you" +
                        " add a WRITE_EXTERNAL_STORAGE permission to the manifest?");
    } catch (IOException e) {
        e.printStackTrace();
    }

which creates a FileOutputStream, wraps it in a PrintWriter, and then appends lines to a file through this stream.

 

Handling Exceptions in the I/O

Note that all of the functional code above for the FileOuputStream is enclosed in a try-catch block. This is standard exception handling in Java.

  1. The constructor FileOutputStream(File file) for FileOutputStream throws FileNotFoundException if the File in its argument cannot be found.

  2. The method close() of FileOutputStream throws IOException if an error occurs in trying to close the stream.

Java requires that these exceptions be handled . The standard way to do that is in a try-catch block, which has the general form

    try {
        // Code in which an exception can be thrown
        .
        .  
    } catch (ExceptionType name1) {
        // Code to process exception type name1
        .
        .
    } catch (ExceptionType name2) {
        // Code to process exception type name2
        .
        .
    }

where one or more appended catch blocks process the named exceptions thrown in the try block. For a more extensive discussion of try-catch blocks, start with the Java Tutorials. In the present code the two catch blocks deal respectively with any FileNotFoundException or IOException that might be thrown in the try block.

For the catch block corresponding to FileNotFoundException we first invoke the printStackTrace() method that FileNotFoundException inherits from Throwable, which sends a human-readable form of the Throwable's stack trace to the System.err stream.


Throwable is the superclass of all classes that can be thrown by the virtual machine. It has two direct subclasses:
  1. Exception, which corresponds to recoverable errors, and

  2. Error, which corresponds to unrecoverable errors.
FileNotFoundException is a subclass of IOException, which is in turn a subclass of Exception.

We then customize the exception handling by reasoning that, since we have seen above that explicit permission must be given in the manifest file to write to external media, a likely reason for such an exception is to forget to do so. So we add the Log.i statement asking whether we remembered to add the external-write permission to the manifest file.

Let's test this. If you temporarily delete from AndroidManifest.xml the line

   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

and execute the application, your code should fail and generate exception output like the following in the logcat stream

   03-14 19:44:54.944 W/System.err(25390): java.io.FileNotFoundException: /storage/emulated/0/download/myData.txt: open failed: EACCES (Permission denied)
   03-14 19:44:54.946 W/System.err(25390):         at libcore.io.IoBridge.open(IoBridge.java:409)
   03-14 19:44:54.946 W/System.err(25390):         at java.io.FileOutputStream.(FileOutputStream.java:88)
   03-14 19:44:54.946 W/System.err(25390):         at java.io.FileOutputStream.(FileOutputStream.java:73)
   03-14 19:44:54.947 W/System.err(25390):         at com.lightcone.writesdcard.MainActivity.writeToSDFile(MainActivity.java:84)
   03-14 19:44:54.947 W/System.err(25390):         at com.lightcone.writesdcard.MainActivity.onCreate(MainActivity.java:30)
   03-14 19:44:54.948 W/System.err(25390):         at android.app.Activity.performCreate(Activity.java:5248)
   03-14 19:44:54.948 W/System.err(25390):         at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110)
   03-14 19:44:54.949 W/System.err(25390):         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2173)
   03-14 19:44:54.949 W/System.err(25390):         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2269)
   03-14 19:44:54.949 W/System.err(25390):         at android.app.ActivityThread.access$800(ActivityThread.java:139)
   03-14 19:44:54.949 W/System.err(25390):         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210)
   03-14 19:44:54.950 W/System.err(25390):         at android.os.Handler.dispatchMessage(Handler.java:102)
   03-14 19:44:54.950 W/System.err(25390):         at android.os.Looper.loop(Looper.java:136)
   03-14 19:44:54.950 W/System.err(25390):         at android.app.ActivityThread.main(ActivityThread.java:5102)
   03-14 19:44:54.951 W/System.err(25390):         at java.lang.reflect.Method.invokeNative(Native Method)
   03-14 19:44:54.951 W/System.err(25390):         at java.lang.reflect.Method.invoke(Method.java:515)
   03-14 19:44:54.951 W/System.err(25390):         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
   03-14 19:44:54.952 W/System.err(25390):         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
   03-14 19:44:54.957 W/System.err(25390):         at dalvik.system.NativeStart.main(Native Method)
   03-14 19:44:54.958 W/System.err(25390): Caused by: libcore.io.ErrnoException: open failed: EACCES (Permission denied)
   03-14 19:44:54.970 W/System.err(25390):         at libcore.io.Posix.open(Native Method)
   03-14 19:44:54.974 W/System.err(25390):         at libcore.io.BlockGuardOs.open(BlockGuardOs.java:110)
   03-14 19:44:54.975 W/System.err(25390):         at libcore.io.IoBridge.open(IoBridge.java:393)
   03-14 19:44:54.975 W/System.err(25390):         ... 18 more
   03-14 19:44:54.975 I/MEDIA   (25390): File not found. Did you add a WRITE_EXTERNAL_STORAGE permission to the manifest?

All but the last line of the above output is generated by the printStackTrace() method that sends the stack trace to the System.err stream.

For the second catch block that processes any IOException thrown in the try block, we just print the stack trace.


The alert Androider will object that there are two other exceptions thrown by code contained in the try block that we have ignored. Indeed, if you check the documentation you will find that
  1. The FileOutputStream constructor throws SecurityException in addition to FileNotFoundException.

  2. The PrintWriter constructor throws NullPointerException.
So why have we ignored these? Well, there are two categories of exceptions in Java:
  1. SecurityException and NullPointerException are subclasses of RuntimeException (the superclass of exceptions occurring as a result of executing an application in the virtual machine). The compiler does not require that a code handle runtime exceptions.

  2. The checked exceptions (those that are not subclasses of RuntimeException or Error---the superclass of all classes representing non-recoverable errors), like FileNotFoundException and IOException, are checked by the compiler and must be handled in your code.
Thus handling of the checked exceptions FileNotFoundException and IOException in our code is mandatory, but handling of the runtime exceptions NullPointerException and SecurityException is optional. For a discussion of the reasoning behind this choice in the Java language, and of situations where you might choose to handle runtime exceptions in your code, see The Java Tutorials.

Within the try-catch block we then instantiate a FileOutputStream f using the constructor with the File file as argument, and create a PrintWriter pw using the just-created FileOutputStream f as argument. Now we can write to the file using the print methods of PrintWriter. In this case we use println(String s) to output lines of text to the file. When we are through writing, we use the flush() method of PrintWriter to ensure that all pending data have been sent to the file, and then close the PrintWriter pw and the FileOutputStream f.


For output of large datasets, performance may be enhanced by using a BufferedOutputStream to wrap an existing OutputStream and buffer the output. A typical construction is
   BufferedOutputStream b = 
        new BufferedOutputStream(new FileOutputStream("file"));
Since most requests can be satisfied by accessing the buffer alone, this minimizes costly interaction with the underlying stream at the (usually smaller) expense of the extra space holding the buffer and time consumed in copying when the buffer is flushed. Likewise, input streams can be wrapped in a BufferedInputStream. We won't use BufferedOutputStream in the present example, but below in readRaw() we will give an example of buffering an input stream.

 

Outputting a File

If you execute this application on a real device or emulator, you should obtain the screen output shown above and you should find that the file /storage/emulated/0/download/myData.txt has been written to the SD card (or equivalent external storage). You can check the content of the file by copying it to your computer using ADB or Eclipse, as described in Transferring Files, or by using a file-management app like Astro File Explorer or WiFi File Explorer. For example, on my Linux system connected to an Android phone I used ADB to give

   [guidry@m33 ~]$ adb -d pull /sdcard/download/myData.txt myData.txt
   0 KB/s (40 bytes in 0.081s)
   [guidry@m33 ~]$ cat myData.txt
   Howdy do to you.
   Here is a second line.
   [guidry@m33 ~]$

indicating that our application has indeed written the requested output to the file on the phone's external storage, and that this file is publicly accessible. (The adb -d pull command copies the file from the device to the computer and the Unix cat command displays the content of the file on the computer.)

Note, however, that to get this to work I had to use the path /sdcard/download/ to the file on the phone for the ADB command, rather than the path /storage/emulated/0/download/ that the getExternalStorageDirectory method returned (and that we specified as the path to write the file). Attempting to use the latter gives an error:

   [guidry@m33new ~]$ adb -d pull /storage/emulated/0/download/myData.txt myData.txt
   remote object '/storage/emulated/0/download/myData.txt' does not exist

Furthermore, if I use Astro File Manager to find the file on the (Moto-X running Android 4.4.2) phone, it says the path to the file is /storage/sdcard0/Download, while WiFi File Explorer shows the file located in the directory /storage/emulated/0/Download/, as illustrated in the following figure,



and Eclipse (in the File Explorer tab of the DDMS view) indicates that the directory /sdcard (and /mnt/sdcard) is symbolically linked to /storage/emulated/legacy. (Furthermore, the file can be found in the File Explorer tab of the Eclipse DDMS view at various places, such as /storage/emulated/legacy/Download/myData.txt, but an attempt to pull the file to the computer from there using Eclipse returns null.) The reason for this somewhat confusing situation is associated with the introduction of multiple users on the same device with Android Jelly Bean, and is addressed further in the following box.


The Android file hierarchy (which is a variant of the Linux file hierarchy) became somewhat confusing with the advent of Jelly Bean (Android 4.2). The reason is that Jelly Bean introduced the option of multiple users on a device, and to separate those multiple users the file system was restructured so that each user effectively has a different portion of the "sdcard" (external storage). For, example, the address /storage/emulated/0/download/ represents a directory on external storage allocated for the first user (user 0). This restructuring employs various symbolic links. It is these symbolic links that cause different addresses to appear for different ways to access a file on the post-4.1 file system. For a more extensive (but I think not very clear) discussion, see The bottom line seems to be that (1) These changes are transparent to the actual users (at least of unrooted devices), but matter for programmers if they access the file system directly, or for users with rooted devices for issues like backup. (2) The Android SDK finds the appropriate file locations, whether on earlier devices or newer devices, as long as methods like getExternalStorageDirectory and not hard-wired addresses are used. (3) File-management apps like Astro File Manager or WiFi File Explorer allow correct navigation to files on old and new devices, but (4) the ADB may not (at least on my Fedora 19 Linux installation) follow the symbolic links properly to an address returned by getExternalStorageDirectory.

 

Appending Rather Than Overwriting

The default behavior of the output stream that we created above is to overwrite the file if it already exists. We can instead append to the file if it already exists by substituting for the constructor FileOutputStream (File file) the constructor FileOutputStream(File file, boolean append), where the file will be overwritten if append is false and appended to if append is true. For example, if the constructor is replaced by the second form with append set to true and the app run again, I obtain

    [guidry@m33 ~]$ cd /home/guidry
    [guidry@m33 ~]$ adb -d pull /sdcard/download/myData.txt myData.txt
    1 KB/s (80 bytes in 0.041s)
    [guidry@m33 ~]$ cat myData.txt
    Howdy do to you.
    Here is a second line.
    Howdy do to you.
    Here is a second line.
    [guidry@m33 ~]$

indicating that the second time the app ran the two lines that we output were appended to the (same) two lines that we wrote the first time it was run.

 

Reading from a Static Resource File

In the method readRaw() we illustrate one way to set up an input stream to read in an external file. In this example we assume the external file to be ascii text in the res/raw directory of the application, and read it in line by line until no lines remain.

 

Input Streams and Readers

We begin by defining a buffered reader using the following code sequence.

        InputStream is = this.getResources().openRawResource(R.raw.textfile);
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr, 8192);    // 2nd arg is buffer size

A file that is stored in the res/raw directory of the project will be exported with the application when it is packaged in the .apk file and thus can be accessed from the application by this approach. (The res/raw directory is not compressed when it is stored in the application.) The method openRawResource(int resourceID) can be used to open only drawable, sound, and raw resources; it will fail if you try to open string or color resources with it. A file stored in the res/raw directory is readable by the application at runtime, but not writable.


Our BufferedReader is an example of the buffered streams discussed above. Note that in production code we would commonly replace
    InputStream is = this.getResources().openRawResource(R.raw.textfile);
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr, 8192);
with the compound, chained expression
 BufferedReader br = new BufferedReader(new InputStreamReader(
	this.getResources().openRawResource(R.raw.textfile)), 8192);
since this is more compact and requires defining fewer variables. But it is also less readable, and for our pedagogical purposes in the projects presented here we often write expressions out as individual statements rather than compounding them into more efficient expressions so that what is happening is clearer for the reader.

 

Testing Read-In from a File

If you execute the application, you should find the lines that you put in the file res/raw/textfile.txt displayed on the screen because of the tv.append() statement in the while-loop. As seen in the figure above, for my example I obtained

    Now is the time
    for all good men
    to come to the aid
    of their country.

which corresponds exactly to the four lines that I inserted in the file res/raw/textfile.txt when it was created.


The complete project for the application described above is archived at the link WriteSDCard. Instructions for installing it in Eclipse may be found in Packages for All Projects.

Last modified: April 4, 2014


Exercises

1. Use the methods of the File class to add a method to MainActivity that will list the complete path to all files and directories on the SD card of the device, and the size of the files in bytes. [Solution]


Previous  | Next  | Home