Assets, Files, and Data Parsing

Android offers a few structured ways to store data, notably SharedPreferences and local SQLite databases. And, of course, you are welcome to store your data “in the cloud” by using an Internet-based service. We will get to all of those topics shortly.

Beyond that, though, Android allows you to work with plain old ordinary files, either ones baked into your app (“assets”) or ones on so-called internal or external storage.

To make those files work — and to consume data off of the Internet — you will likely need to employ a parser. Android ships with several choices for XML and JSON parsing, in addition to third-party libraries you can attempt to use.

This chapter focuses on assets, files, and parsers.

Packaging Files with Your App

Let’s suppose you have some static data you want to ship with the application, such as a list of words for a spell-checker. Somehow, you need to bundle that data with the application, in a way you can get at it from Java code later on, or possibly in a way you can pass to another component (e.g., WebView for bundled HTML files).

There are three main options here: raw resources, XML resources, and assets.

Raw Resources

One way to deploy a file like a spell-check catalog is to put the file in the res/raw directory, so it gets put in the Android application .apk file as part of the packaging process as a raw resource.

To access this file, you need to get yourself a Resources object. From an activity, that is as simple as calling getResources(). A Resources object offers openRawResource() to get an InputStream on the file you specify. Rather than a path, openRawResource() expects an integer identifier for the file as packaged. This works just like accessing widgets via findViewById() – if you put a file named words.xml in res/raw, the identifier is accessible in Java as R.raw.words.

Since you can only get an InputStream, you have no means of modifying this file. Hence, it is really only useful for static reference data. Moreover, since it is unchanging until the user installs an updated version of your application package, either the reference data has to be valid for the foreseeable future, or you will need to provide some means of updating the data. The simplest way to handle that is to use the reference data to bootstrap some other modifiable form of storage (e.g., a database), but this makes for two copies of the data in storage. An alternative is to keep the reference data as-is but keep modifications in a file or database, and merge them together when you need a complete picture of the information. For example, if your application ships a file of URLs, you could have a second file that tracks URLs added by the user or reference URLs that were deleted by the user.

XML Resources

If, however, your file is in an XML format, you are better served not putting it in res/raw/, but rather in res/xml/. This is a directory for XML resources – resources known to be in XML format, but without any assumptions about what that XML represents.

To access that XML, you once again get a Resources object by calling getResources() on your Activity or other Context. Then, call getXml() on the Resources object, supplying the ID value of your XML resource (e.g., R.xml.words). This will return an XmlResourceParser, which implements the XmlPullParser interface. We will discuss how to use this parser, and the performance advantage of using XML resources, later in this chapter.

As with raw resources, XML resources are read-only at runtime.

Assets

Your third option is to package the data in the form of an asset. You can create an assets/ directory in your sourceset (e.g., src/main/assets), then place whatever files you want in there. Those are accessible at runtime by calling getAssets() on your Activity or other Context, then calling open() with the path to the file (e.g., assets/foo/index.html would be retrieved via open("foo/index.html")). As with raw resources, this returns an InputStream on the file’s contents. And, as with all types of resources, assets are read-only at runtime.

One benefit of using assets over raw resources is the file:///android_asset/ Uri prefix. You can use this to load an asset into a WebView. For example, for an asset located in assets/foo/index.html within your project, calling loadUrl("file:///android_asset/foo/index.html") will load that HTML into the WebView.

Note that assets are compressed when the APK is packaged. Unfortunately, on Android 1.x/2.x, this compression mechanism has a 1MB file size limit. If you wish to package an asset that is bigger than 1MB, you either need to give it a file extension that will not be compressed (e.g., .mp3) or actually store a ZIP file of the asset (to avoid the automatic compression) and decompress it yourself at runtime, using the standard java.util.zip classes. This restriction was lifted with Android 3.0, and so if your minSdkVersion is 11 or higher, this will not be an issue for you.

Files and Android

On the whole, Android just uses normal Java file I/O for local files. You will use the same File and InputStream and OutputWriter and other classes that you have used time and again in your prior Java development work.

What is distinctive in Android is where you read and write. Akin to writing a Java Web app, you do not have read and write access to arbitrary locations. Instead, there are only a handful of directories to which you have any access, particularly when running on production hardware.

Internal vs. External

Internal storage refers to your application’s portion of the on-board, always-available flash storage. External storage refers to storage space that can be mounted by the user as a drive in Windows (or, possibly with some difficulty, as a volume in OS X or Linux).

Historically (i.e., Android 1.x/2.x), internal storage was very limited in space. That is far less of a problem on 3.0 and higher.

Similarly, external storage is not always available on Android 1.x and 2.x – if it is mounted as a drive or volume on a host desktop or notebook, your app will not have access to external storage. We will examine this limitation in a bit more detail later in this chapter. This is not usually a problem on Android 3.0+.

Standard vs. Cache

