Cómo acceder a archivos de contenido multimedia desde el almacenamiento compartido

Para proporcionar una experiencia del usuario más enriquecida, muchas apps permiten que los usuarios contribuyan y accedan al contenido multimedia disponible en un volumen de almacenamiento externo. El framework proporciona un índice optimizado para colecciones de contenido multimedia, llamado tienda de contenido multimedia, que permite recuperar y actualizar esos archivos con más facilidad. Incluso después de desinstalar la app, los archivos permanecen en el dispositivo del usuario.

Para interactuar con la abstracción de la tienda de contenido multimedia, usa un objeto ContentResolver que obtengas del contexto de tu app:

Kotlin

val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause

applicationContext.contentResolver.query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)?.use { cursor ->
    while (cursor.moveToNext()) {
        // Use an ID column from the projection to get
        // a URI representing the media item itself.
    }
}

Java

String[] projection = new String[] {
        media-database-columns-to-retrieve
};
String selection = sql-where-clause-with-placeholder-variables;
String[] selectionArgs = new String[] {
        values-of-placeholder-variables
};
String sortOrder = sql-order-by-clause;

Cursor cursor = getApplicationContext().getContentResolver().query(
    MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
);

while (cursor.moveToNext()) {
    // Use an ID column from the projection to get
    // a URI representing the media item itself.
}

El sistema analiza automáticamente un volumen de almacenamiento externo y agrega archivos multimedia a las siguientes colecciones bien definidas:

  • Imágenes, incluidas fotografías y capturas de pantalla, que se almacenan en los directorios DCIM/ y Pictures/. El sistema agrega estos archivos a la tabla MediaStore.Images.
  • Videos, que se almacenan en los directorios DCIM/, Movies/ y Pictures/. El sistema agrega estos archivos a la tabla MediaStore.Video.
  • Archivos de audio, que se almacenan en los directorios Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ y Ringtones/, así como listas de reproducción de audio que se encuentran en los directorios Music/ o Movies/. El sistema agrega estos archivos a la tabla MediaStore.Audio.
  • Archivos descargados, que se almacenan en el directorio Download/. En los dispositivos que ejecutan Android 10 (nivel 29 de API) y versiones posteriores, estos archivos se almacenan en la tabla MediaStore.Downloads. Esta tabla no está disponible en Android 9 (nivel 28 de API) ni en versiones anteriores.

La tienda de contenido multimedia también incluye una colección llamada MediaStore.Files. Su contenido depende de si la app usa el almacenamiento específico, que está disponible en apps orientadas a Android 10 o versiones posteriores:

  • Si el almacenamiento específico está habilitado, la colección solo muestra las fotos, los videos y los archivos de audio que creó tu app.
  • Si el almacenamiento específico no está disponible o no se usa, la colección muestra todos los tipos de archivos multimedia.

Cómo solicitar los permisos necesarios

Antes de realizar operaciones en archivos multimedia, asegúrate de que la app declare los permisos necesarios para acceder a esos archivos. No obstante, ten en cuenta que la app no debe declarar permisos que no necesita ni usa.

Permiso de almacenamiento

El modelo de permisos para acceder a los archivos multimedia en tu app depende de si esta usa almacenamiento específico, disponible en apps orientadas a Android 10 o versiones posteriores.

Almacenamiento específico habilitado

Si tu app usa el almacenamiento específico, debe solicitar permisos relacionados con el almacenamiento solo para dispositivos que ejecuten Android 9 (nivel 28 de API) o versiones anteriores. Para aplicar esta condición, agrega el atributo android:maxSdkVersion a la declaración de permisos en el archivo de manifiesto de la app:

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

No solicites innecesariamente permisos relacionados con el almacenamiento para dispositivos que ejecutan Android 10 o versiones posteriores. Tu app puede contribuir a colecciones de contenido multimedia bien definidas, incluida la colección MediaStore.Downloads, sin solicitar ningún permiso relacionado con el almacenamiento. Si estás desarrollando una app de cámara, por ejemplo, no necesitas solicitar permisos relacionados con el almacenamiento porque la app es propietaria de las imágenes que escribes en la tienda de contenido multimedia.

