Viele Apps bieten Nutzern die Möglichkeit, Medien auf einem externen Speicher zu speichern und darauf zuzugreifen, um die Nutzerfreundlichkeit zu verbessern. Das Framework bietet einen optimierten Index für Mediensammlungen, den sogenannten Media Store, mit dem Nutzer diese Mediendateien abrufen und aktualisieren können. Diese Dateien verbleiben auf dem Gerät des Nutzers, auch nachdem deine App deinstalliert wurde.
Bildauswahl
Als Alternative zur Verwendung des Media Store bietet die Android-Bildauswahl Nutzern eine sichere, integrierte Möglichkeit, Mediendateien auszuwählen, ohne Ihrer App Zugriff auf ihre gesamte Mediathek gewähren zu müssen. Diese Funktion ist nur auf unterstützten Geräten verfügbar. Weitere Informationen finden Sie im Leitfaden zur Bildauswahl.
Medienspeicher
Wenn Sie mit der Abstraktion des MediaStore interagieren möchten, verwenden Sie ein ContentResolver-Objekt, das Sie aus dem Kontext Ihrer App abrufen:
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. }
Das System scannt automatisch ein externes Speicher-Volume und fügt Mediendateien den folgenden vordefinierten Sammlungen hinzu:
- Bilder,einschließlich Fotos und Screenshots, die in den Verzeichnissen
DCIM/undPictures/gespeichert sind. Das System fügt diese Dateien der TabelleMediaStore.Imageshinzu. - Videos, die in den Verzeichnissen
DCIM/,Movies/undPictures/gespeichert sind. Das System fügt diese Dateien der TabelleMediaStore.Videohinzu. - Audiodateien, die in den Verzeichnissen
Alarms/,Audiobooks/,Music/,Notifications/,Podcasts/undRingtones/gespeichert sind. Außerdem erkennt das System Audioplaylists, die sich in den VerzeichnissenMusic/oderMovies/befinden, sowie Sprachaufnahmen, die sich im VerzeichnisRecordings/befinden. Das System fügt diese Dateien der TabelleMediaStore.Audiohinzu. Das VerzeichnisRecordings/ist unter Android 11 (API-Level 30) und niedriger nicht verfügbar. - Heruntergeladene Dateien,die im Verzeichnis
Download/gespeichert sind. Auf Geräten mit Android 10 (API-Level 29) und höher werden diese Dateien in der TabelleMediaStore.Downloadsgespeichert. Diese Tabelle ist unter Android 9 (API-Level 28) und niedriger nicht verfügbar.
Der Medienspeicher enthält auch eine Sammlung namens MediaStore.Files. Der Inhalt hängt davon ab, ob Ihre App den begrenzten Speicher verwendet, der für Apps verfügbar ist, die auf Android 10 oder höher ausgerichtet sind.
- Wenn der begrenzte Speicher aktiviert ist, werden in der Sammlung nur die Fotos, Videos und Audiodateien angezeigt, die von Ihrer App erstellt wurden. Die meisten Entwickler müssen
MediaStore.Filesnicht verwenden, um Mediendateien aus anderen Apps aufzurufen. Wenn Sie jedoch eine bestimmte Anforderung dafür haben, können Sie die BerechtigungREAD_EXTERNAL_STORAGEdeklarieren. Wir empfehlen jedoch, dieMediaStoreAPIs zu verwenden, um Dateien zu öffnen, die nicht von Ihrer App erstellt wurden. - Wenn der begrenzte Speicher nicht verfügbar ist oder nicht verwendet wird, werden in der Sammlung alle Arten von Mediendateien angezeigt.
Erforderliche Berechtigungen anfordern
Bevor du Vorgänge für Mediendateien ausführst, musst du sicherstellen, dass deine App die Berechtigungen deklariert hat, die sie für den Zugriff auf diese Dateien benötigt. Achte jedoch darauf, keine Berechtigungen zu deklarieren, die deine App nicht benötigt oder verwendet.
Speicherberechtigungen
Ob Ihre App Berechtigungen für den Zugriff auf den Speicher benötigt, hängt davon ab, ob sie nur auf ihre eigenen Mediendateien oder auf Dateien zugreift, die von anderen Apps erstellt wurden.
Auf eigene Mediendateien zugreifen
Auf Geräten mit Android 10 oder höher benötigen Sie keine speicherbezogenen Berechtigungen, um auf Mediendateien zuzugreifen und diese zu ändern, die Ihrer App gehören, einschließlich Dateien in der Sammlung MediaStore.Downloads. Wenn Sie beispielsweise eine Kamera-App entwickeln, müssen Sie keine speicherbezogenen Berechtigungen anfordern, um auf die aufgenommenen Fotos zuzugreifen, da Ihre App die Bilder besitzt, die Sie in den MediaStore schreiben.
Auf Mediendateien anderer Apps zugreifen
Wenn du auf Mediendateien zugreifen möchtest, die von anderen Apps erstellt wurden, musst du die entsprechenden speicherbezogenen Berechtigungen deklarieren. Außerdem müssen sich die Dateien in einer der folgenden Mediensammlungen befinden:
Solange eine Datei über die Abfragen MediaStore.Images, MediaStore.Video oder MediaStore.Audio sichtbar ist, ist sie auch über die Abfrage MediaStore.Files sichtbar.
Das folgende Code-Snippet zeigt, wie die entsprechenden Speicherberechtigungen deklariert werden:
<!-- 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" />
Zusätzliche Berechtigungen für Apps, die auf älteren Geräten ausgeführt werden
Wenn Ihre App auf einem Gerät mit Android 9 oder niedriger verwendet wird oder wenn Sie die Verwendung des begrenzten Speichers für Ihre App vorübergehend deaktiviert haben, müssen Sie die Berechtigung READ_EXTERNAL_STORAGE anfordern, um auf Mediendateien zuzugreifen. Wenn Sie Mediendateien ändern möchten, müssen Sie auch die Berechtigung WRITE_EXTERNAL_STORAGE anfordern.
Storage Access Framework für den Zugriff auf Downloads anderer Apps erforderlich
Wenn Ihre App auf eine Datei in der Sammlung MediaStore.Downloads zugreifen möchte, die nicht von Ihrer App erstellt wurde, müssen Sie das Storage Access Framework verwenden. Weitere Informationen zur Verwendung dieses Frameworks finden Sie unter Auf Dokumente und andere Dateien im freigegebenen Speicher zugreifen.
Berechtigung zur Standortermittlung für Medien
Wenn deine App auf Android 10 (API-Level 29) oder höher ausgerichtet ist und nicht redigierte EXIF-Metadaten aus Fotos abrufen muss, musst du die Berechtigung ACCESS_MEDIA_LOCATION im Manifest deiner App deklarieren und diese Berechtigung dann zur Laufzeit anfordern.
Nach Updates für den Medienspeicher suchen
Wenn du zuverlässiger auf Mediendateien zugreifen möchtest, insbesondere wenn deine App URIs oder Daten aus dem MediaStore im Cache speichert, solltest du prüfen, ob sich die MediaStore-Version seit der letzten Synchronisierung deiner Mediendaten geändert hat. Rufen Sie getVersion() auf, um diese Prüfung auf Updates durchzuführen.
Die zurückgegebene Version ist ein eindeutiger String, der sich ändert, wenn sich der Medienspeicher wesentlich ändert. Wenn sich die zurückgegebene Version von der zuletzt synchronisierten Version unterscheidet, scanne und synchronisiere den Medien-Cache deiner App noch einmal.
Führen Sie diese Prüfung beim Start des App-Prozesses durch. Sie müssen die Version nicht jedes Mal prüfen, wenn Sie den MediaStore abfragen.
Gehen Sie nicht von Implementierungsdetails in Bezug auf die Versionsnummer aus.
Mediensammlung abfragen
Wenn Sie nach Medien suchen möchten, die bestimmte Bedingungen erfüllen, z. B. eine Dauer von 5 Minuten oder länger, verwenden Sie eine SQL-ähnliche Auswahlanweisung, die dem folgenden Code-Snippet ähnelt:
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)); } }
Beachten Sie bei einer solchen Abfrage in Ihrer App Folgendes:
- Rufe die Methode
query()in einem Arbeitsthread auf. - Speichern Sie die Spaltenindexe im Cache, damit Sie nicht jedes Mal
getColumnIndexOrThrow()aufrufen müssen, wenn Sie eine Zeile aus dem Abfrageergebnis verarbeiten. - Hängen Sie die ID an den Inhalts-URI an, wie in diesem Beispiel gezeigt.
- Für Geräte mit Android 10 und höher sind Spaltennamen
erforderlich, die in
der
MediaStoreAPI definiert sind. Wenn eine abhängige Bibliothek in Ihrer App einen Spaltennamen erwartet, der in der API nicht definiert ist, z. B."MimeType", verwenden SieCursorWrapper, um den Spaltennamen im Prozess Ihrer App dynamisch zu übersetzen.
Miniaturansichten von Dateien laden
Wenn in Ihrer App mehrere Mediendateien angezeigt werden und der Nutzer eine dieser Dateien auswählen soll, ist es effizienter, Vorabversionen oder Miniaturansichten der Dateien anstelle der Dateien selbst zu laden.
Wenn Sie die Miniaturansicht für eine bestimmte Mediendatei laden möchten, verwenden Sie loadThumbnail() und übergeben Sie die Größe der Miniaturansicht, die Sie laden möchten, wie im folgenden Code-Snippet gezeigt:
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);
Mediendatei öffnen
Die Logik, die Sie zum Öffnen einer Mediendatei verwenden, hängt davon ab, ob der Medieninhalt am besten als Dateideskriptor, Dateistream oder direkter Dateipfad dargestellt wird.
Dateideskriptor
Wenn Sie eine Mediendatei mit einem Dateideskriptor öffnen möchten, verwenden Sie eine Logik, die der im folgenden Code-Snippet ähnelt:
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(); }
Dateistreaming
Wenn Sie eine Mediendatei mit einem Dateistream öffnen möchten, verwenden Sie eine Logik, die der im folgenden Code-Snippet ähnelt:
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". }
Direkte Dateipfade
Damit Ihre App besser mit Medienbibliotheken von Drittanbietern funktioniert, können Sie unter Android 11 (API-Level 30) und höher andere APIs als die MediaStore API verwenden, um auf Mediendateien im freigegebenen Speicher zuzugreifen. Stattdessen können Sie mit einer der folgenden APIs direkt auf Mediendateien zugreifen:
- Die
FileAPI - Native Bibliotheken wie
fopen()
Wenn Sie keine speicherbezogenen Berechtigungen haben, können Sie mit der File API auf Dateien in Ihrem app-spezifischen Verzeichnis sowie auf Mediendateien, die Ihrer App zugeordnet sind, zugreifen.
Wenn Ihre App versucht, über die File API auf eine Datei zuzugreifen, und nicht die erforderlichen Berechtigungen hat, tritt ein FileNotFoundException auf.
Wenn Sie auf einem Gerät mit Android 10 (API-Level 29) auf andere Dateien im freigegebenen Speicher zugreifen möchten, empfehlen wir, den begrenzten Speicher vorübergehend zu deaktivieren. Setzen Sie dazu requestLegacyExternalStorage in der Manifestdatei Ihrer App auf true. Wenn Sie unter Android 10 mit nativen Dateimethoden auf Mediendateien zugreifen möchten, müssen Sie auch die Berechtigung READ_EXTERNAL_STORAGE anfordern.
Überlegungen beim Zugriff auf Medieninhalte
Beachten Sie beim Zugriff auf Medieninhalte die in den folgenden Abschnitten beschriebenen Überlegungen.
Daten im Cache
Wenn deine App URIs oder Daten aus dem MediaStore im Cache speichert, solltest du regelmäßig nach Updates für den MediaStore suchen. Durch diese Prüfung bleiben die im Cache gespeicherten Daten auf der App-Seite mit den Daten des Anbieters auf der Systemseite synchron.
Leistung
Wenn Sie sequenzielle Lesevorgänge von Mediendateien über direkte Dateipfade ausführen, ist die Leistung mit der der MediaStore API vergleichbar.
Wenn Sie jedoch zufällige Lese- und Schreibvorgänge für Mediendateien über direkte Dateipfade ausführen, kann der Vorgang bis zu doppelt so langsam sein. In diesen Situationen empfehlen wir, stattdessen die MediaStore API zu verwenden.
Spalte DATA
Wenn Sie auf eine vorhandene Mediendatei zugreifen, können Sie den Wert der Spalte DATA in Ihrer Logik verwenden. Das liegt daran, dass dieser Wert einen gültigen Dateipfad hat. Gehe jedoch nicht davon aus, dass die Datei immer verfügbar ist. Seien Sie darauf vorbereitet, alle dateibasierten E/A-Fehler zu beheben, die auftreten.
Wenn Sie eine Mediendatei erstellen oder aktualisieren möchten, verwenden Sie nicht den Wert der Spalte DATA. Verwenden Sie stattdessen die Werte der Spalten DISPLAY_NAME und RELATIVE_PATH.
Speicher-Volumes
Apps, die auf Android 10 oder höher ausgerichtet sind, können auf den eindeutigen Namen zugreifen, den das System jedem externen Speicher-Volume zuweist. Mit diesem Benennungssystem können Sie Inhalte effizient organisieren und indexieren und steuern, wo neue Mediendateien gespeichert werden.
Die folgenden Volumes sind besonders nützlich:
- Das Volume
VOLUME_EXTERNALbietet eine Ansicht aller freigegebenen Speichervolumes auf dem Gerät. Sie können den Inhalt dieses synthetischen Volumes lesen, aber nicht ändern. - Das Volume
VOLUME_EXTERNAL_PRIMARYstellt das primäre freigegebene Speicher-Volume auf dem Gerät dar. Sie können den Inhalt dieses Volumes lesen und ändern.
Sie können andere Volumes durch Aufrufen von MediaStore.getExternalVolumeNames() ermitteln:
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();
Ort, an dem die Medien aufgenommen wurden
Einige Fotos und Videos enthalten Standortinformationen in ihren Metadaten, die den Ort zeigen, an dem ein Foto aufgenommen oder ein Video aufgezeichnet wurde.
Wie Sie in Ihrer App auf diese Standortinformationen zugreifen, hängt davon ab, ob Sie auf Standortinformationen für ein Foto oder ein Video zugreifen müssen.
Fotos
Wenn deine App begrenzten Speicher verwendet, blendet das System Standortinformationen standardmäßig aus. Führen Sie die folgenden Schritte aus, um auf diese Informationen zuzugreifen:
- Fordern Sie im Manifest Ihrer App die Berechtigung
ACCESS_MEDIA_LOCATIONan. Rufen Sie aus Ihrem
MediaStore-Objekt die genauen Byte des Fotos ab, indem SiesetRequireOriginal()aufrufen und den URI des Fotos übergeben, wie im folgenden Code-Snippet gezeigt: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]; }
Videos
Verwenden Sie die Klasse MediaMetadataRetriever, um auf Standortinformationen in den Metadaten eines Videos zuzugreifen, wie im folgenden Code-Snippet gezeigt. Ihre App muss keine zusätzlichen Berechtigungen anfordern, um diese Klasse zu verwenden.
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); }
Teilen
In einigen Apps können Nutzer Mediendateien miteinander teilen. In Social-Media-Apps können Nutzer beispielsweise Fotos und Videos mit Freunden teilen.
Verwenden Sie zum Freigeben von Mediendateien einen content://-URI, wie im Leitfaden zum Erstellen eines Contentanbieters empfohlen.
App-Attribution von Mediendateien
Wenn der begrenzte Speicher für eine App aktiviert ist, die auf Android 10 oder höher ausgerichtet ist, ordnet das System jeder Mediendatei eine App zu. Dadurch wird festgelegt, auf welche Dateien Ihre App zugreifen kann, wenn sie keine Speicherberechtigungen angefordert hat. Jede Datei kann nur einer App zugeordnet werden. Wenn Ihre App also eine Mediendatei erstellt, die in der Mediensammlung für Fotos, Videos oder Audiodateien gespeichert wird, hat Ihre App Zugriff auf die Datei.
Wenn der Nutzer Ihre App deinstalliert und neu installiert, müssen Sie jedoch READ_EXTERNAL_STORAGE anfordern, um auf die Dateien zuzugreifen, die Ihre App ursprünglich erstellt hat. Diese Berechtigungsanfrage ist erforderlich, weil das System die Datei der zuvor installierten Version der App zuordnet und nicht der neu installierten.
Wenn Nutzer auf Geräten mit Android 16 oder höher von einer App, die auf SDK 36 oder höher ausgerichtet ist, aufgefordert werden, Berechtigungen für Fotos und Videos zu erteilen, und den Zugriff auf ausgewählte Medien einschränken, werden alle Fotos, die der App gehören, in der Bildauswahl vorab ausgewählt. Nutzer können die Auswahl dieser vorausgewählten Elemente aufheben, wodurch der Zugriff der App auf diese Fotos und Videos widerrufen wird.
Hinzufügen
Wenn Sie einer vorhandenen Sammlung eine Mediendatei hinzufügen möchten, verwenden Sie Code wie den folgenden. Dieses Code-Snippet greift auf Geräten mit Android 10 oder höher auf das Volume VOLUME_EXTERNAL_PRIMARY zu. Das liegt daran, dass Sie auf diesen Geräten den Inhalt eines Volumes nur ändern können, wenn es sich um das primäre Volume handelt, wie im Abschnitt Speichervolumes beschrieben.
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);
Status „Ausstehend“ für Mediendateien ein-/ausblenden
Wenn Ihre App potenziell zeitaufwendige Vorgänge ausführt, z. B. das Schreiben in Mediendateien, ist es nützlich, während der Verarbeitung exklusiven Zugriff auf die Datei zu haben. Auf Geräten mit Android 10 oder höher kann deine App diesen exklusiven Zugriff erhalten, indem du den Wert des Flags IS_PENDING auf „1“ setzt. Nur Ihre App kann die Datei aufrufen, bis Ihre App den Wert von IS_PENDING wieder auf 0 ändert.
Das folgende Code-Snippet baut auf dem vorherigen Code-Snippet auf. In diesem Snippet wird gezeigt, wie Sie das Flag IS_PENDING verwenden, wenn Sie einen langen Song im Verzeichnis speichern, das der Sammlung MediaStore.Audio entspricht:
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);
Hinweis zum Speicherort der Datei geben
Wenn Ihre App Medien auf einem Gerät mit Android 10 speichert, werden die Medien standardmäßig nach Typ organisiert. Standardmäßig werden neue Bilddateien beispielsweise im Verzeichnis Environment.DIRECTORY_PICTURES abgelegt, das der Sammlung MediaStore.Images entspricht.
Wenn Ihre App einen bestimmten Speicherort für Dateien kennt, z. B. ein Fotoalbum namens Pictures/MyVacationPictures, können Sie MediaColumns.RELATIVE_PATH festlegen, um dem System einen Hinweis darauf zu geben, wo die neu geschriebenen Dateien gespeichert werden sollen.
Artikel aktualisieren
Wenn du eine Mediendatei aktualisieren möchtest, die deiner App gehört, verwende Code wie den folgenden:
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);
Wenn der begrenzte Speicher nicht verfügbar oder nicht aktiviert ist, funktioniert der im vorherigen Code-Snippet gezeigte Prozess auch für Dateien, die nicht zu deiner App gehören.
Aktualisierung im nativen Code
Wenn Sie Mediendateien mit nativen Bibliotheken schreiben müssen, übergeben Sie den zugehörigen Dateideskriptor aus Ihrem Java- oder Kotlin-basierten Code an Ihren nativen Code.
Das folgende Code-Snippet zeigt, wie der Dateideskriptor eines Medienobjekts an den nativen Code Ihrer App übergeben wird:
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. }
Mediendateien anderer Apps aktualisieren
Wenn Ihre App begrenzten Speicher verwendet, kann sie normalerweise keine Mediendatei aktualisieren, die von einer anderen App zum MediaStore hinzugefügt wurde.
Sie können jedoch die Nutzereinwilligung zum Ändern der Datei einholen, indem Sie die von der Plattform ausgelöste RecoverableSecurityException abfangen. Anschließend können Sie den Nutzer bitten, Ihrer App Schreibzugriff auf dieses bestimmte Element zu gewähren, wie im folgenden Code-Snippet gezeigt:
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); } }
Führe diesen Vorgang jedes Mal aus, wenn deine App eine Mediendatei ändern muss, die sie nicht erstellt hat.
Wenn Ihre App unter Android 11 oder höher ausgeführt wird, können Sie Nutzer auch dazu auffordern, Ihrer App Schreibzugriff auf eine Gruppe von Mediendateien zu gewähren. Verwenden Sie die Methode createWriteRequest(), wie im Abschnitt Gruppen von Mediendateien verwalten beschrieben.
Wenn Ihre App einen anderen Anwendungsfall hat, der nicht durch den begrenzten Speicher abgedeckt ist, reichen Sie eine Funktionsanfrage ein und deaktivieren Sie vorübergehend den begrenzten Speicher.
Element entfernen
Wenn Sie ein Element, das Ihre App nicht mehr benötigt, aus dem MediaStore entfernen möchten, verwenden Sie eine Logik, die der im folgenden Code-Snippet ähnelt:
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);
Wenn der begrenzte Speicher nicht verfügbar oder nicht aktiviert ist, können Sie das vorherige Code-Snippet verwenden, um Dateien zu entfernen, die anderen Apps gehören. Wenn der begrenzte Speicher aktiviert ist, müssen Sie jedoch für jede Datei, die Ihre App entfernen möchte, eine RecoverableSecurityException abfangen, wie im Abschnitt Mediendateien aktualisieren beschrieben.
Wenn deine App unter Android 11 oder höher ausgeführt wird, kannst du Nutzern die Möglichkeit geben, eine Gruppe von Mediendateien zum Entfernen auszuwählen. Verwenden Sie die Methode createTrashRequest() oder die Methode createDeleteRequest(), wie im Abschnitt Gruppen von Mediendateien verwalten beschrieben.
Wenn Ihre App einen anderen Anwendungsfall hat, der nicht durch den begrenzten Speicher abgedeckt ist, reichen Sie eine Funktionsanfrage ein und deaktivieren Sie vorübergehend den begrenzten Speicher.
Updates an Mediendateien erkennen
Ihre App muss möglicherweise Speicher-Volumes mit Mediendateien identifizieren, die von Apps im Vergleich zu einem früheren Zeitpunkt hinzugefügt oder geändert wurden. Wenn Sie diese Änderungen möglichst zuverlässig erkennen möchten, übergeben Sie das gewünschte Speicher-Volume an getGeneration().
Solange sich die Version des MediaStore nicht ändert, steigt der Rückgabewert dieser Methode im Laufe der Zeit monoton an.
Insbesondere ist getGeneration() robuster als die Datumsangaben in Medienspalten wie DATE_ADDED und DATE_MODIFIED.
Das liegt daran, dass sich die Werte dieser Medienspalten ändern können, wenn eine App setLastModified() aufruft oder der Nutzer die Systemuhr ändert.
Gruppen von Mediendateien verwalten
Unter Android 11 und höher können Sie den Nutzer bitten, eine Gruppe von Mediendateien auszuwählen, und diese Mediendateien dann in einem einzigen Vorgang aktualisieren. Diese Methoden bieten eine bessere Konsistenz auf verschiedenen Geräten und erleichtern Nutzern die Verwaltung ihrer Mediensammlungen.
Die Methoden, die diese „Batch-Update“-Funktion bereitstellen, umfassen Folgendes:
createWriteRequest()- Fordere den Nutzer auf, deiner App Schreibzugriff auf die angegebene Gruppe von Mediendateien zu gewähren.
createFavoriteRequest()- Fordert den Nutzer auf, die angegebenen Mediendateien als „Favoriten“ auf dem Gerät zu markieren. Jede App mit Lesezugriff auf diese Datei kann sehen, dass der Nutzer die Datei als „Favorit“ markiert hat.
createTrashRequest()Den Nutzer auffordern, die angegebenen Mediendateien in den Papierkorb des Geräts zu verschieben. Elemente im Papierkorb werden nach einem systemdefinierten Zeitraum endgültig gelöscht.
createDeleteRequest()Fordern Sie den Nutzer auf, die angegebenen Mediendateien sofort endgültig zu löschen, ohne sie vorher in den Papierkorb zu verschieben.
Nach dem Aufruf einer dieser Methoden erstellt das System ein PendingIntent-Objekt. Nachdem deine App diesen Intent aufgerufen hat, wird Nutzern ein Dialogfeld angezeigt, in dem sie um ihre Einwilligung gebeten werden, dass deine App die angegebenen Mediendateien aktualisieren oder löschen darf.
So strukturieren Sie beispielsweise einen Aufruf von 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);
Evaluate the user's response. Wenn der Nutzer seine Einwilligung erteilt hat, fahre mit dem Medienvorgang fort. Andernfalls erkläre dem Nutzer, warum deine App die Berechtigung benötigt:
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. */ } } }
Sie können dasselbe allgemeine Muster mit createFavoriteRequest(), createTrashRequest() und createDeleteRequest() verwenden.
Berechtigung zur Medienverwaltung
Nutzer vertrauen einer bestimmten App möglicherweise die Medienverwaltung an, z. B. die häufige Bearbeitung von Mediendateien. Wenn Ihre App auf Android 11 oder höher ausgerichtet ist und nicht die Standard-Galerie-App des Geräts ist, müssen Sie dem Nutzer jedes Mal ein Bestätigungsdialogfeld anzeigen, wenn Ihre App versucht, eine Datei zu ändern oder zu löschen.
Wenn Ihre App auf Android 12 (API-Level 31) oder höher ausgerichtet ist, können Sie Nutzer bitten, Ihrer App Zugriff auf die Sonderberechtigung Medien verwalten zu gewähren. Mit dieser Berechtigung kann Ihre App die folgenden Aktionen ausführen, ohne den Nutzer bei jeder Dateioperation um Erlaubnis bitten zu müssen:
- Ändern Sie Dateien mit
createWriteRequest(). - Dateien mit
createTrashRequest()in den Papierkorb verschieben und daraus wiederherstellen - Löschen Sie Dateien mit
createDeleteRequest().
Führen Sie dazu folgende Schritte aus:
Deklarieren Sie die Berechtigungen
MANAGE_MEDIAundREAD_EXTERNAL_STORAGEin der Manifestdatei Ihrer App.Wenn Sie
createWriteRequest()aufrufen möchten, ohne ein Bestätigungsdialogfeld anzuzeigen, deklarieren Sie auch die BerechtigungACCESS_MEDIA_LOCATION.Zeigen Sie dem Nutzer in Ihrer App eine Benutzeroberfläche, auf der Sie erklären, warum er Ihrer App Zugriff auf die Medienverwaltung gewähren sollte.
Rufen Sie die Intent-Aktion
ACTION_REQUEST_MANAGE_MEDIAauf. Dadurch gelangen Nutzer in den Systemeinstellungen zum Bildschirm Apps zur Medienverwaltung. Hier können Nutzer den speziellen App-Zugriff gewähren.
Anwendungsfälle, die eine Alternative zum MediaStore erfordern
Wenn deine App hauptsächlich eine der folgenden Rollen erfüllt, solltest du eine Alternative zu den MediaStore APIs in Betracht ziehen.
Mit anderen Dateitypen arbeiten
Wenn deine App mit Dokumenten und Dateien arbeitet, die nicht ausschließlich Medieninhalte enthalten, z. B. Dateien mit der Dateiendung EPUB oder PDF, verwende die Intent-Aktion ACTION_OPEN_DOCUMENT, wie im Leitfaden zum Speichern von und Zugreifen auf Dokumente und andere Dateien beschrieben.
Dateifreigabe in Companion-Apps
Wenn Sie eine Reihe von Companion-Apps bereitstellen, z. B. eine Messaging-App und eine Profil-App, richten Sie die Dateifreigabe mit content://-URIs ein. Wir empfehlen diesen Workflow auch als Best Practice für die Sicherheit.
Zusätzliche Ressourcen
Weitere Informationen über das Speichern von Medien und den Zugriff darauf finden Sie in den folgenden Ressourcen.
Beispiele
- MediaStore, verfügbar auf GitHub