Accéder aux fichiers multimédias de l'espace de stockage partagé

Pour offrir une expérience utilisateur plus riche, de nombreuses applications permettent aux utilisateurs de contribuer et d'accéder à des contenus multimédias disponibles sur un volume de stockage externe. Le framework fournit un index optimisé des collections multimédias, appelé Media Store, qui permet aux utilisateurs de récupérer et de mettre à jour plus facilement ces fichiers multimédias. Même après la désinstallation de votre application, ces fichiers restent sur l'appareil de l'utilisateur.

Sélecteur de photos

Comme alternative à Media Store, l'outil de sélection de photos d'Android offre aux utilisateurs un moyen intégré et sécurisé de sélectionner des fichiers multimédias, sans avoir à autoriser votre application à accéder à l'ensemble de leur bibliothèque multimédia. Cette fonction n'est disponible que sur les appareils pris en charge. Pour en savoir plus, consultez le guide du sélecteur de photos.

Media Store

Pour interagir avec l'abstraction du Media Store, utilisez un objet ContentResolver que vous récupérez à partir du contexte de votre application :

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

Le système analyse automatiquement un volume de stockage externe et ajoute des fichiers multimédias aux collections suivantes définies correctement :

  • Images, où les images (y compris les photographies et les captures d'écran) sont stockées dans les répertoires DCIM/ et Pictures/. Le système ajoute ces fichiers à la table MediaStore.Images.
  • Vidéos, où les vidéos sont stockées dans les répertoires DCIM/, Movies/ et Pictures/. Le système ajoute ces fichiers à la table MediaStore.Video.
  • Fichiers audio, où les fichiers audio sont stockés dans les répertoires Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ et Ringtones/. En outre, le système reconnaît les playlists audio figurant dans les répertoires Music/ ou Movies/, ainsi que les enregistrements vocaux figurant dans le répertoire Recordings/. Le système ajoute ces fichiers à la table MediaStore.Audio. Le répertoire Recordings/ n'est pas disponible sur Android 11 (niveau d'API 30) ou version antérieure.
  • Fichiers téléchargés, où les téléchargements sont stockés dans le répertoire Download/. Sur les appareils équipés d'Android 10 (niveau d'API 29) ou version ultérieure, ces fichiers sont stockés dans la table MediaStore.Downloads. Cette table n'est pas disponible sur Android 9 (niveau d'API 28) ou version antérieure.

Media Store inclut également une collection appelée MediaStore.Files. Son contenu varie selon que votre application utilise un espace de stockage cloisonné, disponible dans les applications qui ciblent Android 10 ou version ultérieure, ou pas.

  • Si l'espace de stockage cloisonné est activé, la collection n'affiche que les photos, vidéos et fichiers audio créés par votre application. La plupart des développeurs n'ont pas besoin d'utiliser MediaStore.Files pour afficher les fichiers multimédias d'autres applications. Toutefois, si vous définissez cette exigence spécifique, vous pouvez déclarer l'autorisation READ_EXTERNAL_STORAGE. Nous vous recommandons toutefois d'utiliser les API MediaStore pour ouvrir les fichiers que votre application n'a pas créés.
  • Si le stockage cloisonné est indisponible ou inutilisé, la collection affiche tous les types de fichiers multimédias.

Demander les autorisations nécessaires

Avant d'effectuer des opérations sur des fichiers multimédias, assurez-vous que votre application a déclaré les autorisations nécessaires pour accéder à ces fichiers. Veillez toutefois à ne pas déclarer d'autorisations dont votre application n'a pas besoin.

Autorisations de stockage

Votre application peut avoir besoin d'autorisations pour accéder au stockage selon qu'elle accède uniquement à ses propres fichiers multimédias ou à des fichiers créés par d'autres applications.

Accéder à vos propres fichiers multimédias

Sur les appareils équipés d'Android 10 ou version ultérieure, vous n'avez besoin d'aucune autorisation liée au stockage pour accéder aux fichiers multimédias dont votre application est propriétaire, y compris les fichiers de la collection MediaStore.Downloads. Par exemple, si vous développez une application d'appareil photo, vous n'avez pas besoin de demander d'autorisations liées au stockage pour accéder aux photos qu'elle prend, car elle est propriétaire des images que vous écrivez dans Media Store.

