Cómo guardar un archivo en almacenamiento externo

El almacenamiento externo es ideal para los archivos que quieres compartir con otras apps o para permitir que el usuario acceda con una computadora.

Por lo general, el almacenamiento externo está disponible a través de dispositivos extraíbles, como una tarjeta SD. Android representa estos dispositivos con una ruta, como /sdcard.

Una vez que solicitas permisos de almacenamiento y verificas que el almacenamiento está disponible, puedes guardar los siguientes tipos de archivos:

  • Archivos públicos: Archivos que deben estar disponibles de forma gratuita para otras apps y el usuario. Cuando se desinstala tu app, estos archivos deben seguir disponibles. Por ejemplo, las fotos tomadas con tu app se deben guardar como archivos públicos.
  • Archivos privados: Archivos almacenados en un directorio específico de la app al que se accede mediante Context.getExternalFilesDir(). Estos archivos se borran cuando el usuario desinstala tu app. Aunque, técnicamente, el usuario y otras apps pueden acceder a estos archivos porque están en el almacenamiento externo, no ofrecen ningún valor al usuario fuera de tu app. Usa este directorio para los archivos que no quieres compartir con otras apps.

En esta guía, se describe cómo administrar los archivos disponibles en el almacenamiento externo de un dispositivo. Si deseas obtener asistencia sobre cómo trabajar con archivos del almacenamiento interno, consulta la guía sobre cómo administrar archivos del almacenamiento interno.

Cómo configurar un dispositivo de almacenamiento externo virtual

En aquellos dispositivos sin almacenamiento externo extraíble, usa el siguiente comando para habilitar un disco virtual con fines de prueba:

    adb shell sm set-virtual-disk true
    

Cómo solicitar permisos de almacenamiento externo

Android incluye los siguientes permisos para acceder a los archivos del almacenamiento externo:

READ_EXTERNAL_STORAGE
Permite que una app acceda a los archivos de un dispositivo de almacenamiento externo.
WRITE_EXTERNAL_STORAGE
Permite que una app escriba y modifique archivos de un dispositivo de almacenamiento externo. Las apps con este permiso también obtienen automáticamente el permiso de READ_EXTERNAL_STORAGE.

A partir de Android 4.4 (API nivel 19), la lectura o escritura de archivos en tu directorio específico de la app no requiere ningún permiso relacionado con el almacenamiento. Por lo tanto, si tu app admite Android 4.3 (API nivel 18) y versiones anteriores, y solo quieres acceder al directorio específico de tu app, debes declarar que el permiso solo se solicite en las versiones anteriores de Android. Para ello, agrega el atributo maxSdkVersion:

    <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>
    

Cómo verificar la disponibilidad del almacenamiento externo

Dado que el almacenamiento externo puede no estar disponible, por ejemplo, si el usuario lo montó en otra máquina o extrajo la tarjeta SD que proporciona el almacenamiento externo, siempre debes verificar que el volumen esté disponible antes de acceder a él. Para consultar el estado del almacenamiento externo, puedes llamar a getExternalStorageState(). Si el estado que se muestra es MEDIA_MOUNTED, significa que puedes leer y escribir tus archivos. Si es MEDIA_MOUNTED_READ_ONLY, solo podrás leerlos.

Por ejemplo, los siguientes métodos son útiles para determinar la disponibilidad del almacenamiento:

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;
    }
    

Cómo guardar contenido en un directorio público

Si quieres guardar archivos en el almacenamiento externo a los que otras apps deberían poder acceder, usa una de las siguientes API:

  • Si guardas una foto, un archivo de audio o un clip de video, usa la API de MediaStore.
  • Si guardas cualquier otro tipo de archivo, como un documento PDF, usa el intent ACTION_CREATE_DOCUMENT, que forma parte del marco de trabajo de acceso al almacenamiento.

Si quieres ocultar tus archivos del escáner multimedia, incluye un archivo vacío llamado .nomedia en el directorio específico de tu app (ten en cuenta el prefijo del punto en el nombre del archivo). De esta manera, evitas que el escáner multimedia lea tus archivos multimedia y se los proporcione a otras apps mediante la API de MediaStore.

Cómo guardar contenido en un directorio privado

Si quieres guardar archivos privados de tu app en el almacenamiento externo, puedes acceder a tu directorio específico de la app llamando a getExternalFilesDir() y pasando un nombre que indique el tipo de directorio que deseas. Cada directorio que se crea de esta forma se agrega a un directorio principal que encapsula todos los archivos del almacenamiento externo de tu app, que el sistema borra cuando el usuario desinstala tu app.

El siguiente fragmento de código te muestra cómo crear un directorio para un álbum de fotos individual:

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;
    }
    

Es importante que uses los nombres de directorio que proporcionan las constantes de la API, como DIRECTORY_PICTURES. Estos nombres de directorio garantizan que el sistema trate de forma correcta los archivos. Por ejemplo, el escáner multimedia del sistema clasifica los archivos guardados en DIRECTORY_RINGTONES como tonos en lugar de música.

Si ninguno de los nombres de los subdirectorios predefinidos es adecuado para tus archivos, puedes llamar a getExternalFilesDir() y pasar null en su lugar. Se mostrará el directorio raíz del directorio privado de tu app en el almacenamiento externo.

Cómo seleccionar entre varias ubicaciones de almacenamiento

Algunas veces, un dispositivo que asigna una partición de la memoria interna para usar como almacenamiento externo también cuenta con una ranura para tarjeta SD. Esto significa que el dispositivo tiene dos directorios de almacenamiento externo diferentes, de manera que debes seleccionar cuál quieres usar cuando escribes archivos "privados" en el almacenamiento externo.

A partir de Android 4.4 (API nivel 19), puedes acceder a las dos ubicaciones si llamas a getExternalFilesDirs(), lo que muestra una arreglo File con entradas para cada ubicación de almacenamiento. La primera entrada del arreglo se considera el almacenamiento externo principal y debes usar esa ubicación, a menos que esté llena o no se encuentre disponible.

Si tu app admite Android 4.3 y versiones anteriores, debes usar ContextCompat.getExternalFilesDirs(), el método estático de la biblioteca de compatibilidad. Este método siempre muestra un arreglo File, pero si el dispositivo tiene instalado Android 4.3 o versiones anteriores, quiere decir que contiene solo una entrada para el almacenamiento externo principal. (Si hay una segunda ubicación de almacenamiento, no puedes acceder a ella en Android 4.3 y versiones anteriores.)

Nombres de volumen únicos

Las apps que se orientan a Android 10 (API nivel 29) o versiones posteriores pueden acceder al nombre único que el sistema asigna a cada dispositivo de almacenamiento externo. Este sistema de nombres únicos te ayuda a organizar e indexar contenido de manera eficiente y, además, te permite controlar dónde se almacena el contenido nuevo.

El dispositivo de almacenamiento compartido principal siempre se llama VOLUME_EXTERNAL_PRIMARY. Puedes llamar a MediaStore.getExternalVolumeNames() para descubrir otros volúmenes.

Para buscar, insertar, actualizar o borrar un volumen específico, transfiere el nombre del volumen a cualquiera de los métodos getContentUri() disponibles en la API de MediaStore, como en el siguiente fragmento de código:

    // 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.
    

Recursos adicionales

Para obtener más información sobre cómo guardar archivos en el almacenamiento del dispositivo, consulta los siguientes recursos.

Codelabs