Para acceder a los archivos que crearon otras apps, se deben cumplir las siguientes condiciones:

En concreto, si tu app quiere acceder a un archivo incluido en la colección MediaStore.Downloads que no creó, debes usar el framework de acceso a almacenamiento. Si quieres obtener más información para usar este framework, consulta la guía sobre cómo acceder a documentos y otros archivos.

Almacenamiento específico no disponible

Si tu app se usa en un dispositivo que ejecuta Android 9 o versiones anteriores, o bien usa la función de compatibilidad de almacenamiento, debes solicitar el permiso READ_EXTERNAL_STORAGE para acceder a los archivos multimedia. Si deseas modificar archivos multimedia, también debes solicitar el permiso WRITE_EXTERNAL_STORAGE.

Permiso de ubicación de contenido multimedia

Si tu app usa almacenamiento específico, para que pueda obtener metadatos EXIF sin ocultar correspondientes a las fotos, debes declarar el permiso ACCESS_MEDIA_LOCATION en el manifiesto de la app y, luego, solicitar este permiso durante el tiempo de ejecución.

Cómo buscar una colección de contenido multimedia

Para encontrar contenido multimedia que cumpla con un conjunto determinado de condiciones, como una duración de 5 minutos o más, usa una declaración de selección similar a SQL, parecida a la que se muestra en el siguiente fragmento de código:

Kotlin

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
data class Video(val uri: Uri,
    val name: String,
    val duration: Int,
    val size: Int
)
val videoList = mutableListOf<Video>()

val projection = arrayOf(
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
)

// Show only videos that are at least 5 minutes in duration.
val selection = "${MediaStore.Video.Media.DURATION} >= ?"
val selectionArgs = arrayOf(
    TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString()
)

// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"

val query = ContentResolver.query(
    MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    // Cache column indices.
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    val nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
    val durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
        val duration = cursor.getInt(durationColumn)
        val size = cursor.getInt(sizeColumn)

        val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList += Video(contentUri, name, duration, size)
    }
}

Java

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.
class Video {
    private final Uri uri;
    private final String name;
    private final int duration;
    private final int size;

    public Video(Uri uri, String name, int duration, int size) {
        this.uri = uri;
        this.name = name;
        this.duration = duration;
        this.size = size;
    }
}
List<Video> videoList = new ArrayList<Video>();

String[] projection = new String[] {
    MediaStore.Video.Media._ID,
    MediaStore.Video.Media.DISPLAY_NAME,
    MediaStore.Video.Media.DURATION,
    MediaStore.Video.Media.SIZE
};
String selection = MediaStore.Video.Media.DURATION +
        " >= ?";
String[] selectionArgs = new String[] {
    String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
};
String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + " ASC";

try (Cursor cursor = getApplicationContext().getContentResolver().query(
    MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    // Cache column indices.
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    int nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
    int durationColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
    int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);

    while (cursor.moveToNext()) {
        // Get values of columns for a given video.
        long id = cursor.getLong(idColumn);
        String name = cursor.getString(nameColumn);
        int duration = cursor.getInt(durationColumn);
        int size = cursor.getInt(sizeColumn);

        Uri contentUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);

        // Stores column values and the contentUri in a local object
        // that represents the media file.
        videoList.add(new Video(contentUri, name, duration, size));
    }
}

Cuando realices una consulta de este tipo en tu app, ten en cuenta lo siguiente:

  • Llama al método query() en un subproceso del trabajador.
  • Almacena los índices de columna en caché para no tener que llamar a getColumnIndexOrThrow() cada vez que proceses una fila desde el resultado de la consulta.
  • Agrega el ID al URI de contenido, como se muestra en el fragmento de código.
  • Los dispositivos que ejecutan Android 10 y versiones posteriores requieren nombres de columna definidos en la API de MediaStore. Si una biblioteca dependiente de la app espera un nombre de columna que no está definido en la API, como "MimeType", usa CursorWrapper para traducir dinámicamente el nombre de la columna en el proceso de la app.

Cómo cargar miniaturas de archivos