Accéder aux fichiers multimédias d'autres applications

Pour accéder aux fichiers multimédias créés par d'autres applications, vous devez déclarer les autorisations de stockage appropriées. Les fichiers doivent se trouver dans l'une des collections multimédias suivantes :

Tant qu'un fichier est visible à partir des requêtes MediaStore.Images, MediaStore.Video ou MediaStore.Audio, il est également visible à l'aide de la requête MediaStore.Files.

L'extrait de code suivant montre comment déclarer les autorisations de stockage appropriées :

<!-- Required only if your app needs to access images or photos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<!-- Required only if your app needs to access videos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- Required only if your app needs to access audio files
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

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

Autorisations supplémentaires requises pour les applications exécutées sur d'anciens appareils

Si votre application est utilisée sur un appareil équipé d'Android 9 ou version antérieure, ou si elle a désactivé le stockage cloisonné de manière temporaire, vous devez demander l'autorisation READ_EXTERNAL_STORAGE pour accéder à n'importe quel fichier multimédia. Si vous souhaitez modifier des fichiers multimédias, vous devez également demander l'autorisation WRITE_EXTERNAL_STORAGE.

Framework d'accès au stockage requis pour accéder aux téléchargements d'autres applications

Si votre application souhaite accéder à un fichier de la collection MediaStore.Downloads qu'elle n'a pas créé, vous devez utiliser le framework d'accès au stockage. Pour en savoir plus sur l'utilisation de ce framework, consultez Accéder à des documents et d'autres fichiers de l'espace de stockage partagé.

Autorisation d'accéder aux emplacements multimédias

Si votre application cible Android 10 (niveau d'API 29) ou version ultérieure et qu'elle doit récupérer des métadonnées EXIF non masquées à partir de photos, vous devez déclarer l'autorisation ACCESS_MEDIA_LOCATION dans son fichier manifeste, puis demander cette autorisation lors de l'exécution.

Rechercher les mises à jour du Media Store

Pour accéder aux fichiers multimédias de manière plus fiable, en particulier si votre application met en cache les URI ou les données depuis le Media Store, vérifiez si la version de ce dernier a changé par rapport à la dernière synchronisation de vos données multimédias. Pour effectuer cette recherche de mises à jour, appelez getVersion(). La version affichée est une chaîne unique qui change chaque fois que le Media Store change de manière significative. Si la version affichée est différente de la dernière version synchronisée, analysez et synchronisez à nouveau le cache multimédia de votre application.

Effectuez cette vérification au moment du démarrage du processus de l'application. Il n'est pas nécessaire de vérifier la version chaque fois que vous interrogez le Media Store.

Ne partez pas du principe que les détails de l'implémentation sont liés au numéro de version.

Interroger une collection multimédia

Pour rechercher un contenu multimédia qui répond à un ensemble de conditions spécifique (par exemple, une durée de cinq minutes ou plus), utilisez une instruction de sélection de type SQL semblable à celle présentée dans l'extrait de code suivant :

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 collection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Video.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL
        )
    } else {
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    }

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(
    collection,
    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>();

Uri collection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
} else {
    collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}

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(
    collection,
    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));
    }
}

Lorsque vous effectuez une telle requête dans votre application, tenez compte des points suivants :

  • Appelez la méthode query() dans un thread de nœud de calcul.
  • Mettez en cache les index de colonne afin de ne pas avoir à appeler getColumnIndexOrThrow() chaque fois que vous traitez une ligne à partir du résultat de la requête.
  • Ajoutez l'ID à l'URI de contenu, comme indiqué dans cet exemple.
  • Les appareils équipés d'Android 10 ou d'une version ultérieure nécessitent des noms de colonne définis dans l'API MediaStore. Si une bibliothèque dépendante dans votre application s'attend à un nom de colonne non défini dans l'API, comme "MimeType", utilisez CursorWrapper pour traduire la colonne de manière dynamique dans le processus de votre application.