On both internal and external storage, you have the option of saving files as a cache, or on a more permanent basis. Files located in a cache directory may be deleted by the OS or third-party apps to free up storage space for the user. Files located outside of cache will remain unless manually deleted.

Yours vs. Somebody Else’s

Internal storage is on a per-application basis. Files you write to in your own internal storage cannot be read or written to by other applications… normally. Users who “root” their phones can run apps with superuser privileges and be able to access your internal storage. Most users do not root their phones, and so only your app will be able to access your internal storage files.

Files on external storage, though, are visible to all applications and the user. Anyone can read anything stored there, and any application that requests to can write or delete anything it wants.

Working with Internal Storage

You have a few options for manipulating the contents of your app’s portion of internal storage.

One possibility is to use openFileInput() and openFileOutput() on your Activity or other Context to get an InputStream and OutputStream, respectively. However, these methods do not accept file paths (e.g., path/to/file.txt), just simple filenames.

If you want to have a bit more flexibility, getFilesDir() and getCacheDir() return a File object pointing to the roots of your files and cache locations on internal storage, respectively. Given the File, you can create files and subdirectories as you see fit.

To see how this works, take a peek at the Files/FilesEditor sample project.

This application implements a tabbed editor, using a ViewPager and a third-party tab library. Each tab is an EditorFragment, implementing a large EditText widget, akin to what we saw as examples back in the chapter on ViewPager.

However, those ViewPager samples had no persistence. Whatever you typed stayed in the fragments but was lost when the process was terminated. FileEditor instead will save what you enter into files, one file per tab.

The layout for the activity is reminiscent of the ViewPager samples, except that we are using an io.karim.MaterialTabs widget for the tabs, instead of something like a PagerTabStrip:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

  <io.karim.MaterialTabs
    android:id="@+id/tabs"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    app:mtIndicatorColor="@color/accent"
    app:mtSameWeightTabs="true"/>

  <android.support.v4.view.ViewPager
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  </android.support.v4.view.ViewPager>
</LinearLayout>

(from Files/FilesEditor/app/src/main/res/layout/main.xml)

That library, io.karim:materialtabs, is one of our dependencies, along with the support-v13 library for ViewPager itself:

apply plugin: 'com.android.application'

dependencies {
    compile 'io.karim:materialtabs:2.0.2'
    compile 'com.android.support:support-v13:25.1.0'
}

android {
  compileSdkVersion 25
  buildToolsVersion "25.0.2"

  defaultConfig {
      minSdkVersion 15
      targetSdkVersion 25
  }
}

(from Files/FilesEditor/app/build.gradle)

Other than some slight tweaks for using a MaterialTabs for the tabs, the MainActivity is not significantly different than the original ViewPager examples. It loads up the layout and populates the ViewPager and tabs:

  @Override
  protected void onReady(Bundle savedInstanceState) {
    setContentView(R.layout.main);

    ViewPager pager=(ViewPager)findViewById(R.id.pager);

    pager.setAdapter(new SampleAdapter(this, getFragmentManager()));

    MaterialTabs tabs=(MaterialTabs)findViewById(R.id.tabs);
    tabs.setViewPager(pager);
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/MainActivity.java)

Where things start to depart more significantly from the original samples comes in SampleAdapter. Rather than 10 pages, we limit the number of tabs to 2 or 3 in getCount(). Whether we support 2 or 3 pages depends on what version of Android we are running on — we will explore this issue more later in this chapter.

Rather than delegate the page titles to the EditorFragment, getPageTitle() looks up a string resource value from an array, based on the position, and uses that for the title. And getItem()… becomes more complicated:

package com.commonsware.android.fileseditor;

import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.support.v13.app.FragmentPagerAdapter;
import java.io.File;

public class SampleAdapter extends FragmentPagerAdapter {
  private static final int[] TITLES={R.string.internal,
      R.string.external, R.string.pub};
  private static final int TAB_INTERNAL=0;
  private static final int TAB_EXTERNAL=1;
  private static final String FILENAME="test.txt";
  private final Context ctxt;

  public SampleAdapter(Context ctxt, FragmentManager mgr) {
    super(mgr);

    this.ctxt=ctxt;
  }

  @Override
  public int getCount() {
    return(3);
  }

  @Override
  public Fragment getItem(int position) {
    File fileToEdit;

    switch(position) {
      case TAB_INTERNAL:
        fileToEdit=new File(ctxt.getFilesDir(), FILENAME);
        break;

      case TAB_EXTERNAL:
        fileToEdit=new File(ctxt.getExternalFilesDir(null), FILENAME);
        break;

      default:
        fileToEdit=
            new File(Environment.
                  getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),
                FILENAME);
        break;
    }

    return(EditorFragment.newInstance(fileToEdit));
  }

  @Override
  public String getPageTitle(int position) {
    return(ctxt.getString(TITLES[position]));
  }
}
(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/SampleAdapter.java)

Based on the supplied position, we create a File object representing where the data resides for our EditorFragment. Right now, let’s focus on the TAB_INTERNAL case, where we use getFilesDir() to create a File object pointing to a test.txt file on our internal storage.

