Viele Apps ermöglichen es Nutzern, Medien auf einem externen Speichermedium zu nutzen und dort zu speichern, um die Nutzerfreundlichkeit zu verbessern. Das Framework bietet einen optimierten Index für Mediensammlungen, den Media Store. Damit können Nutzer diese Mediendateien einfacher abrufen und aktualisieren. Auch nach der Deinstallation Ihrer App verbleiben diese Dateien auf dem Gerät des Nutzers.
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 zu gewähren. Diese Funktion ist nur auf unterstützten Geräten verfügbar. Weitere Informationen finden Sie im Leitfaden zur Bildauswahl.
Medienspeicher
Wenn Sie mit der Media Store-Abstraktion 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 Speichermedium und fügt Mediendateien den folgenden genau definierten Sammlungen hinzu:
- Bilder,einschließlich Fotos und Screenshots, die in den Verzeichnissen
DCIM/
undPictures/
gespeichert sind. Das System fügt diese Dateien der TabelleMediaStore.Images
hinzu. - Videos, die in den Verzeichnissen
DCIM/
,Movies/
undPictures/
gespeichert sind. Das System fügt diese Dateien der TabelleMediaStore.Video
hinzu. - 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.Audio
hinzu. 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.Downloads
gespeichert. 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 Scoped Storage verwendet, das für Apps verfügbar ist, die auf Android 10 oder höher ausgerichtet sind.
- Wenn der bereichsbezogene 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.Files
nicht verwenden, um Mediendateien aus anderen Apps aufzurufen. Wenn Sie jedoch eine bestimmte Anforderung dafür haben, können Sie die BerechtigungREAD_EXTERNAL_STORAGE
deklarieren. Wir empfehlen jedoch, dieMediaStore
-APIs zum Öffnen von Dateien zu verwenden, die nicht von Ihrer App erstellt wurden. - Wenn der bereichsbezogene Speicher nicht verfügbar ist oder nicht verwendet wird, werden in der Sammlung alle Arten von Mediendateien angezeigt.
Erforderliche Berechtigungen anfordern
Bevor Sie Vorgänge für Mediendateien ausführen, müssen Sie dafür sorgen, dass Ihre App die Berechtigungen deklariert hat, die für den Zugriff auf diese Dateien erforderlich sind. Achten Sie jedoch darauf, keine Berechtigungen zu deklarieren, die Ihre 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 sie 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 Media Store schreiben.
Auf Mediendateien anderer Apps zugreifen
Wenn Sie auf Mediendateien zugreifen möchten, die von anderen Apps erstellt wurden, müssen Sie die entsprechenden speicherbezogenen Berechtigungen deklarieren. Die Dateien müssen sich in einer der folgenden Mediensammlungen befinden:
Solange eine Datei über die Abfragen MediaStore.Images
, MediaStore.Video
oder MediaStore.Audio
aufgerufen werden kann, ist sie auch über die Abfrage MediaStore.Files
aufrufbar.
Das folgende Code-Snippet zeigt, wie Sie die entsprechenden Speicherberechtigungen deklarieren:
<!-- 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 Ihre App vorübergehend von der Speicherung mit beschränktem Zugriff ausgeschlossen wurde, 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 aus dem gemeinsamen Speicher zugreifen.
Berechtigung zur Standortermittlung für Medien
Wenn Ihre App auf Android 10 (API‑Level 29) oder höher ausgerichtet ist und ungeschwärzte EXIF-Metadaten aus Fotos abrufen muss, müssen Sie die Berechtigung ACCESS_MEDIA_LOCATION
im Manifest Ihrer App deklarieren und diese Berechtigung dann zur Laufzeit anfordern.
Nach Updates für den Media Store suchen
Wenn Sie zuverlässiger auf Mediadateien zugreifen möchten, insbesondere wenn Ihre App URIs oder Daten aus dem Media Store im Cache speichert, prüfen Sie, ob sich die Media Store-Version seit der letzten Synchronisierung Ihrer Mediendaten geändert hat. Rufen Sie getVersion()
auf, um nach Updates zu suchen.
Die zurückgegebene Version ist ein eindeutiger String, der sich ändert, wenn sich der Medienspeicher erheblich ändert. Wenn sich die zurückgegebene Version von der zuletzt synchronisierten Version unterscheidet, scannen Sie den Media-Cache Ihrer App noch einmal und synchronisieren Sie ihn neu.
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 Medienspeicher abfragen.
Gehen Sie nicht von bestimmten Implementierungsdetails in Bezug auf die Versionsnummer aus.
Mediensammlung abfragen
Wenn Sie Medien finden möchten, die bestimmte Bedingungen erfüllen, z. B. eine Dauer von mindestens 5 Minuten, verwenden Sie eine SQL-ähnliche Auswahlanweisung wie im folgenden Code-Snippet:
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:
- Rufen Sie die Methode
query()
in einem Worker-Thread auf. - Speichern Sie die Spaltenindexe im Cache, damit Sie
getColumnIndexOrThrow()
nicht jedes Mal 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
MediaStore
API 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 aufgefordert wird, eine dieser Dateien auszuwählen, ist es effizienter, Vorschauversionen oder Thumbnails der Dateien anstelle der Dateien selbst zu laden.
Verwenden Sie loadThumbnail()
, um die Miniaturansicht für eine bestimmte Mediendatei zu laden, 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 genaue Logik, die Sie zum Öffnen einer Mediendatei verwenden, hängt davon ab, ob die Medieninhalte am besten als Dateideskriptor, Dateistream oder direkter Dateipfad dargestellt werden.
Dateideskriptor
Wenn Sie eine Media-Datei mit einem Dateideskriptor öffnen möchten, verwenden Sie eine ähnliche Logik wie im folgenden Code-Snippet:
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(); }
Dateistream
Wenn Sie eine Media-Datei mit einem Dateistream öffnen möchten, verwenden Sie eine ähnliche Logik wie im folgenden Code-Snippet:
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 reibungsloser mit Drittanbieter-Medienbibliotheken zusammenarbeitet, können Sie in Android 11 (API-Level 30) und höher andere APIs als die MediaStore
API verwenden, um auf Mediendateien aus dem freigegebenen Speicher zuzugreifen. Stattdessen können Sie mit einer der folgenden APIs direkt auf Mediendateien zugreifen:
- Die
File
API - Native Bibliotheken, z. B.
fopen()
Wenn Sie keine speicherbezogenen Berechtigungen haben, können Sie mit der File
API auf Dateien in Ihrem app-spezifischen Verzeichnis sowie auf Mediendateien zugreifen, die Ihrer App zugeordnet sind.
Wenn Ihre App versucht, mit der 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, die Funktion „Scoped Storage“ vorübergehend zu deaktivieren, indem Sie requestLegacyExternalStorage
in der Manifestdatei Ihrer App auf true
setzen. 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 Hinweise, die in den folgenden Abschnitten beschrieben werden.
Daten im Cache
Wenn Ihre App URIs oder Daten aus dem Medienspeicher im Cache speichert, sollten Sie regelmäßig nach Updates des Medienspeichers suchen. Mit dieser Prüfung können Sie dafür sorgen, dass die in Ihrer App zwischengespeicherten Daten mit den Anbieterdaten auf Systemseite synchronisiert bleiben.
Leistung
Wenn Sie Media-Dateien sequenziell über direkte Dateipfade lesen, ist die Leistung mit der der MediaStore
API vergleichbar.
Wenn Sie jedoch zufällige Lese- und Schreibvorgänge von Media-Dateien über direkte Dateipfade ausführen, kann der Vorgang bis zu doppelt so lange dauern. In diesen Fällen empfehlen wir, stattdessen die MediaStore
API zu verwenden.
Spalte „DATA“
Wenn Sie auf eine vorhandene Media-Datei zugreifen, können Sie den Wert der Spalte DATA
in Ihrer Logik verwenden. Das liegt daran, dass dieser Wert einen gültigen Dateipfad hat. Sie sollten jedoch nicht davon ausgehen, dass die Datei immer verfügbar ist. Seien Sie darauf vorbereitet, alle dateibasierten E/A-Fehler zu beheben, die auftreten.
Wenn Sie eine Media-Datei 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 Speichermedium zuweist. Dieses Benennungssystem hilft Ihnen, Inhalte effizient zu organisieren und zu indexieren. Außerdem können Sie damit festlegen, wo neue Mediendateien gespeichert werden.
Die folgenden Mengen sind besonders wichtig:
- Das
VOLUME_EXTERNAL
-Volume bietet eine Übersicht aller freigegebenen Speichervolumes auf dem Gerät. Sie können die Inhalte dieses synthetischen Volumes lesen, aber nicht ändern. - Das Volume
VOLUME_EXTERNAL_PRIMARY
ist das primäre freigegebene Speichervolume auf dem Gerät. Sie können die Inhalte 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 in ihren Metadaten Informationen zum Aufnahmeort.
Wie Sie in Ihrer App auf diese Standortinformationen zugreifen, hängt davon ab, ob Sie auf Standortinformationen für ein Foto oder für ein Video zugreifen müssen.
Fotos
Wenn Ihre App Scoped Storage verwendet, werden Standortinformationen standardmäßig vom System ausgeblendet. So greifen Sie auf diese Informationen zu:
- Fordern Sie die Berechtigung
ACCESS_MEDIA_LOCATION
im Manifest Ihrer App an. Rufen Sie aus Ihrem
MediaStore
-Objekt die genauen Bytes 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
Um auf Standortinformationen in den Metadaten eines Videos zuzugreifen, verwenden Sie die Klasse MediaMetadataRetriever
, 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 Media-Dateien 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 Scoped Storage für eine App aktiviert ist, die auf Android 10 oder höher ausgerichtet ist, weist 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 „Fotos“, „Videos“ oder „Audiodateien“ gespeichert wird, hat Ihre App Zugriff auf die Datei.
Wenn der Nutzer Ihre App jedoch deinstalliert und neu installiert, müssen Sie READ_EXTERNAL_STORAGE
anfordern, um auf die Dateien zuzugreifen, die Ihre App ursprünglich erstellt hat. Diese Berechtigungsanfrage ist erforderlich, da das System die Datei der zuvor installierten Version der App zuordnet und nicht der neu installierten.
Wenn eine App, die auf SDK 36 oder höher ausgerichtet ist, auf Geräten mit Android 16 oder höher nach Foto- und Videoberechtigungen fragt, werden im Foto-Picker alle Fotos, die der App gehören, vorausgewählt, wenn Nutzer den Zugriff auf ausgewählte Medien beschränken. Nutzer können die Auswahl dieser vorab ausgewählten Elemente aufheben. Dadurch wird der Zugriff der App auf diese Fotos und Videos widerrufen.
Artikel hinzufügen
Wenn Sie einer vorhandenen Sammlung ein Media-Element hinzufügen möchten, verwenden Sie Code, der dem folgenden ähnelt. Mit diesem Code-Snippet wird auf Geräten mit Android 10 oder höher auf das VOLUME_EXTERNAL_PRIMARY
-Volume zugegriffen. 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);
Ausstehenden Status für Mediendateien ein- oder ausschalten
Wenn Ihre App potenziell zeitaufwendige Vorgänge ausführt, z. B. das Schreiben in Mediendateien, ist es sinnvoll, exklusiven Zugriff auf die Datei zu haben, während sie verarbeitet wird. Auf Geräten mit Android 10 oder höher kann Ihre App diesen exklusiven Zugriff erhalten, indem Sie den Wert des Flags IS_PENDING
auf 1 setzen. Nur Ihre App kann die Datei aufrufen, bis sie 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 Dateispeicherort 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
platziert, das der Sammlung MediaStore.Images
entspricht.
Wenn Ihre App einen bestimmten Speicherort kennt, an dem Dateien gespeichert werden können, z. B. ein Fotoalbum mit dem Namen 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.
Element aktualisieren
Wenn Sie eine Mediendatei aktualisieren möchten, die Ihrer App gehört, verwenden Sie Code, der dem folgenden ähnelt:
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 Bereichsspeicher nicht verfügbar oder nicht aktiviert ist, funktioniert der im vorherigen Code-Snippet gezeigte Prozess auch für Dateien, die nicht Ihrer App gehören.
Im nativen Code aktualisieren
Wenn Sie Mediendateien mit nativen Bibliotheken schreiben müssen, übergeben Sie den zugehörigen Dateideskriptor der Datei aus Ihrem Java- oder Kotlin-basierten Code an Ihren nativen Code.
Das folgende Code-Snippet zeigt, wie Sie den Dateideskriptor eines Media-Objekts an den nativen Code Ihrer App übergeben:
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 Scoped Storage verwendet, kann sie normalerweise keine Mediendatei aktualisieren, die von einer anderen App im Media Store gespeichert wurde.
Sie können die Nutzereinwilligung zum Ändern der Datei einholen, indem Sie die RecoverableSecurityException
abfangen, die von der Plattform ausgegeben wird. 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ühren Sie diesen Vorgang jedes Mal aus, wenn Ihre App eine Mediendatei ändern muss, die nicht von ihr erstellt wurde.
Wenn Ihre App unter Android 11 oder höher ausgeführt wird, können Sie Nutzern alternativ die Möglichkeit geben, Ihrer App Schreibzugriff auf eine Gruppe von Mediendateien zu gewähren. Verwenden Sie die Methode createWriteRequest()
, wie im Abschnitt zum Verwalten von Gruppen von Media-Dateien beschrieben.
Wenn Ihre App einen anderen Anwendungsfall hat, der nicht durch den eingeschränkten Speicher abgedeckt wird, können Sie eine Funktionsanfrage einreichen und den eingeschränkten Speicher vorübergehend deaktivieren.
Element entfernen
Wenn Sie ein Element aus dem Medienspeicher entfernen möchten, das Ihre App nicht mehr benötigt, verwenden Sie eine Logik, die dem 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 bereichsbezogene Speicher nicht verfügbar oder nicht aktiviert ist, können Sie mit dem vorherigen Code-Snippet Dateien entfernen, die anderen Apps gehören. Wenn der bereichsbezogene Speicher aktiviert ist, müssen Sie jedoch für jede Datei, die Ihre App entfernen möchte, ein RecoverableSecurityException
abfangen, wie im Abschnitt zum Aktualisieren von Media-Elementen beschrieben.
Wenn Ihre App unter Android 11 oder höher ausgeführt wird, können Sie Nutzern die Möglichkeit geben, eine Gruppe von Media-Dateien zum Entfernen auszuwählen. Verwenden Sie die Methode createTrashRequest()
oder die Methode createDeleteRequest()
, wie im Abschnitt zum Verwalten von Gruppen von Mediendateien beschrieben.
Wenn Ihre App einen anderen Anwendungsfall hat, der nicht durch den eingeschränkten Speicher abgedeckt wird, können Sie eine Funktionsanfrage einreichen und den eingeschränkten Speicher vorübergehend deaktivieren.
Änderungen an Mediendateien erkennen
Ihre App muss möglicherweise Speichermedien mit Mediendateien identifizieren, die von Apps im Vergleich zu einem früheren Zeitpunkt hinzugefügt oder geändert wurden. Um diese Änderungen am zuverlässigsten zu erkennen, übergeben Sie das gewünschte Speichervolume an getGeneration()
.
Solange sich die Version des Medienspeichers nicht ändert, steigt der Rückgabewert dieser Methode im Laufe der Zeit monoton an.
Insbesondere ist getGeneration()
robuster als die Datumsangaben in Media-Spalten wie DATE_ADDED
und DATE_MODIFIED
.
Das liegt daran, dass sich die Werte in diesen Media-Spalten ändern können, wenn eine App setLastModified()
aufruft oder wenn der Nutzer die Systemuhr ändert.
Gruppen von Media-Dateien verwalten
Unter Android 11 und höher können Sie den Nutzer bitten, eine Gruppe von Media-Dateien auszuwählen, und diese Media-Dateien 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 Funktion für Batch-Updates bieten, sind:
createWriteRequest()
- Fordern Sie den Nutzer auf, Ihrer App Schreibzugriff auf die angegebene Gruppe von Mediendateien zu gewähren.
createFavoriteRequest()
- Den Nutzer bitten, die angegebenen Mediendateien auf dem Gerät als „Favoriten“ zu markieren. Jede App, die Lesezugriff auf diese Datei hat, kann sehen, dass der Nutzer die Datei als „Favorit“ markiert hat.
createTrashRequest()
Den Nutzer bitten, die angegebenen Mediendateien in den Papierkorb des Geräts zu verschieben Elemente im Papierkorb werden nach einem vom System festgelegten Zeitraum endgültig gelöscht.
createDeleteRequest()
Fordere den Nutzer auf, die angegebenen Mediendateien sofort endgültig zu löschen, ohne sie vorher in den Papierkorb zu verschieben.
Nach dem Aufrufen einer dieser Methoden erstellt das System ein PendingIntent
-Objekt. Nachdem Ihre App diesen Intent aufgerufen hat, wird den Nutzern ein Dialogfeld angezeigt, in dem sie um ihre Einwilligung gebeten werden, die angegebenen Media-Dateien zu aktualisieren oder zu löschen.
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);
Bewerten Sie die Antwort des Nutzers. Wenn der Nutzer seine Einwilligung erteilt hat, fahren Sie mit dem Media-Vorgang fort. Andernfalls erklären Sie dem Nutzer, warum Ihre 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 möglicherweise einer bestimmten App, die Medienverwaltung durchzuführen, z. B. häufige Änderungen an Mediendateien vorzunehmen. Wenn Ihre App auf Android 11 oder höher ausgerichtet ist und nicht die Standardgalerie-App des Geräts ist, müssen Sie dem Nutzer jedes Mal, wenn Ihre App versucht, eine Datei zu ändern oder zu löschen, einen Bestätigungsdialog anzeigen.
Wenn Ihre App auf Android 12 (API‑Level 31) oder höher ausgerichtet ist, können Sie Nutzer bitten, Ihrer App die spezielle Berechtigung Medienverwaltung zu erteilen. Mit dieser Berechtigung kann Ihre App die folgenden Aktionen ausführen, ohne den Nutzer für jeden Dateivorgang zur Bestätigung aufzufordern:
- Dateien mit
createWriteRequest()
ändern - Dateien in den Papierkorb verschieben und daraus verschieben – mit
createTrashRequest()
. - Dateien mit
createDeleteRequest()
löschen
Führen Sie dazu folgende Schritte aus:
Deklarieren Sie die Berechtigung
MANAGE_MEDIA
und die BerechtigungREAD_EXTERNAL_STORAGE
in 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 an, in der erklärt wird, warum er Ihrer App Zugriff auf die Medienverwaltung gewähren sollte.
Rufen Sie die Intent-Aktion
ACTION_REQUEST_MANAGE_MEDIA
auf. 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 Medienspeicher erfordern
Wenn Ihre App hauptsächlich eine der folgenden Aufgaben erfüllt, sollten Sie eine Alternative zu den MediaStore
-APIs in Betracht ziehen.
Mit anderen Dateitypen arbeiten
Wenn Ihre App mit Dokumenten und Dateien arbeitet, die nicht ausschließlich Medieninhalte enthalten, z. B. Dateien mit der Dateiendung EPUB oder PDF, verwenden Sie die Intent-Aktion ACTION_OPEN_DOCUMENT
, wie im Leitfaden zum Speichern 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 zum Speichern und Zugreifen auf Medien finden Sie in den folgenden Ressourcen.
Produktproben
- MediaStore, auf GitHub verfügbar