Charger les vignettes des fichiers

Si votre application affiche plusieurs fichiers multimédias et demande à l'utilisateur de choisir l'un de ces fichiers, il est plus efficace de charger les versions preview (ou les vignettes) des fichiers plutôt que les fichiers eux-mêmes.

Pour charger la vignette d'un fichier multimédia donné, utilisez loadThumbnail() et indiquez la taille de la vignette à charger, comme indiqué ci-dessous dans l'extrait de code :

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

Ouvrir un fichier multimédia

La logique spécifique à utiliser pour ouvrir un fichier multimédia varie selon que le contenu multimédia est mieux représenté par un descripteur de fichier, un flux de fichiers ou un chemin d'accès direct.

Descripteur de fichier

Pour ouvrir un fichier multimédia à l'aide d'un descripteur de fichier, utilisez une logique semblable à celle présentée dans l'extrait de code suivant :

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

Flux de fichiers

Pour ouvrir un fichier multimédia à l'aide d'un flux de fichiers, utilisez une logique semblable à celle présentée dans l'extrait de code suivant :

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".
}

Chemins d'accès directs

Pour améliorer le fonctionnement de votre application avec des bibliothèques multimédias tierces, utilisez Android 11 (niveau d'API 30) ou une version ultérieure afin d'utiliser d'autres API que l'API MediaStore pour accéder aux fichiers multimédias d'un espace de stockage partagé. Vous pouvez accéder aux fichiers multimédias directement à l'aide de l'une des API suivantes :

  • L'API File
  • Les bibliothèques natives, telles que fopen()

Si vous ne disposez pas d'autorisations de stockage, vous pouvez accéder aux fichiers du répertoire propre à votre application, ainsi qu'aux fichiers multimédias attribués à votre application, à l'aide de l'API File.

Si votre application tente d'accéder à un fichier à l'aide de l'API File et qu'elle ne dispose pas des autorisations nécessaires, une erreur FileNotFoundException se produit.

Pour accéder à d'autres fichiers du stockage partagé sur un appareil exécutant Android 10 (niveau d'API 29), nous vous recommandons de désactiver temporairement le stockage cloisonné en définissant requestLegacyExternalStorage sur true dans le fichier manifeste de votre application. Pour accéder aux fichiers multimédias à l'aide de méthodes de fichiers natives sous Android 10, vous devez également demander l'autorisation READ_EXTERNAL_STORAGE.

Points à prendre en compte concernant l'accès au contenu multimédia

Lorsque vous accédez à du contenu multimédia, tenez compte des considérations décrites dans les sections suivantes.

Données en cache

Si votre application met en cache les URI ou les données du Media Store, recherchez régulièrement des mises à jour pour celui-ci. Cette vérification permet de synchroniser vos données en cache côté application avec celles du fournisseur côté système.

Performances

Lorsque vous effectuez des lectures séquentielles de fichiers multimédias à l'aide de chemins d'accès directs, les performances sont comparables à celles de l'API MediaStore.

Toutefois, lorsque vous effectuez des lectures et écritures aléatoires de fichiers multimédias à l'aide de chemins d'accès directs, le processus peut être jusqu'à deux fois plus lent. Dans ce cas, nous vous recommandons d'utiliser plutôt l'API MediaStore.

Colonne DATA

Lorsque vous accédez à un fichier multimédia existant, vous pouvez utiliser la valeur de la colonne DATA dans votre logique. Cette valeur possède un chemin d'accès valide. Toutefois, ne partez pas du principe que le fichier est toujours disponible. Préparez-vous à gérer les erreurs d'E/S basées sur les fichiers.

En revanche, pour créer ou mettre à jour un fichier multimédia, n'utilisez pas la valeur de la colonne DATA. Utilisez plutôt les valeurs des colonnes DISPLAY_NAME et RELATIVE_PATH.

Volumes de stockage

Les applications qui ciblent Android 10 ou une version ultérieure peuvent accéder au nom unique attribué par le système à chaque volume de stockage externe. Ce système d'attribution de noms vous aide à organiser et à indexer efficacement le contenu, et vous permet de contrôler l'emplacement de stockage des nouveaux fichiers multimédias.

