Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Cómo trabajar con archivos multimedia en almacenamiento externo

Las API de MediaStore proporcionan una interfaz para acceder a los siguientes tipos de archivos multimedia bien definidos:

MediaStore también incluye una colección llamada MediaStore.Files, que proporciona acceso a todos los tipos de archivos multimedia.

En esta guía, se describe cómo acceder y compartir archivos multimedia, que suelen estar almacenados en un dispositivo de almacenamiento externo.

Accede a archivos

Para cargar archivos multimedia, llama a uno de los siguientes métodos de ContentResolver:

  • Para un solo archivo multimedia, usa openFileDescriptor().
  • Para la miniatura de un solo archivo multimedia, usa loadThumbnail() y transfiere el tamaño de la miniatura que deseas cargar.
  • Para una colección de archivos multimedia, usa query().

En el siguiente fragmento de código, se ejemplifica cómo acceder a los archivos multimedia:

    val resolver = context.getContentResolver()

    // Open a specific media item.
    resolver.openFileDescriptor(item, mode).use { pfd ->
        // ...
    }

    // Load thumbnail of a specific media item.
    val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)

    // Find all videos on a given storage device, including pending files.
    val collection = MediaStore.Video.Media.getContentUri(volumeName)
    val collectionWithPending = MediaStore.setIncludePending(collection)
    resolver.query(collectionWithPending, null, null, null).use { c ->
        // ...
    }

    // Publish a video onto an external storage device.
    val values = ContentValues().apply {
        put(MediaStore.Audio.Media.RELATIVE_PATH, "Video/My Videos")
        put(MediaStore.Audio.Media.DISPLAY_NAME, "My Video.mp4")
    }
    val item = resolver.insert(collection, values)
    

Accede desde el código nativo

Es posible que en ocasiones tu app necesite trabajar con un archivo multimedia específico en código nativo, como un archivo que compartió otra app con la tuya o un archivo multimedia de la colección del usuario. En estos casos, inicia el descubrimiento de archivos multimedia en tu código basado en Java o Kotlin y, luego, transfiere a tu código nativo el descriptor del archivo asociado.

El siguiente fragmento de código ejemplifica cómo transferir el descriptor de archivo de un objeto multimedia al código nativo de tu app:

Kotlin

    val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(BaseColumns._ID))
    val fileOpenMode = "r"
    val parcelFd = resolver.openFileDescriptor(uri, fileOpenMode)
    val fd = parcelFd?.detachFd()
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
    

Java

    Uri contentUri = ContentUris.withAppendedId(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(Integer.parseInt(BaseColumns._ID)));
    String fileOpenMode = "r";
    ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(uri, fileOpenMode);
    if (parcelFd != null) {
        int fd = parcelFd.detachFd();
        // Pass the integer value "fd" into your native code. Remember to call
        // close(2) on the file descriptor when you're done using it.
    }
    

Para obtener más información sobre cómo acceder a los archivos en código nativo, mira la charla Files for Miles de la Android Dev Summit de 2018 (a partir del minuto 15:20).

Nombres de columnas en consultas de contenido

Si el código de tu app usa una proyección de nombre de columna, como mime_type AS MimeType, ten en cuenta que los dispositivos que ejecutan Android 10 (API nivel 29) y versiones posteriores requieren nombres de columnas que estén definidos en la API de MediaStore.

Si una biblioteca dependiente de tu app espera un nombre de columna que no está definido en la API de Android, como MimeType, usa CursorWrapper para traducir el nombre de la columna dinámicamente en el proceso de tu app.

Proporciona el estado pendiente para los archivos multimedia que se almacenan

En dispositivos que ejecutan Android 10 (API nivel 29) y versiones posteriores, tu app puede obtener acceso exclusivo a un archivo multimedia mientras se escribe en el disco mediante la marca IS_PENDING.

En el siguiente fragmento de código, se muestra cómo usar la marca IS_PENDING al almacenar una imagen en el directorio que corresponde a la colección de MediaStore.Images:

Kotlin

    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val resolver = context.getContentResolver()
    val collection = MediaStore.Images.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val item = resolver.insert(collection, values)

    resolver.openFileDescriptor(item, "w", null).use { pfd ->
        // Write data into the pending image.
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear()
    values.put(MediaStore.Images.Media.IS_PENDING, 0)
    resolver.update(item, values, null, null)
    

Java

    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    values.put(MediaStore.Images.Media.IS_PENDING, 1);

    ContentResolver resolver = context.getContentResolver();
    Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    Uri item = resolver.insert(collection, values);

    try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(item, "w", null)) {
        // Write data into the pending image.
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear();
    values.put(MediaStore.Images.Media.IS_PENDING, 0);
    resolver.update(item, values, null, null);
    

Actualiza los archivos multimedia de otras apps

Si tu app usa almacenamiento específico, por lo general, no puede actualizar un archivo multimedia que una app diferente colocó en el almacenamiento multimedia. De todos modos, es posible obtener el consentimiento del usuario para modificar el archivo. Para ello, se debe obtener el elemento RecoverableSecurityException que arroja la plataforma. Luego, puedes pedirle al usuario que le permita a tu app realizar operaciones de escritura en ese elemento, como se muestra en el siguiente fragmento de código:

Kotlin

    try {
        // ...
    } catch (rse: RecoverableSecurityException) {
        val requestAccessIntentSender = rse.userAction.actionIntent.intentSender

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null)
    }
    

Java

    try {
        // ...
    } catch (RecoverableSecurityException rse) {
        IntentSender requestAccessIntentSender = rse.getUserAction()
                .getActionIntent().getIntentSender();

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null);
    }
    

Proporciona una sugerencia para el lugar de almacenamiento de los archivos

Cuando tu app proporciona contenido multimedia en un dispositivo con Android 10 (API nivel 29), ese contenido se organiza según su tipo de manera predeterminada. Por ejemplo, los nuevos archivos de imagen se colocan de manera predeterminada en un directorio Environment.DIRECTORY_PICTURES, que corresponde a la colección MediaStore.Images.

Si tu app identifica una ubicación concreta donde se deberían almacenar los archivos, como Pictures/MyVacationPictures, puedes definir MediaColumns.RELATIVE_PATH para brindar al sistema una sugerencia sobre dónde almacenar los archivos escritos recientemente. De forma similar, puedes mover archivos en el disco durante una llamada a update() si cambias MediaColumns.RELATIVE_PATH o MediaColumns.DISPLAY_NAME

Casos prácticos comunes

En esta sección, se describe cómo completar varios casos prácticos comunes relacionados con archivos multimedia.

Comparte archivos multimedia

Algunas apps permiten el uso compartido de archivos multimedia entre ellas. Por ejemplo, las apps de redes sociales brindan a los usuarios la capacidad de compartir fotos y videos con amigos.

Para acceder a los archivos multimedia que los usuarios quieren compartir, usa el proceso que se describe en la sección sobre cómo acceder a archivos y cómo acceder a volúmenes que usan sus nombres únicos.

En los casos en los que proporciones un conjunto de apps complementarias (como una app de mensajería y una app de perfil), deberás configurar el uso compartido de archivos mediante los URI content://. Este flujo de trabajo es la práctica recomendada de seguridad.

Trabaja en documentos

Algunas apps usan documentos como la unidad de almacenamiento en donde los usuarios ingresan datos que quieren compartir con sus pares o importar a otros documentos. Entre algunos ejemplos, se incluye un usuario que abre un documento sobre la productividad del negocio o un libro guardado como archivo *.epub.

En estos casos, permite que el usuario elija qué archivo se abrirá invocando el intent ACTION_OPEN_DOCUMENT, que abrirá la app del selector de archivos del sistema. Para que solo se muestren los tipos de archivos compatibles con tu app, deberás agregar Intent.EXTRA_MIME_TYPES en tu intent.

En el ejemplo de ActionOpenDocument de GitHub, se muestra cómo usar ACTION_OPEN_DOCUMENT para abrir un archivo luego de obtener el consentimiento del usuario.

Administra grupos de archivos

Las apps de administración de archivos y creación de contenido multimedia generalmente administran grupos de archivos en una jerarquía de directorios. Estas apps pueden invocar el intent ACTION_OPEN_DOCUMENT_TREE para permitir que el usuario otorgue acceso a un árbol de directorios completo. Esa app podría editar cualquier archivo en el directorio seleccionado y cualquiera de los subdirectorios.

Con esta interfaz, los usuarios pueden acceder a los archivos desde cualquier instancia instalada de DocumentsProvider, que es compatible con todas las soluciones basadas en la nube o guardadas en copias locales.

En el ejemplo de ActionOpenDocumentTree de GitHub, se muestra cómo usar ACTION_OPEN_DOCUMENT_TREE para abrir un árbol de directorios luego de obtener el consentimiento del usuario.