Si la app muestra varios archivos multimedia y solicita que el usuario elija uno de estos, es más eficiente cargar versiones de vista previa (o miniaturas) de los archivos, en lugar de los archivos en sí.

Para cargar la miniatura de un archivo multimedia determinado, usa loadThumbnail() y pasa el tamaño de la miniatura que desees cargar, como se muestra en el siguiente fragmento de código:

Kotlin

// Load thumbnail of a specific media item.
val thumbnail: Bitmap =
        applicationContext.contentResolver.loadThumbnail(
        content-uri, Size(640, 480), null)

Java

// Load thumbnail of a specific media item.
Bitmap thumbnail =
        getApplicationContext().getContentResolver().loadThumbnail(
        content-uri, new Size(640, 480), null);

Cómo abrir un archivo multimedia

La lógica específica que uses para abrir un archivo multimedia dependerá de si la mejor representación del contenido multimedia es un descriptor de archivo o una transmisión de archivos:

Descriptor de archivo

Para abrir un archivo multimedia con un descriptor de archivo, usa una lógica similar a la que se muestra en el siguiente fragmento de código:

Kotlin

// Open a specific media item using ParcelFileDescriptor.
val resolver = applicationContext.contentResolver

// "rw" for read-and-write;
// "rwt" for truncating or overwriting existing file contents.
val readOnlyMode = "r"
resolver.openFileDescriptor(content-uri, readOnlyMode).use { pfd ->
    // Perform operations on "pfd".
}

Java

// Open a specific media item using ParcelFileDescriptor.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// "rw" for read-and-write;
// "rwt" for truncating or overwriting existing file contents.
String readOnlyMode = "r";
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(content-uri, readOnlyMode)) {
    // Perform operations on "pfd".
} catch (IOException e) {
    e.printStackTrace();
}

Transmisión de archivos

Para abrir un archivo multimedia con una transmisión de archivos, usa una lógica similar a la que se muestra en el siguiente fragmento de código:

Kotlin

// Open a specific media item using InputStream.
val resolver = applicationContext.contentResolver
resolver.openInputStream(content-uri).use { stream ->
    // Perform operations on "stream".
}

Java

// Open a specific media item using InputStream.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();
try (InputStream stream = resolver.openInputStream(content-uri)) {
    // Perform operations on "stream".
}

Consideraciones para tener en cuenta al acceder a contenido multimedia

Cuando accedas a contenido multimedia, ten en cuenta las consideraciones que se analizan en las siguientes secciones.

Volúmenes de almacenamiento

Las apps orientadas a Android 10 o versiones posteriores pueden acceder al nombre único que asigna el sistema a cada volumen de almacenamiento externo. Este sistema de nombres te ayuda a organizar e indexar el contenido de manera eficiente, y te permite controlar dónde se almacenan los nuevos archivos multimedia.

El volumen del almacenamiento compartido principal siempre se llama VOLUME_EXTERNAL_PRIMARY. Para descubrir otros volúmenes, llama a MediaStore.getExternalVolumeNames():

Kotlin

val volumeNames: Set<String> = MediaStore.getExternalVolumeNames(context)
val firstVolumeName = volumeNames.iterator().next()

Java

Set<String> volumeNames = MediaStore.getExternalVolumeNames(context);
String firstVolumeName = volumeNames.iterator().next();

Información de ubicación en fotografías

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

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

  1. Solicita el permiso ACCESS_MEDIA_LOCATION en el manifiesto de la app.
  2. Desde el objeto MediaStore, llama a setRequireOriginal() para obtener los bytes exactos de la fotografía y pasa el URI de correspondiente, como se muestra en el siguiente fragmento de código:

    Kotlin

    val photoUri: Uri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex)
    )
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    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 = 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 using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    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 compartir contenido 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 compartir archivos multimedia, usa un URI content://, como se recomienda en la guía para crear un proveedor de contenido.

Cómo acceder al contenido mediante rutas de archivos sin procesar

Si no tienes ningún permiso relacionado con el almacenamiento, puedes acceder a los archivos del directorio específico de la app y a los archivos multimedia atribuidos a la app; para ello, usa la API de File.

Si tu app intenta acceder a un archivo con la API de File y no tiene los permisos necesarios, se genera una FileNotFoundException.