Les volumes suivants sont particulièrement utiles :

  • Le volume VOLUME_EXTERNAL fournit une vue de tous les volumes de stockage partagés sur l'appareil. Vous pouvez lire le contenu de ce volume synthétique, mais vous ne pouvez pas le modifier.
  • Le volume VOLUME_EXTERNAL_PRIMARY représente le volume de stockage principal partagé sur l'appareil. Vous pouvez lire et modifier le contenu de ce volume.

Vous pouvez découvrir d'autres volumes en appelant 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();

Emplacement où le contenu multimédia a été capturé

Certaines photographies et vidéos contiennent des informations de localisation dans leurs métadonnées. Ces informations indiquent le lieu où une photo a été prise ou l'endroit où une vidéo a été enregistrée.

La manière dont vous accédez à ces informations de localisation dans votre application varie selon que vous avez besoin d'accéder à ces informations pour une photo ou une vidéo.

Photographies

Si votre application utilise un espace de stockage cloisonné, le système masque les informations de localisation par défaut. Pour accéder à ces informations, procédez comme suit :

  1. Demandez l'autorisation ACCESS_MEDIA_LOCATION dans le fichier manifeste de votre application.
  2. Depuis votre objet MediaStore, récupérez les octets exacts de la photo en appelant setRequireOriginal() et en transmettant l'URI de la photo, comme indiqué dans l'extrait de code suivant :

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

Vidéos

Pour accéder aux informations de localisation dans les métadonnées d'une vidéo, utilisez la classe MediaMetadataRetriever, comme indiqué dans l'extrait de code suivant. Votre application n'a pas besoin de demander d'autorisations supplémentaires pour utiliser cette classe.

Kotlin

val retriever = MediaMetadataRetriever()
val context = applicationContext

// Find the videos that are stored on a device by querying the video collection.
val query = ContentResolver.query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
query?.use { cursor ->
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        val videoUri: Uri = ContentUris.withAppendedId(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            id
        )
        extractVideoLocationInfo(videoUri)
    }
}

private fun extractVideoLocationInfo(videoUri: Uri) {
    try {
        retriever.setDataSource(context, videoUri)
    } catch (e: RuntimeException) {
        Log.e(APP_TAG, "Cannot retrieve video file", e)
    }
    // Metadata uses a standardized format.
    val locationMetadata: String? =
            retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
}

Java

MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Context context = getApplicationContext();

// Find the videos that are stored on a device by querying the video collection.
try (Cursor cursor = context.getContentResolver().query(
    collection,
    projection,
    selection,
    selectionArgs,
    sortOrder
)) {
    int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    while (cursor.moveToNext()) {
        long id = cursor.getLong(idColumn);
        Uri videoUri = ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
        extractVideoLocationInfo(videoUri);
    }
}

private void extractVideoLocationInfo(Uri videoUri) {
    try {
        retriever.setDataSource(context, videoUri);
    } catch (RuntimeException e) {
        Log.e(APP_TAG, "Cannot retrieve video file", e);
    }
    // Metadata uses a standardized format.
    String locationMetadata = retriever.extractMetadata(
            MediaMetadataRetriever.METADATA_KEY_LOCATION);
}

Partage

Certaines applications permettent aux utilisateurs de s'échanger des fichiers multimédias. Par exemple, les applications de réseaux sociaux leur permettent de partager des photos et des vidéos avec leurs amis.

Pour partager des fichiers multimédias, utilisez un URI content://, comme recommandé dans le guide de création d'un fournisseur de contenu.

Attribution de fichiers multimédias aux applications

Lorsque l'espace de stockage cloisonné est activé pour une application qui cible Android 10 ou version ultérieure, le système attribue une application à chaque fichier multimédia, ce qui détermine les fichiers auxquels votre application peut accéder quand elle n'a demandé aucune autorisation de stockage. Chaque fichier ne peut être attribué qu'à une seule application. Par conséquent, si votre application crée un fichier multimédia stocké dans les collections Photos, Vidéos ou Fichiers audio, elle a accès au fichier.

