Viele Apps bieten Nutzern die Möglichkeit, Medien auf einem externen Speicher zu speichern und darauf zuzugreifen. Das Framework bietet einen optimierten Index für Mediensammlungen, den Media Store, mit dem Nutzer diese Mediendateien einfacher abrufen und aktualisieren können. Diese Dateien verbleiben auch nach der Deinstallation Ihrer App auf dem Gerät des Nutzers.
Bildauswahl
Als Alternative zum Media-Shop 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
Verwenden Sie zum Interagieren mit der Abstraktion des Medienspeichers 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 Speichervolumen 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 im VerzeichnisMusic/
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 unter Android 9 (API-Level 28) und niedriger nicht verfügbar.
Der Medienspeicher enthält außerdem eine Sammlung namens MediaStore.Files
. Der Inhalt hängt davon ab, ob Ihre App speicherbeschränkten Speicher verwendet, der in Apps verfügbar ist, die auf Android 10 oder höher ausgerichtet sind.
- Wenn der abgegrenzte 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 anzusehen. Wenn Sie dies jedoch benötigen, können Sie die BerechtigungREAD_EXTERNAL_STORAGE
deklarieren. Wir empfehlen jedoch, dieMediaStore
-APIs zu verwenden, um Dateien zu öffnen, die nicht von Ihrer App erstellt wurden. - Wenn der Speicherplatz mit begrenztem Zugriff nicht verfügbar oder nicht verwendet wird, werden in der Sammlung alle Arten von Mediendateien angezeigt.
Erforderliche Berechtigungen anfordern
Bevor Sie Vorgänge auf Mediendateien ausführen, müssen Sie in Ihrer App die Berechtigungen deklarieren, die für den Zugriff auf diese Dateien erforderlich sind. Achten Sie jedoch darauf, keine Berechtigungen zu deklarieren, die für Ihre App nicht erforderlich sind oder nicht verwendet werden.
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 Speicherberechtigungen, um auf Mediendateien zuzugreifen und sie zu ändern, die Ihrer App gehören, einschließlich Dateien in der MediaStore.Downloads
-Sammlung. Wenn Sie beispielsweise eine Kamera-App entwickeln, müssen Sie keine speicherbezogenen Berechtigungen anfordern, um auf die aufgenommenen Fotos zuzugreifen, da die Bilder, die Sie in den Medienspeicher schreiben, Ihrer App gehören.
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 angeben. 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
angezeigt werden kann, ist sie auch über die Abfrage MediaStore.Files
verfügbar.
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 Sie die befristete Deaktivierung des befristeten Speichers für Ihre App aktiviert haben, müssen Sie die Berechtigung READ_EXTERNAL_STORAGE
anfordern, um auf Mediendateien zugreifen zu können. 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 MediaStore.Downloads
-Sammlung zugreifen soll, 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 freigegebenen Speichern zugreifen.
Berechtigung zur Standortermittlung für Medien
Wenn Ihre App auf Android 10 (API-Level 29) oder höher ausgerichtet ist und nicht entfernte 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 Medienshop suchen
Damit Sie zuverlässiger auf Mediendateien zugreifen können, insbesondere wenn Ihre App URIs oder Daten aus dem Medienspeicher im Cache speichert, prüfen Sie, ob sich die Version des Medienspeichers 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 wesentlich ändert. Wenn sich die zurückgegebene Version von der zuletzt synchronisierten Version unterscheidet, scannen und synchronisieren Sie den Mediencache Ihrer App noch einmal.
Führen Sie diese Prüfung beim Starten des App-Prozesses durch. Sie müssen die Version nicht jedes Mal prüfen, wenn Sie den Medienspeicher abfragen.
Gehen Sie nicht von Implementierungsdetails bezüglich der Versionsnummer aus.
Mediensammlung abfragen
Wenn du nach Medien suchen möchtest, die bestimmte Bedingungen erfüllen, z. B. eine Dauer von mindestens 5 Minuten, verwende eine SQL-ähnliche Auswahlanweisung wie die 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 der Ausführung einer solchen Abfrage in Ihrer App Folgendes:
- Rufen Sie die Methode
query()
in einem Worker-Thread auf. - Cachen Sie die Spaltenindizes, damit Sie nicht jedes Mal, wenn Sie eine Zeile aus dem Abfrageergebnis verarbeiten,
getColumnIndexOrThrow()
aufrufen müssen. - Hängen Sie die ID wie in diesem Beispiel an den Inhalts-URI an.
- 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 dynamisch in den Prozess Ihrer App zu übersetzen.
Datei-Miniaturansichten laden
Wenn Ihre App mehrere Mediendateien anzeigt und den Nutzer auffordert, eine dieser Dateien auszuwählen, ist es effizienter, Vorschauversionen – oder Miniaturansichten – der Dateien statt der Dateien selbst zu laden.
Wenn du das Thumbnail für eine bestimmte Mediendatei laden möchtest, verwende loadThumbnail()
und gib die Größe des Thumbnails an, das du laden möchtest, 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 du zum Öffnen einer Mediendatei verwendest, hängt davon ab, ob die Medieninhalte am besten als Dateideskriptor, Dateistream oder direkter Dateipfad dargestellt werden.
Dateideskriptor
Wenn du eine Mediendatei mit einem Dateideskriptor öffnen möchtest, verwende eine Logik ähnlich der im folgenden Code-Snippet gezeigten:
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 du eine Mediendatei mit einem Dateistream öffnen möchtest, verwende eine Logik ähnlich der im folgenden Code-Snippet gezeigten:
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 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. Sie können stattdessen über eine der folgenden APIs direkt auf Mediendateien zugreifen:
- Die
File
API - Native Bibliotheken, z. B.
fopen()
Wenn Sie keine speicherbezogenen Berechtigungen haben, können Sie über die File
API auf Dateien in Ihrem app-spezifischen Verzeichnis sowie auf Mediendateien zugreifen, die Ihrer App zugeordnet sind.
Wenn Ihre App versucht, über die File
API auf eine Datei zuzugreifen, aber nicht über die erforderlichen Berechtigungen verfügt, tritt der Fehler FileNotFoundException
auf.
Wenn Sie auf andere Dateien im freigegebenen Speicher auf einem Gerät mit Android 10 (API-Ebene 29) zugreifen möchten, empfehlen wir Ihnen, den speicherortbezogenen Speicher vorübergehend zu deaktivieren. Legen Sie dazu in der Manifestdatei Ihrer App requestLegacyExternalStorage
auf true
fest. Wenn Sie unter Android 10 mithilfe von 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 Aspekte.
Daten im Cache
Wenn Ihre App URIs oder Daten aus dem Medienspeicher im Cache speichert, sollten Sie regelmäßig nach Updates für den Medienspeicher suchen. Durch diese Prüfung bleiben die app-seitigen, im Cache gespeicherten Daten mit den systemseitigen Anbieterdaten synchron.
Leistung
Wenn du sequenzielle Lesevorgänge von Mediendateien mit direkten Dateipfaden ausführst, ist die Leistung mit der der MediaStore
API vergleichbar.
Wenn Sie jedoch zufällige Lese- und Schreibvorgänge von Mediendateien mit direkten Dateipfaden ausführen, kann der Vorgang bis zu doppelt so langsam sein. In diesen Fällen empfehlen wir stattdessen die Verwendung der MediaStore
API.
DATENSALTE
Wenn du auf eine vorhandene Mediendatei zugreifst, kannst du den Wert der Spalte DATA
in deiner Logik verwenden. Das liegt daran, dass dieser Wert einen gültigen Dateipfad hat. Angenommen werden darf jedoch nicht, dass die Datei immer verfügbar ist. Seien Sie auf alle auftretenden dateibasierten I/O-Fehler vorbereitet.
Wenn Sie hingegen 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
.
Speichervolumes
Apps, die auf Android 10 oder höher ausgerichtet sind, können auf den eindeutigen Namen zugreifen, den das System jedem externen Speichervolume zuweist. Dieses Benennungssystem hilft dir, Inhalte effizient zu organisieren und zu indexieren. Außerdem kannst du damit festlegen, wo neue Mediendateien gespeichert werden.
Die folgenden Bände sind besonders nützlich:
- Das Volume
VOLUME_EXTERNAL
enthält 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_PRIMARY
steht für das primäre freigegebene Speichervolume auf dem Gerät. Sie können den Inhalt dieses Volumes lesen und ändern.
Sie können andere Volumes durch Aufrufen von MediaStore.getExternalVolumeNames()
finden:
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, an dem ein Foto aufgenommen oder ein Video aufgenommen wurde.
Wie Sie in Ihrer App auf diese Standortinformationen zugreifen, hängt davon ab, ob Sie auf die Standortinformationen für ein Foto oder für ein Video zugreifen möchten.
Fotos
Wenn Ihre App einen abgegrenzten Speicher 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. Rufe
setRequireOriginal()
auf und gib den URI des Fotos an, um die genauen Bytes des Fotos aus deinemMediaStore
-Objekt abzurufen, 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
Wenn du auf Standortinformationen in den Metadaten eines Videos zugreifen möchtest, verwende 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); }
Inhalte teilen
In einigen Apps können Nutzer Mediendateien miteinander teilen. Mit Social-Media-Apps können Nutzer beispielsweise Fotos und Videos mit Freunden teilen.
Verwende zum Freigeben von Mediendateien einen content://
-URI, wie im Leitfaden zum Erstellen eines Contentanbieters empfohlen.
App-Attribution von Mediendateien
Wenn der befristete Speicher für eine App aktiviert ist, die auf Android 10 oder höher ausgerichtet ist, ordnet das System jeder Mediendatei eine App zu. So 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 wieder 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, nicht der neu installierten.
Artikel hinzufügen
Wenn du einer vorhandenen Sammlung ein Medienelement hinzufügen möchtest, verwende Code, der dem folgenden ähnelt. Dieses Code-Snippet ruft die Lautstärke VOLUME_EXTERNAL_PRIMARY
auf Geräten mit Android 10 oder höher ab. Das liegt daran, dass Sie auf diesen Geräten den Inhalt eines Volumes nur dann ä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 umschalten
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 Ihre App diesen exklusiven Zugriff erhalten, indem Sie den Wert des Flags IS_PENDING
auf 1 festlegen. Die Datei kann nur von Ihrer App angezeigt werden, bis Ihre App den Wert von IS_PENDING
wieder in 0 ändert.
Das folgende Code-Snippet baut auf dem vorherigen Code-Snippet auf. In diesem Snippet wird gezeigt, wie du das Flag IS_PENDING
verwendest, wenn du einen langen Titel im Verzeichnis speicherst, 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);
Einen Hinweis zum Dateispeicherort geben
Wenn Ihre App Medien auf einem Gerät mit Android 10 speichert, werden sie standardmäßig nach Typ organisiert. So werden neue Bilddateien standardmäßig im Verzeichnis Environment.DIRECTORY_PICTURES
abgelegt, das der Sammlung MediaStore.Images
entspricht.
Wenn Ihre App einen bestimmten Speicherort kennt, an dem Dateien gespeichert werden können, 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 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 Speicher mit begrenztem Zugriff nicht verfügbar oder nicht aktiviert ist, funktioniert das im vorherigen Code-Snippet gezeigte Verfahren auch für Dateien, die nicht Ihrer App gehören.
Aktualisierung im nativen Code
Wenn Sie Mediendateien mit nativen Bibliotheken schreiben möchten, übergeben Sie den zugehörigen Dateideskriptor der Datei aus Ihrem Java- oder Kotlin-Code in Ihren nativen Code.
Im folgenden Code-Snippet wird gezeigt, 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 speicherortspezifischen Speicher verwendet, kann sie normalerweise keine Mediendatei aktualisieren, die eine andere App zum Medienspeicher beigesteuert hat.
Sie können jedoch die Nutzereinwilligung zur Änderung der Datei einholen, indem Sie die von der Plattform geworfene RecoverableSecurityException
abfangen. Sie können dann 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 auf Android 11 oder höher ausgeführt wird, können Sie Nutzern auch erlauben, Ihrer App Schreibzugriff auf eine Gruppe von Mediendateien zu gewähren. Verwenden Sie die Methode createWriteRequest()
, wie im Abschnitt zum Verwalten von Gruppen von Mediendateien beschrieben.
Wenn Ihre App einen anderen Anwendungsfall hat, der nicht vom Speicher mit begrenztem Zugriff abgedeckt ist, reichen Sie einen Funktionsantrag ein und deaktivieren Sie den Speicher mit begrenztem Zugriff vorübergehend.
Elemente entfernen
Wenn Sie einen Artikel aus dem Medienspeicher entfernen möchten, der für Ihre App nicht mehr benötigt wird, verwenden Sie eine Logik, die der im folgenden Code-Snippet gezeigten ä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 befristete Speicherplatz nicht verfügbar oder nicht aktiviert ist, können Sie mit dem Code-Snippet oben Dateien entfernen, deren Eigentümer andere Apps sind. Wenn der eingeschränkte Speicher aktiviert ist, müssen Sie jedoch für jede Datei, die Ihre App entfernen möchte, eine RecoverableSecurityException
abfangen, wie im Abschnitt zum Aktualisieren von Medienelementen beschrieben.
Wenn Ihre App auf Android 11 oder höher ausgeführt wird, können Sie Nutzern erlauben, eine Gruppe von Mediendateien auszuwählen, die entfernt werden sollen. 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 vom Speicher mit begrenztem Zugriff abgedeckt ist, reichen Sie einen Funktionsantrag ein und deaktivieren Sie den Speicher mit begrenztem Zugriff vorübergehend.
Aktualisierungen von Mediendateien erkennen
Ihre App muss möglicherweise Speichervolumes mit Mediendateien identifizieren, die Apps im Vergleich zu einem früheren Zeitpunkt hinzugefügt oder geändert haben. Um diese Änderungen möglichst zuverlässig zu erkennen, geben Sie das gewünschte Speichervolumen in getGeneration()
ein.
Solange sich die Version des Medienspeichers nicht ändert, steigt der Rückgabewert dieser Methode mit 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 dann in einem einzigen Vorgang aktualisieren. Diese Methoden bieten eine bessere Gerätekonsistenz und erleichtern Nutzern die Verwaltung ihrer Mediensammlungen.
Zu den Methoden, die diese Funktion „Batch-Update“ bieten, gehören:
createWriteRequest()
- Bitten Sie den Nutzer, Ihrer App Schreibzugriff auf die angegebene Gruppe von Mediendateien zu gewähren.
createFavoriteRequest()
- Bitten Sie den Nutzer, die angegebenen Mediendateien als seine „Lieblingsmedien“ auf dem Gerät zu markieren. Jede App, die Lesezugriff auf diese Datei hat, kann sehen, dass der Nutzer die Datei als „Favoriten“ markiert hat.
createTrashRequest()
Bitte den Nutzer, die angegebenen Mediendateien in den Papierkorb des Geräts zu verschieben. Elemente im Papierkorb werden nach einem systemdefinierten 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 Aufruf einer dieser Methoden erstellt das System ein PendingIntent
-Objekt. Nachdem Ihre App diese Intent aufgerufen hat, wird Nutzern ein Dialogfeld angezeigt, in dem sie ihre Einwilligung dazu geben müssen, dass Ihre App die angegebenen Mediendateien aktualisieren oder löschen darf.
So strukturierst du beispielsweise einen Aufruf an 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 Medienvorgang 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. */ } } }
Dieses allgemeine Muster lässt sich auch für createFavoriteRequest()
, createTrashRequest()
und createDeleteRequest()
verwenden.
Berechtigung zur Medienverwaltung
Nutzer vertrauen einer bestimmten App möglicherweise die Medienverwaltung an, z. B. häufige Änderungen an Mediendateien. Wenn Ihre App auf Android 11 oder höher ausgerichtet ist und nicht die Standardgalerie-App des Geräts ist, muss dem Nutzer jedes Mal ein Bestätigungsdialogfeld angezeigt werden, 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 die spezielle Berechtigung Medienverwaltung zu gewähren. Mit dieser Berechtigung kann Ihre App Folgendes tun, ohne den Nutzer bei jedem Dateivorgang um Erlaubnis bitten zu müssen:
- Dateien mit
createWriteRequest()
ändern - Verschieben Sie Dateien mit
createTrashRequest()
in den Papierkorb und wieder daraus. - Dateien können mit
createDeleteRequest()
gelöscht werden.
Gehen Sie dazu so vor:
Deklarieren Sie die Berechtigung
MANAGE_MEDIA
und die BerechtigungREAD_EXTERNAL_STORAGE
in der Manifestdatei Ihrer App.Wenn
createWriteRequest()
ohne Bestätigungsdialogfeld aufgerufen werden soll, müssen Sie auch die BerechtigungACCESS_MEDIA_LOCATION
angeben.Zeigen Sie in Ihrer App eine Benutzeroberfläche, in der erklärt wird, warum der Nutzer Ihrer App Zugriff auf die Medienverwaltung gewähren sollte.
Rufen Sie die Intent-Aktion
ACTION_REQUEST_MANAGE_MEDIA
auf. Dadurch gelangen Nutzer in den Bereich Apps zur Medienverwaltung in den Systemeinstellungen. Hier können Nutzer der App den speziellen Zugriff gewähren.
Anwendungsfälle, für die eine Alternative zum Medienspeicher erforderlich ist
Wenn Ihre App hauptsächlich eine der folgenden Rollen ausführt, sollten Sie eine Alternative zu den MediaStore
APIs in Betracht ziehen.
Mit anderen Dateitypen arbeiten
Wenn Ihre App mit Dokumenten und Dateien funktioniert, 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 Zugriff auf Dokumente und andere Dateien beschrieben.
Dateifreigabe in Companion-Apps
Wenn Sie eine Suite von Companion-Apps anbieten, z. B. eine Messaging-App und eine Profil-App, richten Sie die Dateifreigabe mithilfe von content://
-URIs ein. Außerdem empfehlen wir diesen Workflow als Best Practice für die Sicherheit.
Weitere Informationen
Weitere Informationen zum Speichern und Abrufen von Medien finden Sie in den folgenden Ressourcen.
Produktproben
- MediaStore, verfügbar auf GitHub