Para acceder a otros archivos del almacenamiento compartido en un dispositivo con Android 10, se recomienda que configures requestLegacyExternalStorage como true en el archivo de manifiesto de tu app a fin de inhabilitar el almacenamiento específico.

Cómo acceder al contenido desde código nativo

Es posible que tu app necesite trabajar con un archivo multimedia específico en código nativo, como un archivo que otra app compartió con la tuya, o un archivo multimedia de la colección del usuario.

Antes de que tu app pueda leer archivos multimedia con métodos de archivo nativos como fopen(), debes hacer lo siguiente:

  1. Configura requestLegacyExternalStorage como true en el archivo de manifiesto de tu app.
  2. Solicita el permiso READ_EXTERNAL_STORAGE.

Si necesitas escribir en estos archivos multimedia, pasa el descriptor de archivo asociado del código basado en Java o Kotlin al código nativo. En el siguiente fragmento de código, se muestra cómo pasar el descriptor de archivo de un objeto multimedia al código nativo de la app:

Kotlin

val contentUri: Uri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(BaseColumns._ID))
val fileOpenMode = "r"
val parcelFd = resolver.openFileDescriptor(contentUri, 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(contentUri, 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 del código nativo, mira la charla Files for Miles de la Android Dev Summit de 2018 (a partir del minuto 15:20).

Cómo atribuir archivos multimedia con la app

Cuando el almacenamiento específico está habilitado para una app orientada a Android 10 o versiones posteriores, el sistema atribuye cada archivo multimedia a una app, lo que determina los archivos a los que puede acceder tu app si no solicitó ningún permiso de almacenamiento. Se puede atribuir cada archivo a una sola app. Por lo tanto, si tu app crea un archivo multimedia que se almacena en la colección de contenido multimedia de fotos, videos o archivos de audio, la app tendrá acceso al archivo.

Sin embargo, si el usuario desinstala y reinstala tu app, debes solicitar el permiso READ_EXTERNAL_STORAGE para acceder a los archivos que la app creó originalmente. Esta solicitud es obligatoria porque el sistema considera que se atribuye el archivo a la versión instalada previamente de la app, en lugar de a la que se instaló recientemente.

Cómo agregar un elemento

Para agregar un elemento multimedia a una colección existente, llama a un código similar al siguiente:

Kotlin

// Add a specific media item.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
// On API <= 28, use VOLUME_EXTERNAL instead.
val audioCollection = MediaStore.Audio.Media.getContentUri(
        MediaStore.VOLUME_EXTERNAL_PRIMARY)

// Publish a new song.
val newSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3")
}

// Keeps a handle to the new song's URI in case we need to modify it
// later.
val myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails)

Java

// Add a specific media item.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
// On API <= 28, use VOLUME_EXTERNAL instead.
Uri audioCollection = MediaStore.Audio.Media.getContentUri(
        MediaStore.VOLUME_EXTERNAL_PRIMARY);

// Publish a new song.
ContentValues newSongDetails = new ContentValues();
newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Song.mp3");

// Keeps a handle to the new song's URI in case we need to modify it
// later.
Uri myFavoriteSongUri = resolver
        .insert(audioCollection, newSongDetails);

Cómo activar o desactivar el estado pendiente para archivos multimedia

Si la app realiza operaciones que pueden tardar mucho tiempo, como escribir en archivos multimedia, resulta práctico contar con acceso exclusivo al archivo mientras se procesa. En dispositivos que ejecutan Android 10 o versiones posteriores, la app puede obtener ese acceso exclusivo si establece el valor de la marca IS_PENDING en 1. Solo tu app podrá ver el archivo hasta que cambie nuevamente el valor de IS_PENDING a 0.

En el siguiente fragmento de código, se muestra el ejemplo en mayor detalle del fragmento de código anterior. Ese fragmento muestra cómo usar la marca IS_PENDING cuando se almacena una canción larga en el directorio correspondiente a la colección MediaStore.Audio:

Kotlin

// Add a media item that other apps shouldn't see until the item is
// fully written to the media store.
val resolver = applicationContext.contentResolver