Toutefois, si l'utilisateur désinstalle et réinstalle votre application, vous devez demander l'autorisation READ_EXTERNAL_STORAGE pour accéder aux fichiers créés par votre application. Cette demande d'autorisation est nécessaire, car le système considère que le fichier est attribué à la version précédemment installée de l'application, et non à celle que vous venez d'installer.

Ajouter un élément

Pour ajouter un élément multimédia à une collection existante, utilisez un code semblable à ce qui suit. Cet extrait de code accède au volume VOLUME_EXTERNAL_PRIMARY sur les appareils équipés d'Android 10 ou version ultérieure. Sur ces appareils, vous ne pouvez modifier le contenu d'un volume que s'il s'agit du volume principal, comme décrit dans la section Volumes de stockage.

Kotlin

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

// Find all audio files on the primary external storage device.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

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

// Keep a handle to the new song's URI in case you 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.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

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

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

Activer/désactiver l'état "En attente" pour les fichiers multimédias

Si votre application effectue des opérations potentiellement chronophages, comme écrire dans des fichiers multimédias, il est utile d'avoir un accès exclusif au fichier en cours de traitement. Sur les appareils dotés d'Android 10 ou version ultérieure, votre application peut obtenir cet accès exclusif en définissant la valeur de l'option IS_PENDING sur 1. Seule votre application peut afficher le fichier jusqu'à ce que votre application redéfinisse la valeur de IS_PENDING sur 0.

L'extrait de code suivant s'appuie sur l'extrait de code précédent. Il montre comment utiliser l'option IS_PENDING lors du stockage d'un long morceau de musique dans le répertoire correspondant à la collection MediaStore.Audio :

Kotlin

// Add a media item that other apps don'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.
val audioCollection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Audio.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL_PRIMARY
        )
    } else {
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

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)

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

// Now that you're finished, release the "pending" status and let other apps
// 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 don'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.
Uri audioCollection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    audioCollection = MediaStore.Audio.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
} else {
    audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}

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

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

// "w" for write.
try (ParcelFileDescriptor pfd =
        resolver.openFileDescriptor(songContentUri, "w", null)) {
    // Write data into the pending audio file.
}

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

Donner un indice sur l'emplacement du fichier

Lorsque votre application stocke des contenus multimédias sur un appareil exécutant Android 10, ils sont organisés par défaut en fonction de leur type. Par exemple, les nouveaux fichiers image sont placés par défaut dans le répertoire Environment.DIRECTORY_PICTURES, qui correspond à la collection MediaStore.Images.

Si votre application connaît un emplacement spécifique où stocker les fichiers, par exemple un album photo appelé Pictures/MyVacationPictures, vous pouvez définir MediaColumns.RELATIVE_PATH pour indiquer au système où stocker les fichiers nouvellement créés.

Mettre à jour un élément

Pour mettre à jour un fichier multimédia appartenant à votre application, utilisez un extrait de code semblable à celui-ci :

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 you 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 you 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 l'espace de stockage cloisonné est indisponible ou désactivé, le processus affiché dans l'extrait de code précédent fonctionne également pour les fichiers n'appartenant pas à votre application.

Mettre à jour en code natif

Si vous devez écrire des fichiers multimédias à l'aide de bibliothèques natives, transmettez le descripteur du fichier associé de votre code Java ou Kotlin à votre code natif.

L'extrait de code suivant montre comment transmettre le descripteur de fichier d'un objet multimédia au code natif de votre application :

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

Mettre à jour les fichiers multimédias d'autres applications

Si votre application utilise un espace de stockage cloisonné, elle ne peut généralement pas mettre à jour un fichier multimédia qu'une autre application a ajouté à Media Store.

Vous pouvez toutefois obtenir le consentement de l'utilisateur pour modifier le fichier, en interceptant la RecoverableSecurityException que la plate-forme génère. Vous pouvez ensuite demander à l'utilisateur d'accorder à votre application un accès en écriture à cet élément spécifique, comme indiqué dans l'extrait de code suivant :