The newInstance() factory method on EditorFragment now takes the File object as input, instead of the position. A File is Serializable, and so we can put a File into the arguments Bundle:

  static EditorFragment newInstance(File fileToEdit) {
    EditorFragment frag=new EditorFragment();
    Bundle args=new Bundle();

    args.putSerializable(KEY_FILE, fileToEdit);
    frag.setArguments(args);

    return(frag);
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

In onCreateView() of EditorFragment, we inflate a layout that contains our large EditText widget and retrieve that EditText widget:

  @Override
  public View onCreateView(LayoutInflater inflater,
                           ViewGroup container,
                           Bundle savedInstanceState) {
    View result=inflater.inflate(R.layout.editor, container, false);

    editor=(EditText)result.findViewById(R.id.editor);

    return(result);
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

In addition to an editor field for our EditText, EditorFragment has two other fields. One is a LoadTextTask, an AsyncTask subclass that we will use to load text from our file into our EditText. The other is loaded, a simple boolean to see if we have loaded our text yet:

  private EditText editor;
  private LoadTextTask loadTask=null;
  private boolean loaded=false;

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

In onViewCreated(), if we have not yet loaded the text, we kick off a LoadTextTask to do just that, passing in the File that we put into the arguments Bundle:

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    if (!loaded) {
      loadTask=new LoadTextTask();
      loadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
          (File)getArguments().getSerializable(KEY_FILE));
    }
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

LoadTextTask, in doInBackground(), goes through a typical Java file I/O read-all-the-lines process to read in a text file, if it exists. The resulting string is poured into the EditText. In onPostExecute(), it updates the EditText with the read-in text, plus clears the loadTask field and sets loaded to true:

  private class LoadTextTask extends AsyncTask<File, Void, String> {
    @Override
    protected String doInBackground(File... files) {
      String result=null;

      if (files[0].exists()) {
        BufferedReader br;

        try {
          br=new BufferedReader(new FileReader(files[0]));

          try {
            StringBuilder sb=new StringBuilder();
            String line=br.readLine();

            while (line!=null) {
              sb.append(line);
              sb.append("\n");
              line=br.readLine();
            }

            result=sb.toString();
          }
          finally {
            br.close();
          }
        }
        catch (IOException e) {
          Log.e(getClass().getSimpleName(), "Exception reading file", e);
        }
      }

      return(result);
    }

    @Override
    protected void onPostExecute(String s) {
      editor.setText(s);
      loadTask=null;
      loaded=true;
    }
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

However, since we are using an AsyncTask, we should retain this fragment:

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

    setRetainInstance(true);
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

…and in onDestroy(), we should cancel() this task if it is still running, as we no longer need the results:

  @Override
  public void onDestroy() {
    if (loadTask!=null) {
      loadTask.cancel(false);
    }

    super.onDestroy();
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

Rather than have some dedicated “save” action bar item or similar UI element, we can just arrange to save the data when our fragment gets paused. This is a typical approach in Android apps, as users do not necessarily get an opportunity to click some “save” UI element, if they get interrupted by a phone call or something. So, in onPause(), we kick off a SaveThread to write our EditText contents to the same File, once again pulled from the arguments Bundle:

  @Override
  public void onPause() {
    if (loaded) {
      new SaveThread(editor.getText().toString(),
          (File)getArguments().getSerializable(KEY_FILE)).start();
    }

    super.onPause();
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

However, note that we do not fork the SaveThread if loaded is still false. In that case, we know that we are still loading in the text, which means the text cannot possibly have been modified by the user, so there is nothing to save.

SaveThread ensures that the directory we want to write to exists (as it may or may not exist, particularly on emulators), then uses Java Writer objects to write out our text. Since there is nothing that we want to do with the UI here, a plain Thread, rather than an AsyncTask, is a better solution:

  private static class SaveThread extends Thread {
    private final String text;
    private final File fileToEdit;

    SaveThread(String text, File fileToEdit) {
      this.text=text;
      this.fileToEdit=fileToEdit;
    }

    @Override
    public void run() {
      try {
        fileToEdit.getParentFile().mkdirs();

        FileOutputStream fos=new FileOutputStream(fileToEdit);

        Writer w=new BufferedWriter(new OutputStreamWriter(fos));

        try {
          w.write(text);
          w.flush();
          fos.getFD().sync();
        }
        finally {
          w.close();
        }
      }
      catch (IOException e) {
        Log.e(getClass().getSimpleName(), "Exception writing file", e);
      }
    }
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

The reason for using a FileOutputStream, and that mysterious getFD().sync() part, will be covered later in this chapter.

The result is a set of tabbed editors, where the first one is our one for internal storage:

FilesEditor Sample, As Initially Launched
Figure 287: FilesEditor Sample, As Initially Launched

If you type something into the “Internal” tab, press BACK to exit the activity, and go back into the app again, whatever you typed in will be re-loaded from disk and will show up in the editor.

The files stored in internal storage are accessible only to your application, by default. Other applications on the device have no rights to read, let alone write, to this space. However, bear in mind that some users “root” their Android phones, gaining superuser access. These users will be able to read and write whatever files they wish. As a result, please consider application-local files to be secure against malware but not necessarily secure against interested users.

Working with External Storage

On most Android 1.x devices and some early Android 2.x devices, external storage came in the form of a micro SD card or the equivalent. On the remaining Android 2.x devices, external storage was part of the on-board flash, but housed in a separate partition from the internal storage. On most Android 3.0+ devices, external storage is now simply a special directory in the partition that holds internal storage.

Devices will have at least 1GB of external storage free when they ship to the user. That being said, many devices have much more than that, but the available size at any point could be smaller than 1GB, depending on how much data the user has stored.

Where to Write

If you have files that are tied to your application that are simply too big to risk putting in internal storage, or if the user should be able to download the files off their device at will, you can use getExternalFilesDir(), available on any activity or other Context. This will give you a File object pointing to an automatically-created directory on external storage, unique for your application. While not secure against other applications, it does have one big advantage: when your application is uninstalled, these files are automatically deleted, just like the ones in the application-local file area. This method was added in API Level 8. This method takes one parameter — typically null — that indicates a particular type of file you are trying to save (or, later, load).

In SampleAdapter of the sample app, if the user chooses the “External” tab, we use getExternalFilesDir() to create the File to be used by the EditorFragment:

      case TAB_EXTERNAL:
        fileToEdit=new File(ctxt.getExternalFilesDir(null), FILENAME);
        break;

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/SampleAdapter.java)

There is also getExternalCacheDir(), which returns a File pointing at a directory that contains files that you would like to have, but if Android or a third-party app clears the cache, your app will continue to function normally.

Android 4.4 (API Level 19) added two new methods, getExternalCacheDirs() and getExternalFilesDirs(), the plural versions of the classic methods. These return an array of File objects, representing one or more places where your app can work with external storage. The first element in the array will be the same File object returned by the singular versions of the methods (e.g., getExternalFilesDir()). The other elements in the array, if any, will represent app-specific directories on alternative external storage locations, like removable cards. The Android Support package has a ContextCompat class containing static versions of getExternalCacheDirs() and getExternalFilesDirs(), so you can use the same code on API Level 4 and above, though the backport will only ever return one directory in the array.

If you have files that belong more to the user than to your app — pictures taken by the camera, downloaded MP3 files, etc. — a better solution is to use getExternalStoragePublicDirectory(), available on the Environment class. This will give you a File object pointing to a directory set aside for a certain type of file, based on the type you pass into getExternalStoragePublicDirectory(). For example, you can ask for DIRECTORY_MOVIES, DIRECTORY_MUSIC, or DIRECTORY_PICTURES for storing MP4, MP3, or JPEG files, respectively. These files will be left behind when your application is uninstalled. This method was also added in API Level 8.

In SampleAdapter of the sample app, if the user chooses the “Public” tab, we use getExternalStoragePublicDirectory() to create the File to be used by the EditorFragment, putting our file in the DIRECTORY_DOCUMENTS location:

      default:
        fileToEdit=
            new File(Environment.
                  getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),
                FILENAME);
        break;

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/SampleAdapter.java)

You will also find a getExternalStorageDirectory() method on Environment, pointing to the root of the external storage. This is no longer the preferred approach — the methods described above help keep the user’s files better organized. However, if you are supporting older Android devices, you may need to use getExternalStorageDirectory(), simply because the newer options may not be available to you.

Relevant Permissions

On all relevant Android versions prior to Android 4.4 (API Level 19), if you want to write to external storage, you need to hold the WRITE_EXTERNAL_STORAGE permission. And, on those versions, you do not need a permission to read from external storage.

On Android 4.4 and up, the rules are a bit different:

Hence, so long as your android:minSdkVersion is less than 19, you need to take the most conservative approach:

Note that you might get paths to external storage locations from third-party apps, typically in the form of a Uri. If you are handling Uri values from third-party apps, you should request READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE, in case the third-party app hands you a Uri pointing to external storage.

For example, here is the sample app’s manifest, complete with the <uses-permission> element for WRITE_EXTERNAL_STORAGE:

<?xml version="1.0"?>
<manifest package="com.commonsware.android.fileseditor"
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:versionCode="1"
  android:versionName="1.0">

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

  <supports-screens
    android:anyDensity="true"
    android:largeScreens="true"
    android:normalScreens="true"
    android:smallScreens="true" />

  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/Theme.Apptheme">
    <activity
      android:name=".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>
(from Files/FilesEditor/app/src/main/AndroidManifest.xml)

However, on Android 6.0+, WRITE_EXTERNAL_STORAGE is one of those dangerous permissions that we have to request at runtime. That is why this sample app uses the AbstractPermissionActivity profiled in the material on runtime permissions. Overall, our MainActivity looks like this:

package com.commonsware.android.fileseditor;

import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.widget.Toast;
import io.karim.MaterialTabs;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;

public class MainActivity extends AbstractPermissionActivity  {
  @Override
  protected String[] getDesiredPermissions() {
    return(new String[]{WRITE_EXTERNAL_STORAGE});
  }

  @Override
  protected void onPermissionDenied() {
    Toast
      .makeText(this, R.string.msg_sorry, Toast.LENGTH_LONG)
      .show();
    finish();
  }

  @Override
  protected void onReady(Bundle savedInstanceState) {
    setContentView(R.layout.main);

    ViewPager pager=(ViewPager)findViewById(R.id.pager);

    pager.setAdapter(new SampleAdapter(this, getFragmentManager()));

    MaterialTabs tabs=(MaterialTabs)findViewById(R.id.tabs);
    tabs.setViewPager(pager);
  }
}
(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/MainActivity.java)

getDesiredPermissions() indicates that we want WRITE_EXTERNAL_STORAGE, and onPermissionDenied() exits the app afters showing a Toast. onReady() is where we set up the tabs, as we now have all the permissions that we need to be able to work with external storage.

Note that we do not need WRITE_EXTERNAL_STORAGE for getExternalFilesDir() on API Level 19+ devices. This leads to another possible permission strategy for this app:

When to Write

Also, external storage may be tied up by the user having mounted it as a USB storage device. You can use getExternalStorageState() (a static method on Environment) to determine if external storage is presently available or not. On Android 3.0 and higher, this should be much less of an issue, as they changed how the external storage is used by the host PC — originally, this used USB Mass Storage Mode (think thumb drives) and now uses the USB Media Transfer Protocol (think MP3 players). With MTP, both the Android device and the PC it is connected to can have access to the files simultaneously; Mass Storage Mode would only allow the host PC to have access to the files if external storage is mounted.

Nowadays, you can use getStorageState() on the EnvironmentCompat class from the support-v4 library to find out the state of external storage, for the particular File passed as a parameter.

Letting the User See Your Files

The switch to MTP has one side-effect for Android developers: files you write to external storage may not be automatically visible to the user. At the time of this writing, the only files that will show up on the user’s PC will be ones that have been indexed by the MediaStore. While the MediaStore is typically thought of as only indexing “media” (images, audio files, video files, etc.), it was given the added role in Android 3.0 of maintaining an index of all files for the purposes of MTP.

Your file that you place on external storage will not be indexed automatically simply by creating it and writing to it. Eventually, it will be indexed, though it may be quite some time for an automatic indexing pass to take place.

To force Android to index your file, you can use scanFile() on MediaScannerConnection:


String[] paths={pathToYourNewFileOnExternalStorage};
MediaScannerConnection.scanFile(this, paths, null, null); 

The third parameter to scanFile() is an array of MIME types, to line up with the array of paths in the second parameter. If your file is some form of media, and you know the MIME type, supplying that will ensure that your media will be visible as appropriate to the right apps (e.g., images in the Gallery app). Otherwise, Android will try to infer a MIME type from the file extension.

In the sample app, since the EditorFragment does not know whether the file is on external storage and therefore is reachable, it does not know whether or not this sort of indexing is appropriate. In a more conventional scenario, where the EditorFragment would consistently be writing to external storage, SaveThread could arrange to invoke MediaScannerConnection as part of its work. However, scanFile() needs a Context, and so the SaveThread would need one of those. You would wind up with something a bit like:


  private static class SaveThread extends Thread {
    private final String text;
    private final File fileToEdit;
    private final Context ctxt;

    SaveThread(Context ctxt, String text, File fileToEdit) {
      this.ctxt=ctxt.getApplicationContext();
      this.text=text;
      this.fileToEdit=fileToEdit;
    }

    @Override
    public void run() {
      try {
        fileToEdit.getParentFile().mkdirs();

        FileOutputStream fos=new FileOutputStream(fileToEdit);

        Writer w=new BufferedWriter(new OutputStreamWriter(fos));

        try {
          w.write(text);
          w.flush();
          fos.getFD().sync();
        }
        finally {
          w.close();
          String[] paths={fileToEdit};
          MediaScannerConnection.scanFile(ctxt, paths, null, null); 
        }
      }
      catch (IOException e) {
        Log.e(getClass().getSimpleName(), "Exception writing file", e);
      }
    }
  }

Here, we use getApplicationContext(), which returns to us a Context that is a process-wide singleton. That way, if our activity is destroyed while the thread is still running, we still have a valid Context to use.

Limits on External Storage Open Files

Many Android devices will have a per-process limit of 1024 open files, on any sort of storage. This is usually not a problem for developers.

On some devices — including probably all that are running Android 4.2 and higher — there is a global limit of 1024 open files on external storage. In other words, all running apps combined can only open 1024 files simultaneously on external storage.

This means that it is important for you to minimize how many open files on external storage you have at a time. Having a few open files is perfectly reasonable; having a few hundred open files is not.

Multiple User Accounts

On Android 4.1 and earlier, each Android device was assumed to be used by just one person.

On Android 4.2+ tablets — and Android 5.0+ phones — it is possible for a device’s owner to set up multiple user accounts. Each user gets their own section of internal and external storage for files, databases, SharedPreferences, and so forth. From your standpoint, it is as if the users are really on different devices, even though in reality it is all the same hardware.

However, this means that paths to internal and external storage now may vary by user. Hence, it is very important for you to use the appropriate methods, outlined in this chapter, for finding locations on internal storage (e.g., getFilesDir()) and external storage (e.g., getExternalFilesDir()).

Some blog posts, Stack Overflow answers, and the like will show the use of hard-coded paths for these locations (e.g., /sdcard or /mnt/sdcard for the root of external storage). Hard-coding such paths was never a good idea. And, as of Android 4.2, those paths are simply wrong and will not work.

On Android 4.2+, for the original user of the device, internal storage will wind up in the same location as before, but external storage will use a different path. For the second and subsequent users defined on the device, both internal and external storage will reside in different paths. The various methods, like getFilesDir(), will handle this transparently for you.

Note that, at the time of this writing, multiple accounts are not available on the emulators, only on actual tablets. Phones usually will not have multiple-account support, under the premise that tablets are more likely to be shared than are phones.

Linux Filesystems: You Sync, You Win

Android is built atop a Linux kernel and uses Linux filesystems for holding its files. Classically, Android used YAFFS (Yet Another Flash File System), optimized for use on low-power devices for storing data to flash memory.

YAFFS has one big problem: only one process can write to the filesystem at a time. For those of you into filesystems, rather than offering file-level locking, YAFFS has partition-level locking. This can become a bit of a bottleneck, particularly as Android devices grow in power and start wanting to do more things at the same time like their desktop and notebook brethren.

Android 3.0 switched to ext4, another Linux filesystem aimed more at desktops/notebooks. Your applications will not directly perceive the difference. However, ext4 does a fair bit of buffering, and it can cause problems for applications that do not take this buffering into account. Linux application developers ran headlong into this in 2008-2009, when ext4 started to become popular. Android developers will need to think about it now… for your own file storage.

If you are using SQLite or SharedPreferences, you do not need to worry about this problem. Android (and SQLite, in the case of SQLite) handle all the buffering issues for you. If, however, you write your own files, you may wish to contemplate an extra step as you flush your data to disk. Specifically, you need to trigger a Linux system call known as fsync(), which tells the filesystem to ensure all buffers are written to disk.

If you are using java.io.RandomAccessFile in a synchronous mode, this step is handled for you as well, so you will not need to worry about it. However, Java developers tend to use FileOutputStream, which does not trigger an fsync(), even when you call close() on the stream. Instead, you call getFD().sync() on the FileOutputStream to trigger the fsync(). Note that this may be time-consuming, and so disk writes should be done off the main application thread wherever practical, such as via an AsyncTask.

This is why, in EditorFragment, our SaveThread implementation looks like this:

  private static class SaveThread extends Thread {
    private final String text;
    private final File fileToEdit;

    SaveThread(String text, File fileToEdit) {
      this.text=text;
      this.fileToEdit=fileToEdit;
    }

    @Override
    public void run() {
      try {
        fileToEdit.getParentFile().mkdirs();

        FileOutputStream fos=new FileOutputStream(fileToEdit);

        Writer w=new BufferedWriter(new OutputStreamWriter(fos));

        try {
          w.write(text);
          w.flush();
          fos.getFD().sync();
        }
        finally {
          w.close();
        }
      }
      catch (IOException e) {
        Log.e(getClass().getSimpleName(), "Exception writing file", e);
      }
    }
  }

(from Files/FilesEditor/app/src/main/java/com/commonsware/android/fileseditor/EditorFragment.java)

While we use a Writer to do the writing, it is wrapped around a FileOutputStream, so we can get access to the FileDescriptor (via getFD()) and call sync() on it.

StrictMode: Avoiding Janky Code

Users are more likely to like your application if, to them, it feels responsive. Here, by “responsive”, we mean that it reacts swiftly and accurately to user operations, like taps and swipes.

Conversely, users are less likely to be happy with you if they perceive that your UI is “janky” — sluggish to respond to their requests. For example, maybe your lists do not scroll as smoothly as they would like, or tapping a button does not yield the immediate results they seek.

While threads and AsyncTask and the like can help, it may not always be obvious where you should be applying them. A full-scale performance analysis, using Traceview or similar Android tools, is certainly possible. However, there are a few standard sorts of things that developers do, sometimes quite by accident, on the main application thread that will tend to cause sluggishness:

  1. Flash I/O, both for internal and external storage
  2. Network I/O

However, even here, it may not be obvious that you are performing these operations on the main application thread. This is particularly true when the operations are really being done by Android’s code that you are simply calling.

That is where StrictMode comes in. Its mission is to help you determine when you are doing things on the main application thread that might cause a janky user experience.

StrictMode works on a set of policies. There are presently two categories of policies: VM policies and thread policies. The former represent bad coding practices that pertain to your entire application, notably leaking SQLite Cursor objects and kin. The latter represent things that are bad when performed on the main application thread, notably flash I/O and network I/O.

Each policy dictates what StrictMode should watch for (e.g., flash reads are OK but flash writes are not) and how StrictMode should react when you violate the rules, such as:

  1. Log a message to LogCat
  2. Display a dialog
  3. Crash your application (seriously!)

The simplest thing to do is call the static enableDefaults() method on StrictMode from onCreate() of your first activity. This will set up normal operation, reporting all violations by simply logging to LogCat. However, you can set your own custom policies via Builder objects if you so choose.

However, do not use StrictMode in production code. It is designed for use when you are building, testing, and debugging your application. It is not designed to be used in the field.

So, for example, you might have something like this in your launcher activity:


StrictMode.ThreadPolicy.Builder b=new StrictMode.ThreadPolicy.Builder();

if (BuildConfig.DEBUG) {
  b.detectAll().penaltyDeath();
}
else {
  b.detectAll().penaltyLog();
}

StrictMode.setThreadPolicy(b.build());

BuildConfig.DEBUG will be true for debuggable builds, false otherwise. So, in the case of a debug build, we want to detect all mistakes and crash the app immediately when we encounter them, but in production, we want to just log information about the mistake to LogCat.

You will note that the sample app does not contain this code. That is because calling methods like getFilesDir() and getExternalFilesDir() really ought to be on background threads, as StrictMode will complain about them. Hence, this code would cause SampleAdapter to crash when it tries building the File object to use. This could be rectified by having SampleAdapter simply pass in a flag indicating the storage location and having LoadThreadTask and SaveThread deal with the File objects.

Note that StrictMode will also report leaked open files. For example, if you create a FileOutputStream on a File and fail to close() it later, when the FileOutputStream (and related objects) are garbage-collected, StrictMode will report to you the fact that you failed to close the stream. This is very useful to help you make sure that you are not leaking open files that may contribute to exhausting the 1,024 open file limit on external storage.

Files, and Your Development Machine

All this reading and writing of data is nice, but for debugging and diagnostic purposes, it is often useful for you to be able to look at the files, other than through your app.

This is somewhat challenging, due to the lack of tools and due to security restrictions in production devices (as compared to emulators).

That being said, the following sections will outline some options that you have to access your app’s files independently of your app.

Mounting as a Drive

If you have an actual Android device, when you plug it in via a USB cable, usually you will get external storage available as a drive letter (Windows) or a mounted volume (OS X and Linux). Depending upon the device, manufacturer, and configuration, you might also have access to removable storage this way as well.

In these cases, you can use your development machine’s OS to poke around these file locations and look at your files (or anyone else’s).

However, there are some wrinkles:

Push and Pull for External Storage

You can get at external storage (and possibly removable storage) of devices and emulators via the command-line adb tool. This program is in platform-tools/ of your Android SDK installation, and it is a good idea to add that directory to your operating system’s PATH environment variable, so you can run adb from anywhere.

adb push and adb pull allow you to upload and download files, respectively. Both take the local path and the remote (device/emulator) path as command-line arguments, although in varying order:

For external storage, the root directory name varies by Android OS version:

So, for example, the following command would push an index.html file to the getExternalFilesDir() location for the primary device account, for an app whose application is your.package.name.here:


adb push index.html /storage/emulated/0/Android/data/your.package.name.here/files

If you try to push a local directory, or pull a remote directory, the contents of those directories will be uploaded and downloaded, respectively. However, the directory itself is not, which can cause some confusion.

Suppose we have a directory on our development PC named foo/. It contains four PNG files, named 1.png, 2.png, 3.png, and parallelism-is-boring.png. We then execute the following command on the command line:


adb push foo /storage/emulated/0/Android/data/your.package.name.here/files

You will wind up with:

Note, though, that the foo directory name is not included. In other words, the contents of foo/ are transferred, but not foo/ itself.

Run-As for Internal Storage

adb push and adb pull work directly for internal storage as well… on emulators.

On production hardware, though, you have some additional work to do. Specifically, you need to use external storage as an intermediary and use adb run-as to give yourself the temporary ability to work with internal storage.

For example, on an emulator, you could push index.html to the directory returned by getFilesDir(), for an app with an application ID of your.package.name.here, for the primary device account, via:


adb push index.html /data/data/your.package.name.here/files

If you try that on production hardware, it will fail. While the piece that adb communicates with on the emulator runs with superuser privileges, the equivalent piece on production hardware does not. The same security that prevents other apps from accessing your app’s portion of internal storage prevents adb from doing so as well.

However, adb on production hardware can use the run-as command, to execute a Linux command as if it were being run by the Linux user associated with your app, the user that owns all your files and who has read/write access to those files.

So, the equivalent script to copy the file to internal storage on a production Android 4.x/5.x device would be:


adb push index.html /mnt/shell/emulated/0
adb shell run-as your.package.name.here cp /mnt/shell/emulated/0/index.html /data/data/your.package.name.here/files
adb shell rm /mnt/shell/emulated/0/index.html

(note that the second command should appear all on one line, even though it may show up as word-wrapped here due to the length of the line and the available width of the book)

This will only work for debuggable apps, which is the normal state of apps that you run from your IDE. This script:

(if you are wondering why we do not use mv instead of cp and rm, mv generates errors related to attempting to change the ownership of the moved file)

XML Parsing Options

Android supports a fairly standard implementation of the Java DOM and SAX APIs. If you have existing experience with these, or if you have code that already leverages them, feel free to use them.

Android also bakes in the XmlPullParser from the xmlpull.org site. Like SAX, the XmlPullParser is an event-driven interface, compared to the DOM that builds up a complete data structure and hands you that result. Unlike SAX, which relies on a listener and callback methods, the XmlPullParser has you pull events off a queue, ignoring those you do not need and dispatching the rest as you see fit to the rest of your code.

The primary reason the XmlPullParser was put into Android was for XML-encoded resources. While you write plain-text XML during development, what is packaged in your APK file is a so-called “binary XML” format, where angle brackets and quotation marks and such are replaced by bitfields. This helps compression a bit, but mostly this conversion is done to speed up parsing. Android’s XML resource parser can parse this “binary XML” approximately ten times faster than it can parse the equivalent plain-text XML. Hence, anything you put in an XML resource (res/xml/) will be parsed similarly quickly.

For plain-text XML content, the XmlPullParser is roughly equivalent, speed-wise, to SAX. All else being equal, lean towards SAX, simply because more developers will be familiar with it from classic Java development. However, if you really like the XmlPullParser interface, feel free to use it.

You are welcome to try a third-party XML parser JAR, but bear in mind that there may be issues when trying to get it working in Android.

JSON Parsing Options

Android has bundled the org.json classes into the SDK since the beginning, for use in parsing JSON. These classes have a DOM-style interface: you hand JSONObject a hunk of JSON, and it gives you an in-memory representation of the completely parsed result. This is handy but, like the DOM, a bit of a performance hog.

API Level 11 added JSONReader, based on Google’s GSON parser, as a “streaming” parser alternative. JSONReader is much more reminiscent of the XmlPullParser, in that you pull events out of the “reader” and process them. This can have significant performance advantages, particularly in terms of memory consumption, if you do not need the entire JSON data structure. However, this is only available on API Level 11 and higher.

Because JSONReader is a bit “late to the party”, there has been extensive work on getting other JSON parsers working on Android. Google’s GSON is popular, as is Jackson. Jackson offers a few APIs, and the streaming API reportedly works very nicely on Android with top-notch performance.

Using Files with Implicit Intents

Earlier, we saw how to use an implicit Intent to, say, view a Web page, given an https URL. You can do the same sort of thing with files… though there are issues.

Technically, you can take any File, pass it to Uri.fromFile(), and get a Uri pointing to that file. You can put that Uri into an implicit Intent, such as one for ACTION_VIEW, and pass that Intent to startActivity():


startActivity(new Intent(Intent.ACTION_VIEW, Uri.fromFile(somethingCool)));

However, at best, this only works for files on external storage. Other apps — such as whatever activity handles your ACTION_VIEW request — do not have rights to your portion of internal storage or your portion of removable storage. Plus, you have no guarantee that the other app has either the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE permission (though, if it responded to your Intent, it should).

Hence, in Android 7.0, the file scheme on a Uri is banned, in effect. If you attempt to pass a file: Uri in an Intent that is going to another app, you will crash with a FileUriExposedException exception.

(you will face similar issues with putting file: Uri values on the clipboard in ClipData — coverage of the clipboard is later in this book)

This is coming from an updated edition of StrictMode. StrictMode.VmPolicy.Builder has a penaltyDeathOnFileUriExposure() method that triggers the detection of file: Uri values and the resulting FileUriExposedException exceptions. And, it appears that this is pre-configured, much as how StrictMode is pre-configured to apply penaltyDeathOnNetwork() (the source of your NetworkOnMainThreadException crashes).

However, this only kicks in if your targetSdkVersion is set to 24 or higher. At that point, you will need to find other ways of getting your content to other apps, such as via a class called FileProvider, which is covered later in this book. Or, you can also disable the check by configuring your own StrictMode.VmPolicy and skipping directFileUriExposure(), though this is not a great solution.

Visit the Trails!

In addition to this chapter, you can learn more about accessing multimedia files via the MediaStore and learn more about the impacts of multiple user accounts on tablets.