// Find all audio files on the primary external storage device.
// On API <= 28, use VOLUME_EXTERNAL instead.
val audioCollection = MediaStore.Audio.Media
        .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

val songDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3")
    put(MediaStore.Audio.Media.IS_PENDING, 1)
}

val songContentUri = resolver.insert(audioCollection, songDetails)

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

// Now that we're finished, release the "pending" status, and allow other apps
// to play the audio track.
songDetails.clear()
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0)
resolver.update(songContentUri, songDetails, null, null)

Java

// Add a media item that other apps shouldn't see until the item is
// fully written to the media store.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// Find all audio files on the primary external storage device.
// On API <= 28, use VOLUME_EXTERNAL instead.
Uri audioCollection = MediaStore.Audio.Media.getContentUri(
        MediaStore.VOLUME_EXTERNAL_PRIMARY);

ContentValues songDetails = new ContentValues();
newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Workout Playlist.mp3");
newSongDetails.put(MediaStore.Audio.Media.IS_PENDING, 1);

Uri songContentUri = resolver
        .insert(audioCollection, songDetails);

try (ParcelableFileDescriptor pfd =
        resolver.openFileDescriptor(longSongContentUri, "w", null)) {
    // Write data into the pending audio file.
}

// Now that we're finished, release the "pending" status, and allow other apps
// to play the audio track.
songDetails.clear();
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0);
resolver.update(longSongContentUri, songDetails, null, null);

Cómo brindar sugerencias sobre la ubicación de archivos

Cuando tu app almacena contenido multimedia en un dispositivo que ejecuta Android 10, se organiza ese contenido de forma predeterminada según el tipo. Por ejemplo, se colocan los archivos de imagen nuevos de forma predeterminada en el directorio Environment.DIRECTORY_PICTURES, que corresponde a la colección MediaStore.Images.

Si la app conoce la ubicación específica donde se deben almacenar los archivos, como un álbum de fotos llamado Fotos/MisFotosdeVacaciones, puedes establecer MediaColumns.RELATIVE_PATH para sugerir al sistema dónde almacenar archivos recién escritos.

Cómo actualizar un elemento

Para actualizar un archivo multimedia de propiedad de tu app, ejecuta un código similar al siguiente:

Kotlin

// Updates an existing media item.
val mediaId = // MediaStore.Audio.Media._ID of item to update.
val resolver = applicationContext.contentResolver

// When performing a single item update, prefer using the ID
val selection = "${MediaStore.Audio.Media._ID} = ?"

// By using selection + args we protect against improper escaping of // values.
val selectionArgs = arrayOf(mediaId.toString())

// Update an existing song.
val updatedSongDetails = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3")
}

// Use the individual song's URI to represent the collection that's
// updated.
val numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs)

Java

// Updates an existing media item.
long mediaId = // MediaStore.Audio.Media._ID of item to update.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();

// When performing a single item update, prefer using the ID
String selection = MediaStore.Audio.Media._ID + " = ?";

// By using selection + args we protect against improper escaping of
// values. Here, "song" is an in-memory object that caches the song's
// information.
String[] selectionArgs = new String[] { getId().toString() };

// Update an existing song.
ContentValues updatedSongDetails = new ContentValues();
updatedSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME,
        "My Favorite Song.mp3");

// Use the individual song's URI to represent the collection that's
// updated.
int numSongsUpdated = resolver.update(
        myFavoriteSongUri,
        updatedSongDetails,
        selection,
        selectionArgs);

Si el almacenamiento específico no está disponible o habilitado, el proceso que se muestra en el fragmento de código anterior también funcionará para los archivos que no son de la app.

Cómo actualizar los archivos multimedia de otras apps

Si tu app usa almacenamiento específico, por lo general no podrá actualizar un archivo multimedia que otra app colocó en el almacenamiento multimedia.

Sin embargo, es posible obtener el consentimiento del usuario para modificar el archivo si se captura la RecoverableSecurityException que arroja la plataforma. Luego puedes pedirle al usuario que le permita a tu app realizar operaciones de escritura en ese elemento específico, como se muestra en el siguiente fragmento de código:

Kotlin