Kotlin

// Apply a grayscale filter to the image at the given content URI.
try {
    // "w" for write.
    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 {
    // "w" for write.
    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);
    }
}

Suivez cette procédure chaque fois que votre application doit modifier un fichier multimédia qu'elle n'a pas créé.

Si votre application exécute Android 11 ou version ultérieure, vous pouvez également permettre aux utilisateurs d'accorder à votre application l'accès en écriture à un groupe de fichiers multimédias. Utilisez la méthode createWriteRequest(), comme décrit dans la section concernant la gestion des groupes de fichiers multimédias.

Si votre application utilise un autre cas d'utilisation qui n'est pas couvert par l'espace de stockage cloisonné, envoyez une demande de fonctionnalité et désactivez temporairement l'espace de stockage cloisonné.

Supprimer un élément

Pour supprimer un élément dont votre application n'a plus besoin dans Media Store, utilisez une logique semblable à celle présentée dans l'extrait de code suivant :

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 le stockage cloisonné est indisponible ou n'est pas activé, vous pouvez utiliser l'extrait de code précédent pour supprimer les fichiers appartenant à d'autres applications. Si le stockage cloisonné est activé, vous devez intercepter RecoverableSecurityException pour chaque fichier à supprimer, comme décrit dans la section sur la mise à jour des éléments multimédias.

Si votre application fonctionne sous Android 11 ou version ultérieure, vous pouvez autoriser les utilisateurs à choisir un groupe de fichiers multimédias à supprimer. Utilisez les méthodes createTrashRequest() ou createDeleteRequest(), comme décrit dans la section sur la gestion des groupes de fichiers multimédias.

Si votre application utilise un autre cas d'utilisation qui n'est pas couvert par l'espace de stockage cloisonné, envoyez une demande de fonctionnalité et désactivez temporairement l'espace de stockage cloisonné.

Détecter les mises à jour de fichiers multimédias

Votre application devra peut-être identifier les volumes de stockage contenant des fichiers multimédias ajoutés ou modifiés par des applications, en effectuant une comparaison par rapport à un moment antérieur. Pour détecter ces modifications de manière plus fiable, transmettez le volume de stockage qui vous intéresse à getGeneration(). Tant que la version de Media Store ne change pas, la valeur renvoyée par cette méthode augmente de manière linéaire au fil du temps.

En particulier, getGeneration() est plus robuste que les dates dans les colonnes multimédias, telles que DATE_ADDED et DATE_MODIFIED. En effet, ces valeurs de colonne peuvent changer lorsqu'une application appelle setLastModified() ou lorsque l'utilisateur modifie l'horloge système.

Gérer des groupes de fichiers multimédias

Sous Android 11 ou version ultérieure, vous pouvez demander à l'utilisateur de sélectionner un groupe de fichiers multimédias, puis de mettre à jour ces fichiers multimédias en une seule opération. Ces méthodes améliorent la cohérence entre les appareils et permettent aux utilisateurs de gérer plus facilement leurs collections multimédias.

Les méthodes qui fournissent cette fonctionnalité de "mise à jour groupée" sont les suivantes :

createWriteRequest()
Demande à l'utilisateur d'accorder à votre application l'accès en écriture au groupe de fichiers multimédias spécifié.
createFavoriteRequest()
Demande à l'utilisateur de marquer les fichiers multimédias spécifiés comme certains de leurs contenus multimédias favoris sur l'appareil. Toute application disposant d'un accès en lecture à ce fichier peut voir que l'utilisateur l'a marqué comme "favori".
createTrashRequest()

Demande à l'utilisateur de placer les fichiers multimédias spécifiés dans la corbeille de l'appareil. Les éléments présents dans la corbeille sont définitivement supprimés à la fin de la période définie par le système.

createDeleteRequest()

Demande à l'utilisateur de supprimer définitivement et immédiatement les fichiers multimédias spécifiés, sans les placer au préalable dans la corbeille.

