The Android Developer Challenge is back! Submit your idea before December 2.

Save a file on external storage

Using external storage is great for files that you want to share with other apps or allow the user to access with a computer.

External storage is usually available through removable devices, such as an SD card. Android represents these devices using a path, such as /sdcard.

After you request storage permissions and verify that storage is available, you can save the following types of files:

  • Public files: Files that should be freely available to other apps and to the user. When the user uninstalls your app, these files should remain available to the user. For example, photos captured by your app should be saved as public files.
  • Private files: Files stored in an app-specific directory—accessed using Context.getExternalFilesDir(). These files are cleaned up when the user uninstalls your app. Although these files are technically accessible by the user and other apps because they're on the external storage, they don't provide value to the user outside of your app. Use this directory for files that you don't want to share with other apps.

This guide describes how to manage files that are available on a device's external storage device. For guidance on how to work with files in internal storage, see the guide on how to manage files in internal storage.

Set up a virtual external storage device

On devices without removable external storage, use the following command to enable a virtual disk for testing purposes:

adb shell sm set-virtual-disk true

Request external storage permissions

Android includes the following permissions for accessing files in external storage:

READ_EXTERNAL_STORAGE
Allows an app to access files within an external storage device.
WRITE_EXTERNAL_STORAGE
Allows an app to write and modify files within an external storage device. Apps that have this permission are also granted the READ_EXTERNAL_STORAGE permission automatically.

Beginning with Android 4.4 (API level 19), reading or writing files in your app-specific directory doesn't require any storage-related permissions. So if your app supports Android 4.3 (API level 18) and lower, and you want to access only your app-specific directory, you should declare that the permission be requested only on lower versions of Android by adding the maxSdkVersion attribute:

<manifest ...>
    <!-- If you need to modify files in external storage, request
         WRITE_EXTERNAL_STORAGE instead. -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
</manifest>

Verify that external storage is available

Because the external storage might be unavailable—such as when the user has mounted the storage on another machine or has removed the SD card that provides the external storage—you should always verify that the volume is available before accessing it. You can query the external storage state by calling getExternalStorageState(). If the returned state is MEDIA_MOUNTED, then you can read and write your files. If it's MEDIA_MOUNTED_READ_ONLY, you can only read the files.

For example, the following methods are useful to determine the storage availability:

Kotlin

/* Checks if external storage is available for read and write */
fun isExternalStorageWritable(): Boolean {
    return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}

/* Checks if external storage is available to at least read */
fun isExternalStorageReadable(): Boolean {
     return Environment.getExternalStorageState() in
        setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
}

Java

/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}

Save to a public directory

If you want to save files on external storage that other apps should be able to access, use one of the following APIs:

  • If you're saving a photo, audio file, or video clip, use the MediaStore API.
  • If you're saving any other file, such as a PDF document, use the ACTION_CREATE_DOCUMENT intent, which is part of the Storage Access Framework.

If you want to hide your files from the Media Scanner, include an empty file named .nomedia in your app-specific directory (note the dot prefix in the filename). This prevents the Media Scanner from reading your media files and providing them to other apps through the MediaStore API.

Save to a private directory

If you want to save files that are private to your app on external storage, you can acquire your app-specific directory by calling getExternalFilesDir() and passing it a name indicating the type of directory you'd like. Each directory created this way is added to a parent directory that encapsulates all your app's external storage files, which the system cleans up when the user uninstalls your app.

The following code snippet shows how you can create a directory for an individual photo album:

Kotlin

fun getPrivateAlbumStorageDir(context: Context, albumName: String): File? {
    // Get the directory for the app's private pictures directory.
    val file = File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName)
    if (!file?.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created")
    }
    return file
}

Java

public File getPrivateAlbumStorageDir(Context context, String albumName) {
    // Get the directory for the app's private pictures directory.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

It's important that you use directory names provided by API constants like DIRECTORY_PICTURES. These directory names ensure that the files are treated properly by the system. For instance, files saved in DIRECTORY_RINGTONES are categorized by the system media scanner as ringtones instead of music.

If none of the pre-defined sub-directory names suit your files, you can instead call getExternalFilesDir() and pass null. This returns the root directory for your app's private directory on the external storage.

Select between multiple storage locations

Sometimes, a device that allocates a partition of the internal memory for use as the external storage also provides an SD card slot. This means that the device has two different external storage directories, so you need to select which one to use when writing "private" files to the external storage.

Beginning with Android 4.4 (API level 19), you can access both locations by calling getExternalFilesDirs(), which returns a File array with entries for each storage location. The first entry in the array is considered the primary external storage, and you should use that location unless it's full or unavailable.

If your app supports Android 4.3 and lower, you should use the support library's static method, ContextCompat.getExternalFilesDirs(). This always returns a File array, but if the device is running Android 4.3 and lower, then it contains just one entry for the primary external storage. (If there's a second storage location, you cannot access it on Android 4.3 and lower.)

Unique volume names

Apps that target Android 10 (API level 29) or higher can access the unique name that the system assigns each external storage device. This naming system helps you efficiently organize and index content, and it gives you control over where new content is stored.

The primary shared storage device is always called VOLUME_EXTERNAL_PRIMARY. You can discover other volumes by calling MediaStore.getExternalVolumeNames().

To query, insert, update, or delete a specific volume, pass the volume name to any of the getContentUri() methods available in the MediaStore API, such as in the following code snippet:

// Assumes that the storage device of interest is the 2nd one
// that your app recognizes.
val volumeNames = MediaStore.getExternalVolumeNames(context)
val selectedVolumeName = volumeNames[1]
val collection = MediaStore.Audio.Media.getContentUri(selectedVolumeName)
// ... Use a ContentResolver to add items to the returned media collection.

Additional resources

For more information about saving files to the device's storage, consult the following resources.

Codelabs