// Apply a grayscale filter to the image at the given content URI.
try {
    contentResolver.openFileDescriptor(image-content-uri, "w")?.use {
        setGrayscaleFilter(it)
    }
} catch (securityException: SecurityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val recoverableSecurityException = securityException as?
            RecoverableSecurityException ?:
            throw RuntimeException(securityException.message, securityException)

        val intentSender =
            recoverableSecurityException.userAction.actionIntent.intentSender
        intentSender?.let {
            startIntentSenderForResult(intentSender, image-request-code,
                    null, 0, 0, 0, null)
        }
    } else {
        throw RuntimeException(securityException.message, securityException)
    }
}

Java

try {
    ParcelFileDescriptor imageFd = getContentResolver()
            .openFileDescriptor(image-content-uri, "w");
    setGrayscaleFilter(imageFd);
} catch (SecurityException securityException) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        RecoverableSecurityException recoverableSecurityException;
        if (securityException instanceof RecoverableSecurityException) {
            recoverableSecurityException =
                    (RecoverableSecurityException)securityException;
        } else {
            throw new RuntimeException(
                    securityException.getMessage(), securityException);
        }
        IntentSender intentSender =recoverableSecurityException.getUserAction()
                .getActionIntent().getIntentSender();
        startIntentSenderForResult(intentSender, image-request-code,
                null, 0, 0, 0, null);
    } else {
        throw new RuntimeException(
                securityException.getMessage(), securityException);
    }
}

Completa este proceso cada vez que tu app necesite modificar un archivo multimedia que no creó.

Si tu app tiene otro caso práctico que no abarca el almacenamiento específico, envía una solicitud de función y usa la función de compatibilidad de apps que proporciona la plataforma.

Cómo quitar un elemento

Para quitar un elemento que tu app ya no necesita en la tienda de contenido multimedia, usa una lógica similar a la que se muestra en el siguiente fragmento de código:

Kotlin

// Remove a specific media item.
val resolver = applicationContext.contentResolver

// URI of the image to remove.
val imageUri = "..."

// WHERE clause.
val selection = "..."
val selectionArgs = "..."

// Perform the actual removal.
val numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs)

Java

// Remove a specific media item.
ContentResolver resolver = getApplicationContext()
        getContentResolver();

// URI of the image to remove.
Uri imageUri = "...";

// WHERE clause.
String selection = "...";
String[] selectionArgs = "...";

// Perform the actual removal.
int numImagesRemoved = resolver.delete(
        imageUri,
        selection,
        selectionArgs);

Si el almacenamiento específico no está disponible o habilitado, puedes usar el fragmento de código anterior para quitar los archivos que pertenecen a otras apps. Sin embargo, si el almacenamiento específico está habilitado, deberás capturar una RecoverableSecurityException para cada archivo que la app desea quitar, según se describe en la sección sobre cómo actualizar los elementos multimedia.

Si tu app tiene otro caso práctico que no abarca el almacenamiento específico, envía una solicitud de función y usa la función de compatibilidad de apps que proporciona la plataforma.

Casos prácticos que requieren una alternativa a la tienda de contenido multimedia

Si tu app realiza principalmente una de las siguientes funciones, procura contar con una alternativa a las API de MediaStore.

Cómo administrar grupos de archivos multimedia

Las apps de creación de contenido multimedia suelen administrar grupos de archivos mediante una jerarquía de directorios. Para proporcionar esta capacidad en tu app, usa la acción de intent ACTION_OPEN_DOCUMENT_TREE, según se describe en la guía sobre cómo almacenar y acceder a documentos y otros archivos.

Cómo trabajar con otros tipos de archivos

Si tu app trabaja con documentos y archivos que no incluyen exclusivamente contenido multimedia, como archivos que usan la extensión EPUB o PDF, utiliza la acción de intent ACTION_OPEN_DOCUMENT, según se describe en la guía para almacenar y acceder a documentos y otros archivos.

Cómo compartir archivos en apps complementarias

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 de content://. Este flujo de trabajo es la práctica recomendada de seguridad.

Recursos adicionales

Para obtener más información sobre cómo almacenar y acceder al contenido multimedia, consulta los siguientes recursos.

Ejemplos

Videos