Après avoir appelé l'une de ces méthodes, le système crée un objet PendingIntent. Une fois que votre application a appelé cet intent, les utilisateurs voient une boîte de dialogue leur demandant leur autorisation pour mettre à jour ou supprimer les fichiers multimédias spécifiés.

Par exemple, voici comment structurer un appel de createWriteRequest() :

Kotlin

val urisToModify = /* A collection of content URIs to modify. */
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
        urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
    null, 0, 0, 0)

Java

List<Uri> urisToModify = /* A collection of content URIs to modify. */
PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver,
                  urisToModify);

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.getIntentSender(),
    EDIT_REQUEST_CODE, null, 0, 0, 0);

Évalue la réponse de l'utilisateur. Si l'utilisateur a donné son consentement, poursuivez l'opération multimédia. Sinon, expliquez à l'utilisateur pourquoi votre application a besoin de cette autorisation :

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int,
                 data: Intent?) {
    ...
    when (requestCode) {
        EDIT_REQUEST_CODE ->
            if (resultCode == Activity.RESULT_OK) {
                /* Edit request granted; proceed. */
            } else {
                /* Edit request not granted; explain to the user. */
            }
    }
}

Java

@Override
protected void onActivityResult(int requestCode, int resultCode,
                   @Nullable Intent data) {
    ...
    if (requestCode == EDIT_REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            /* Edit request granted; proceed. */
        } else {
            /* Edit request not granted; explain to the user. */
        }
    }
}

Vous pouvez utiliser ce même schéma général avec createFavoriteRequest(), createTrashRequest() et createDeleteRequest().

Autorisation de gestion multimédia

Les utilisateurs peuvent faire confiance à une application pour effectuer des tâches de gestion multimédia, comme modifier fréquemment des fichiers multimédias. Si votre application cible Android 11 ou une version ultérieure et n'est pas l'application de galerie par défaut de l'appareil, vous devez afficher une boîte de dialogue de confirmation chaque fois que votre application tente de modifier ou de supprimer un fichier.

Si votre application cible Android 12 (niveau d'API 31) ou une version ultérieure, vous pouvez demander aux utilisateurs d'accorder à votre application l'autorisation spéciale Gestion multimédia. Cette autorisation permet à votre application d'effectuer chacune des opérations suivantes sans avoir à inviter l'utilisateur à chaque opération sur le fichier :

Pour ce faire, procédez comme suit :

  1. Déclarez les autorisations MANAGE_MEDIA et READ_EXTERNAL_STORAGE dans le fichier manifeste de votre application.

    Pour appeler createWriteRequest() sans afficher de boîte de dialogue de confirmation, déclarez également l'autorisation ACCESS_MEDIA_LOCATION.

  2. Dans votre application, présentez une interface utilisateur à l'utilisateur pour lui expliquer pourquoi il peut accorder l'accès à la gestion multimédia à votre application.

  3. Appelez l'action d'intent ACTION_REQUEST_MANAGE_MEDIA. Les utilisateurs sont alors redirigés vers l'écran Applications de gestion multimédia dans les paramètres de la console. Les utilisateurs peuvent alors accorder un accès spécial à l'application.

Cas d'utilisation nécessitant une alternative au Media Store

Si votre application effectue principalement l'un des rôles suivants, envisagez de mettre en place une alternative aux API MediaStore.

Travailler avec d'autres types de fichiers

Si votre application fonctionne avec des documents et des fichiers qui ne contiennent pas exclusivement du contenu multimédia, tels que des fichiers EPUB ou PDF, utilisez l'action ACTION_OPEN_DOCUMENT, comme décrit dans le guide expliquant comment stocker des documents et d'autres fichiers, et y accéder.

Partage de fichiers dans les applications associées

Si vous fournissez une suite d'applications associées, telles qu'une application de messagerie et une application de profil, configurez le partage de fichiers à l'aide des URI content://. Nous vous recommandons également d'utiliser ce workflow comme bonnes pratiques de sécurité.

Ressources supplémentaires

Pour en savoir plus sur le stockage et l'accès aux fichiers multimédias, consultez les ressources suivantes.

Exemples

Vidéos