Für eine verbesserte Nutzererfahrung bieten viele Apps den Nutzern die Möglichkeit, Medien beizusteuern und darauf zuzugreifen, die auf einem externen Speicher-Volume verfügbar sind. Das Framework stellt einen optimierten Index für Mediensammlungen bereit, den sogenannten Medienspeicher, mit dem Nutzer diese Mediendateien leichter abrufen und aktualisieren können. Auch nach der Deinstallation der App bleiben diese Dateien auf dem Gerät des Nutzers.
Zielgenaue Bildauswahl
Als Alternative zur Verwendung des Media Stores bietet die Android-Bildauswahl eine sichere, integrierte Möglichkeit für Nutzer, Mediendateien auszuwählen, ohne deiner App Zugriff auf die gesamte Mediathek gewähren zu müssen. Diese Option ist nur auf unterstützten Geräten verfügbar. Weitere Informationen finden Sie in der Anleitung zur Bildauswahl.
Media-Shop
Für die Interaktion mit der Media Store-Abstraktion 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 klar 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 aus den VerzeichnissenMusic/
oderMovies/
sowie Sprachaufnahmen im VerzeichnisRecordings/
. 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 für Android 9 (API-Level 28) und niedriger nicht verfügbar.
Der Media Store enthält auch eine Sammlung namens MediaStore.Files
. Der Inhalt hängt davon ab, ob Ihre App eingeschränkten Speicher verwendet, der bei Apps für Android 10 oder höher verfügbar ist.
- Wenn der beschränkte Speicher aktiviert ist, werden in der Sammlung nur die Fotos, Videos und Audiodateien angezeigt, die von Ihrer Anwendung erstellt wurden. Die meisten Entwickler müssen
MediaStore.Files
nicht verwenden, um Mediendateien aus anderen Apps anzusehen. Wenn Sie jedoch eine bestimmte Anforderung dazu haben, können Sie die BerechtigungREAD_EXTERNAL_STORAGE
erklären. Wir empfehlen jedoch, dieMediaStore
-APIs zum Öffnen von Dateien zu verwenden, die nicht von Ihrer Anwendung erstellt wurden. - Wenn der beschränkte Speicher nicht verfügbar ist oder nicht verwendet wird, enthält die Sammlung alle Arten von Mediendateien.
Erforderliche Berechtigungen anfordern
Bevor Sie Vorgänge mit Mediendateien ausführen, sollten Sie prüfen, ob in Ihrer Anwendung die Berechtigungen deklariert wurden, 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 Anwendung Berechtigungen für den Speicherzugriff benötigt, hängt davon ab, ob sie nur auf ihre eigenen Mediendateien oder auf Dateien zugreift, die von anderen Anwendungen erstellt wurden.
Zugriff auf eigene Mediendateien
Auf Geräten mit Android 10 oder höher benötigst du keine speicherbezogenen Berechtigungen, um auf Mediendateien zuzugreifen und diese zu ändern, die deiner App gehören, einschließlich Dateien in der Sammlung MediaStore.Downloads
. Wenn Sie beispielsweise eine Kamera-App entwickeln, müssen Sie für den Zugriff auf die aufgenommenen Fotos keine speicherbezogenen Berechtigungen anfordern, da Ihre App der Inhaber der Bilder ist, die Sie in den Medienspeicher schreiben.
Auf Mediendateien anderer Apps zugreifen
Für den Zugriff auf Mediendateien, die von anderen Anwendungen erstellt werden, müssen Sie 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, kann sie auch mit der Abfrage MediaStore.Files
angezeigt werden.
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" />
Für Apps, die auf älteren Geräten ausgeführt werden, sind zusätzliche Berechtigungen erforderlich
Wenn deine App auf einem Gerät mit Android 9 oder niedriger verwendet wird oder wenn der eingeschränkte Speicher für deine App vorübergehend deaktiviert wurde, musst du die Berechtigung READ_EXTERNAL_STORAGE
anfordern, um auf eine Mediendatei zugreifen zu können. Wenn Sie Mediendateien ändern möchten, müssen Sie auch die Berechtigung WRITE_EXTERNAL_STORAGE
anfordern.
Storage Access Framework, das für den Zugriff auf die Downloads anderer Apps erforderlich ist
Wenn Ihre Anwendung auf eine Datei in der Sammlung MediaStore.Downloads
zugreifen möchte, die nicht von Ihrer Anwendung 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 freigegebenem Speicher zugreifen.
Berechtigung zur Standortermittlung für Medien
Wenn deine App auf Android 10 (API-Level 29) oder höher ausgerichtet ist und nicht entfernte 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 Sie zuverlässiger auf Mediendateien zugreifen möchten, insbesondere wenn Ihre App URIs oder Daten aus dem Medienspeicher im Cache speichert, prüfen Sie, ob sich die Version des Medienspeichers im Vergleich zur letzten Synchronisierung Ihrer Mediendaten geändert hat. Rufen Sie dazu getVersion()
auf.
Die zurückgegebene Version ist ein eindeutiger String, der sich immer dann ändert, wenn sich der Medienspeicher erheblich ändert. Wenn sich die zurückgegebene Version von der letzten synchronisierten Version unterscheidet, scannen Sie den Medien-Cache Ihrer App noch einmal und synchronisieren Sie ihn noch einmal.
Führen Sie diese Prüfung beim Start des App-Prozesses durch. Die Version muss nicht jedes Mal geprüft werden, wenn Sie den Mediaspeicher abfragen.
Gehen Sie nicht von Implementierungsdetails in Bezug auf die Versionsnummer aus.
Mediensammlung abfragen
Um Medien zu finden, die einen bestimmten Satz von Bedingungen erfüllen, z. B. eine Dauer von mindestens 5 Minuten, verwenden Sie eine SQL-ähnliche Auswahlanweisung, die der im folgenden Code-Snippet gezeigten ä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 der Durchführung 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 wie in diesem Beispiel gezeigt an den Inhalts-URI an.
- Geräte mit Android 10 und höher benötigen Spaltennamen, die in der
MediaStore
API definiert sind. Wenn eine abhängige Bibliothek in Ihrer Anwendung einen Spaltennamen erwartet, der in der API nicht definiert ist, z. B."MimeType"
, verwenden SieCursorWrapper
, um den Spaltennamen im Prozess Ihrer Anwendung dynamisch zu übersetzen.
Miniaturansichten von Dateien laden
Wenn Ihre Anwendung mehrere Mediendateien anzeigt und der Nutzer auffordert, eine dieser Dateien auszuwählen, ist es effizienter, Vorschauversionen oder Miniaturansichten der Dateien anstelle der Dateien selbst zu laden.
Um die Miniaturansicht für eine bestimmte Mediendatei zu laden, verwenden Sie loadThumbnail()
und übergeben die Größe der zu ladenden Miniaturansicht, 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
Welche Logik Sie zum Öffnen einer Mediendatei verwenden, hängt davon ab, ob der Medieninhalt am besten als Dateideskriptor, als Dateistream oder als direkter Dateipfad dargestellt werden sollte.
Dateideskriptor
Um eine Mediendatei mit einem Dateideskriptor zu öffnen, verwenden Sie eine 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
Um eine Mediendatei mit einem Dateistream zu öffnen, verwenden Sie eine Logik, die der im folgenden Code-Snippet gezeigten ä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 reibungslos mit Mediatheken von Drittanbietern funktioniert, können Sie ab Android 11 (API-Level 30) mit anderen APIs als der MediaStore
API auf Mediendateien aus freigegebenem Speicher zugreifen. Sie können stattdessen mit einer der folgenden APIs direkt auf Mediendateien zugreifen:
- Die
File
API - Native Bibliotheken wie
fopen()
Wenn Sie keine speicherbezogenen Berechtigungen haben, können Sie über die File
API auf Dateien in Ihrem anwendungsspezifischen Verzeichnis sowie auf Mediendateien zugreifen, die Ihrer Anwendung zugeordnet sind.
Wenn Ihre Anwendung versucht, über die File
API auf eine Datei zuzugreifen, und sie nicht die erforderlichen Berechtigungen hat, wird FileNotFoundException
ausgegeben.
Wenn Sie auf einem Gerät mit Android 10 (API-Level 29) auf andere Dateien im freigegebenen Speicher zugreifen möchten, empfehlen wir Ihnen, den begrenzten Speicher vorübergehend zu deaktivieren. Setzen Sie dazu requestLegacyExternalStorage
in der Manifestdatei Ihrer App auf true
. Für den Zugriff auf Mediendateien unter Android 10 mithilfe nativer Dateimethoden müssen Sie außerdem 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 Ihre Anwendung URIs oder Daten aus dem Medienspeicher im Cache speichert, prüfen Sie regelmäßig, ob der Medienspeicher aktualisiert wurde. Durch diese Prüfung bleiben Ihre im Cache gespeicherten App-Daten mit den systemseitigen Anbieterdaten synchron.
Leistung
Wenn Sie sequenzielle Lesevorgänge mit direkten Dateipfaden durchführen, ist die Leistung mit der der MediaStore
API vergleichbar.
Wenn Sie zufällige Lese- und Schreibvorgänge von Mediendateien über direkte Dateipfade ausführen, kann der Vorgang jedoch bis zu doppelt so langsam sein. In diesen Situationen empfehlen wir die Verwendung der MediaStore
API.
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. Gehen Sie jedoch nicht davon aus, dass die Datei immer verfügbar ist. Sie sollten auf den Umgang mit dateibasierten E/A-Fehlern vorbereitet sein.
Um eine Mediendatei zu erstellen oder zu aktualisieren, solltest du jedoch nicht den Wert der Spalte DATA
verwenden. 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. Dieses Benennungssystem hilft Ihnen dabei, Inhalte effizient zu organisieren und zu indexieren, und gibt Ihnen die Kontrolle darüber, wo neue Mediendateien gespeichert werden.
Beachten Sie dabei insbesondere die folgenden Bände:
- Das Volume
VOLUME_EXTERNAL
bietet eine Ansicht aller freigegebenen Speicher-Volumes auf dem Gerät. Sie können den Inhalt dieses synthetischen Volumes lesen, aber nicht ändern. - Das Volume
VOLUME_EXTERNAL_PRIMARY
steht für das primäre Volume mit freigegebenem Speicher auf dem Gerät. Sie können den Inhalt dieses Volumes lesen und ändern.
Sie können andere Volumes ermitteln, indem Sie MediaStore.getExternalVolumeNames()
aufrufen:
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();
Aufnahmeort der Medien
Die Metadaten einiger Fotos und Videos enthalten Standortinformationen, die angeben, an welchem Ort ein Foto oder Video aufgenommen wurde.
Wie Sie in der App auf diese Standortinformationen zugreifen, hängt davon ab, ob Sie für ein Foto oder ein Video auf Standortinformationen zugreifen müssen.
Fotos
Wenn Ihre Anwendung eingeschränkten Speicher verwendet, blendet das System standardmäßig Standortinformationen aus. So greifen Sie auf diese Informationen zu:
- Fordern Sie die Berechtigung
ACCESS_MEDIA_LOCATION
im Manifest Ihrer App an. Um die exakten Byte des Fotos aus dem
MediaStore
-Objekt abzurufen, rufen SiesetRequireOriginal()
auf und übergeben Sie den URI des Fotos, 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
Für den Zugriff auf Standortinformationen in den Metadaten eines Videos 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); }
Freigabe
In einigen Apps können Nutzer Mediendateien untereinander freigeben. 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 für Android 10 oder höher aktiviert ist, weist das System jeder Mediendatei eine App zu. Dadurch wird bestimmt, 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 deine App jedoch deinstalliert und neu installiert, musst du READ_EXTERNAL_STORAGE
anfordern, um auf die ursprünglich von deiner App erstellten Dateien zuzugreifen. Diese Berechtigungsanfrage ist erforderlich, da das System die Datei der zuvor installierten und nicht der neu installierten Version der App zuordnet.
Artikel hinzufügen
Um ein Medienelement zu einer vorhandenen Sammlung hinzuzufügen, verwenden Sie Code ähnlich dem folgenden. Dieses Code-Snippet greift auf das Volume VOLUME_EXTERNAL_PRIMARY
auf Geräten mit Android 10 oder höher zu. Der Grund dafür ist, dass Sie auf diesen Geräten den Inhalt eines Volumes nur ändern können, wenn es das primäre Volume ist, wie im Abschnitt Speicher-Volumes 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 aktivieren/deaktivieren
Wenn Ihre Anwendung potenziell zeitaufwendige Vorgänge wie das Schreiben in Mediendateien ausführt, ist es hilfreich, 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 Anwendung kann die Datei ansehen, bis sie den Wert von IS_PENDING
wieder auf 0 ändert.
Das folgende Code-Snippet baut auf dem vorherigen Code-Snippet auf. Dieses Snippet zeigt, wie das Flag IS_PENDING
verwendet wird, wenn ein langer Song im Verzeichnis gespeichert wird, 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);
Geben Sie einen Hinweis auf den Speicherort einer Datei
Wenn deine App Medien auf einem Gerät mit Android 10 speichert, werden die Medien standardmäßig nach ihrem Typ organisiert. Neue Bilddateien werden beispielsweise standardmäßig im Verzeichnis Environment.DIRECTORY_PICTURES
abgelegt, das der Sammlung MediaStore.Images
entspricht.
Wenn Ihre App einen bestimmten Speicherort erkennt, z. B. ein Fotoalbum mit dem Namen Pictures/MyVacationPictures
, können Sie MediaColumns.RELATIVE_PATH
so festlegen, dass das System einen Hinweis dazu erhält, wo die neu geschriebenen Dateien gespeichert werden sollen.
Artikel aktualisieren
Um eine Mediendatei zu aktualisieren, die Ihrer App gehört, verwenden Sie Code ähnlich dem 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 beschränkte Speicher nicht verfügbar oder nicht aktiviert ist, funktioniert der im vorherigen Code-Snippet gezeigte Vorgang auch für Dateien, die nicht Ihrer Anwendung gehören.
Im nativen Code aktualisieren
Wenn Sie Mediendateien mit nativen Bibliotheken schreiben müssen, übergeben Sie den mit der Datei verknüpften Dateideskriptor aus dem Java- oder Kotlin-basierten Code in den nativen Code.
Das folgende Code-Snippet zeigt, wie der Dateideskriptor eines Medienobjekts in 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 Anwendung eingeschränkten Speicher verwendet, kann sie normalerweise keine Mediendatei aktualisieren, die von einer anderen Anwendung zum Medienspeicher bereitgestellt wurde.
Sie können jedoch die Nutzereinwilligung für die Änderung der Datei einholen, indem Sie das von der Plattform ausgegebene RecoverableSecurityException
abfangen. Anschließend kannst du den Nutzer bitten, deiner 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 Anwendung eine Mediendatei ändern muss, die nicht erstellt wurde.
Wenn deine App unter Android 11 oder höher ausgeführt wird, kannst du Nutzern erlauben, deiner 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 Anwendung einen anderen Anwendungsfall hat, der nicht durch den begrenzten Speicher abgedeckt ist, reichen Sie eine Funktionsanfrage ein und deaktivieren Sie den begrenzten Speicher vorübergehend.
Elemente entfernen
Wenn Sie einen Artikel entfernen möchten, den Ihre App nicht mehr im Media Store benötigt, verwenden Sie eine Logik wie im folgenden Code-Snippet:
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 beschränkte Speicher nicht verfügbar oder nicht aktiviert ist, können Sie mit dem vorherigen Code-Snippet Dateien entfernen, die anderen Anwendungen gehören. Wenn der beschränkte Speicher aktiviert ist, müssen Sie jedoch für jede Datei, die Ihre Anwendung entfernen möchte, eine RecoverableSecurityException
abfangen, wie im Abschnitt zum Aktualisieren von Medienelementen beschrieben.
Wenn deine App unter Android 11 oder höher läuft, kannst du Nutzern erlauben, eine Gruppe von Mediendateien auszuwählen, die entfernt werden sollen. Verwende die Methode createTrashRequest()
oder createDeleteRequest()
, wie im Abschnitt zur Verwaltung von Mediendateigruppen beschrieben.
Wenn Ihre Anwendung einen anderen Anwendungsfall hat, der nicht durch den begrenzten Speicher abgedeckt ist, reichen Sie eine Funktionsanfrage ein und deaktivieren Sie den begrenzten Speicher vorübergehend.
Updates für Mediendateien erkennen
Ihre Anwendung muss möglicherweise Speicher-Volumes identifizieren, die Mediendateien enthalten, die von Apps im Vergleich zu einem früheren Zeitpunkt hinzugefügt oder geändert wurden. Damit diese Änderungen zuverlässig erkannt werden, übergeben Sie das gewünschte Speichervolumen an getGeneration()
.
Solange sich die Version des Media Stores nicht ändert, erhöht sich der Rückgabewert dieser Methode mit der Zeit kontinuierlich.
Insbesondere ist getGeneration()
zuverlässiger als die Datumsangaben in Medienspalten wie DATE_ADDED
und DATE_MODIFIED
.
Das liegt daran, dass sich diese Medienspaltenwerte ä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 zu aktualisieren. Diese Methoden bieten eine bessere Konsistenz auf allen Geräten und die Methoden erleichtern Nutzern die Verwaltung ihrer Mediensammlungen.
Diese "Batch-Update"-Funktionalität wird unter anderem für folgende Methoden bereitgestellt:
createWriteRequest()
- Der Nutzer muss Ihrer Anwendung Schreibzugriff auf die angegebene Gruppe von Mediendateien gewähren.
createFavoriteRequest()
- Der Nutzer soll die angegebenen Mediendateien als bevorzugte Medien auf dem Gerät markieren. Jede App mit Lesezugriff auf diese Datei kann sehen, dass der Nutzer die Datei als „Favorit“ markiert hat.
createTrashRequest()
Nutzer auffordern, 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()
Bitten Sie den Nutzer, 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, sehen Nutzer ein Dialogfeld, in dem sie um Zustimmung bitten, dass Ihre 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);
Bewerten Sie die Antwort der Nutzenden. Wenn der Nutzer eingewilligt hat, fahren Sie mit dem Medienvorgang fort. Erklären Sie andernfalls 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 für die Medienverwaltung
Nutzer vertrauen möglicherweise einer bestimmten App für die Medienverwaltung, z. B. häufig Änderungen an Mediendateien. 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 ein Bestätigungsdialogfeld einblenden, 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 spezielle Berechtigung Medienverwaltung zu gewähren. Mit dieser Berechtigung kann Ihre App die folgenden Aktionen ausführen, ohne den Nutzer für jeden Dateivorgang auffordern zu müssen:
- Ändern Sie Dateien mit
createWriteRequest()
. - Mit
createTrashRequest()
können Sie Dateien in den und aus dem Papierkorb verschieben. - Löschen Sie Dateien mit
createDeleteRequest()
.
Führen Sie dazu die folgenden Schritte aus:
Deklariere in der Manifestdatei deiner App die Berechtigungen
MANAGE_MEDIA
undREAD_EXTERNAL_STORAGE
.Wenn Sie
createWriteRequest()
aufrufen möchten, ohne ein Bestätigungsdialogfeld zu sehen, müssen Sie auch die BerechtigungACCESS_MEDIA_LOCATION
deklarieren.Zeige dem Nutzer in deiner App eine UI, in der er erklärt, warum er der Medienverwaltung Zugriff auf deine App gewähren könnte.
Rufen Sie die Intent-Aktion
ACTION_REQUEST_MANAGE_MEDIA
auf. Dadurch werden Nutzer zum Bildschirm Apps zur Medienverwaltung in den Systemeinstellungen weitergeleitet. Von hier aus können Nutzer der speziellen App Zugriff gewähren.
Anwendungsfälle, die eine Alternative zum Media Store erfordern
Wenn Ihre Anwendung hauptsächlich eine der folgenden Rollen ausführt, sollten Sie eine Alternative zu den MediaStore
APIs in Betracht ziehen.
Mit anderen Dateitypen arbeiten
Wenn deine App mit Dokumenten und Dateien arbeitet, die keine ausschließlichen Medieninhalte enthalten, z. B. Dateien mit der Dateiendung EPUB oder PDF, verwende die Intent-Aktion ACTION_OPEN_DOCUMENT
, wie in der Anleitung zum Speichern und Aufrufen von Dokumenten und anderen Dateien beschrieben.
Dateifreigabe in Companion-Apps
Wenn Sie eine Suite von Companion-Apps bereitstellen, z. B. eine Messaging-App und eine Profil-App, richten Sie die Dateifreigabe ein. Verwenden Sie dazu content://
-URIs. 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, verfügbar auf GitHub