Cómo administrar el acceso específico al almacenamiento externo

A fin de ofrecer a los usuarios más control sobre sus archivos y limitar la sobrecarga de archivos, las apps orientandas a Android 10 (nivel de API 29) y versiones posteriores reciben acceso específico a un dispositivo de almacenamiento externo, o almacenamiento específico, de manera predeterminada. Esas apps solo pueden ver su directorio específico de la app, al que se puede acceder mediante Context.getExternalFilesDir(), y tipos de archivos multimedia específicos. La práctica recomendada es usar el almacenamiento específico, a menos que tu app necesite acceder a un archivo que no se encuentra ni en su directorio específico ni en MediaStore.

En la siguiente tabla, se resume la manera en que el almacenamiento específico afecta el acceso de archivos:

Ubicación del archivo Permisos requeridos Método de acceso (*) ¿Se quitan los archivos cuando se desinstala la app?
Directorio específico de la app Ninguno getExternalFilesDir()
Colecciones de contenido multimedia
(fotos, videos y audio)
READ_EXTERNAL_STORAGE
solo cuando
se accede a los archivos de otras apps
MediaStore No
Descargas
(documentos y
libros electrónicos)
Ninguno Marco de trabajo de acceso al almacenamiento
(carga el selector de archivos del sistema)
No

Puedes usar el marco de trabajo de acceso a almacenamiento para acceder a cada una de las ubicaciones que aparecen en la tabla anterior sin solicitar ningún permiso.

En esta página, se describen los archivos a los que tu app puede acceder al usar almacenamiento específico y cómo actualizar tu app para que pueda continuar compartiendo, accediendo y modificando otros archivos guardados en un dispositivo de almacenamiento externo.

Permisos necesarios para el acceso a archivos

Una app que usa almacenamiento específico siempre tiene acceso de lectura/escritura a los archivos que crea, tanto dentro como fuera de su directorio específico. Como resultado, si tu app guarda solo los archivos que crea y únicamente accede a ellos, no es necesario que solicites el permiso READ_EXTERNAL_STORAGE ni WRITE_EXTERNAL_STORAGE.

Para acceder archivos que crearon otras apps, sin embargo, deben cumplirse estas dos condiciones:

  1. Tu app recibió el permiso READ_EXTERNAL_STORAGE.
  2. Los archivos se alojan en una de las siguientes colecciones de contenido multimedia bien definidas:

Para acceder a cualquier otro archivo creado por otra app, incluidos los archivos en un directorio de "descargas", tu app debe usar el marco de trabajo de acceso al almacenamiento, que le permite al usuario seleccionar un archivo específico.

Incluso con el permiso READ_EXTERNAL_STORAGE, si un app accede a la vista del sistema de archivos sin procesar de un dispositivo de almacenamiento externo, la app puede acceder solo al directorio específico de la app. Si una app intenta abrir archivos fuera de este directorio con una vista del sistema de archivos sin procesar, se produce un error:

Restricciones de datos multimedia

El almacenamiento específico impone las siguientes restricciones de datos del contenido multimedia:

La guía que describe cómo trabajar con archivos multimedia proporciona prácticas recomendadas para acceder a documentos individuales y árboles de documentos dentro de MediaStore. Si tu app usa almacenamiento específico, estos métodos para acceder a archivos multimedia son necesarios.

Información sobre la ubicación en fotos

Algunas fotos contienen información sobre la ubicación en sus metadatos EXIF, lo que permite a los usuarios ver el lugar donde se tomaron. Como la información de ubicación es sensible, Android 10 la oculta de forma predeterminada de tu app si esta usa almacenamiento específico.

Si tu app necesita acceder a esa información, sigue estos pasos:

  1. Solicita el permiso ACCESS_MEDIA_LOCATION en el manifiesto de tu app.
  2. Desde tu objeto MediaStore, llama a setRequireOriginal() y especifica el URI de la fotografía, como se muestra en el siguiente fragmento de código:

    Kotlin

        // Get location data from the ExifInterface class.
        val photoUri = MediaStore.setRequireOriginal(photoUri)
        contentResolver.openInputStream(photoUri).use { stream ->
            ExifInterface(stream).run {
                // If lat/long is null, fall back to the coordinates (0, 0).
                val latLong = ?: doubleArrayOf(0.0, 0.0)
            }
        }
        

    Java

        Uri photoUri = Uri.withAppendedPath(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                cursor.getString(idColumnIndex));
    
        final double[] latLong;
    
        // Get location data from the ExifInterface class.
        photoUri = MediaStore.setRequireOriginal(photoUri);
        InputStream stream = getContentResolver().openInputStream(photoUri);
        if (stream != null) {
            ExifInterface exifInterface = new ExifInterface(stream);
            double[] returnedLatLong = exifInterface.getLatLong();
    
            // If lat/long is null, fall back to the coordinates (0, 0).
            latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
            // Don't reuse the stream associated with the instance of "ExifInterface".
            stream.close();
        } else {
            // Failed to load the stream, so return the coordinates (0, 0).
            latLong = new double[2];
        }
        

Cómo inhabilitar el almacenamiento específico

Antes de que tu app sea totalmente compatible con el almacenamiento específico, puedes inhabilitarlo temporalmente en el nivel del SDK de destino de tu app o el atributo del manifiesto requestLegacyExternalStorage:

  • Orienta la app a Android 9 (API nivel 28) o versiones anteriores.
  • Si la orientas a Android 10 o versiones posteriores, define el valor de requestLegacyExternalStorage en true en el archivo de manifiesto de tu app:

        <manifest ... >
          <!-- This attribute is "false" by default on apps targeting
               Android 10 or higher. -->
          <application android:requestLegacyExternalStorage="true" ... >
            ...
          </application>
        </manifest>
        

Para probar cómo funciona una app orientada a Android 9 o versiones anteriores con el almacenamiento específico, puedes inhabilitar el comportamiento mediante la configuración del valor requestLegacyExternalStorage en false.