Über freigegebenen Speicher auf Mediendateien zugreifen

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 sogenannten Medienspeicher. mit der Nutzer diese Mediendateien leichter abrufen und aktualisieren können. Gleichmäßig nachdem die App deinstalliert wurde, bleiben diese Dateien auf dem Gerät des Nutzers.

Bildauswahl

Als Alternative zur Verwendung des Medienspeichers bietet das Bildauswahl-Tool von Android eine sichere, integrierte Möglichkeit für Nutzer, Mediendateien auszuwählen, ohne um 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 in der Leitfaden für die Bildauswahl.

Media-Shop

Verwenden Sie zum Interagieren mit der Abstraktion des Medienspeichers ein ContentResolver-Objekt, das Sie aus dem Kontext Ihrer App abrufen:

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.
    }
}
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 ein externes Speicher-Volume automatisch und fügt Mediendateien hinzu klar definierten Sammlungen hinzugefügt:

  • Bilder,einschließlich Fotos und Screenshots, die in den DCIM/ und Pictures/. Das System fügt diese Dateien der Tabelle MediaStore.Images hinzu.
  • Videos, die in DCIM/, Movies/ und Pictures/ gespeichert werden Verzeichnisse enthalten. Das System fügt diese Dateien der Tabelle MediaStore.Video hinzu.
  • Audiodateien, die in den folgenden Formaten gespeichert sind: Alarms/, Audiobooks/, Music/ Notifications/, Podcasts/ und Ringtones/. Darüber hinaus enthält der das System Audioplaylists erkennt, die sich in Music/ oder Movies/ befinden. sowie Sprachaufnahmen in der Recordings/ -Verzeichnis. Das System fügt diese Dateien der Tabelle MediaStore.Audio hinzu. Das Verzeichnis Recordings/ ist unter Android 11 (API-Level 30) nicht verfügbar und nach unten.
  • Heruntergeladene Dateien,die im Verzeichnis Download/ gespeichert sind. An mit Android 10 (API-Level 29) oder höher verwenden, werden diese Dateien im MediaStore.Downloads . Diese Tabelle ist unter Android 9 (API-Level 28) und niedriger nicht verfügbar.

Der Media Store enthält auch eine Sammlung namens MediaStore.Files Inhalt hängen davon ab, ob Ihre App einen Bereich Speicherplatz, der in Apps verfügbar ist, die auf Android 10 oder höher.

  • Wenn der beschränkte Speicher aktiviert ist, werden in der Sammlung nur Fotos, Videos, und Audiodateien, die Ihre App erstellt hat. 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 Berechtigung READ_EXTERNAL_STORAGE deklarieren. Wir empfehlen jedoch, die MediaStore-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 du Aktionen für Mediendateien durchführst, vergewissere dich, dass in deiner App der Fehlercode Berechtigungen, die für den Zugriff auf diese Dateien benötigt werden. Achten Sie jedoch darauf, Berechtigungen deklarieren, die deine App nicht benötigt oder verwendet.

Speicherberechtigungen

Ob deine App Berechtigungen für den Speicherzugriff benötigt, hängt davon ab, ob sie nur eigene Mediendateien oder Dateien, die von anderen Apps erstellt wurden.

Auf eigene Mediendateien zugreifen

Auf Geräten mit Android 10 oder höher benötigen Sie für den Zugriff auf und die Änderung von Mediendateien, der App gehört, einschließlich Dateien im MediaStore.Downloads . Wenn Sie zum Beispiel eine Kamera-App entwickeln, speicherbezogene Berechtigungen anfordern, um auf die aufgenommenen Fotos zuzugreifen. App ist Inhaber der Bilder, 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 angeben. Außerdem müssen sich die Dateien in einer der folgenden Mediensammlungen befinden:

Solange eine Datei über MediaStore.Images angesehen werden kann, MediaStore.Video- oder MediaStore.Audio-Suchanfragen können Sie sie auch über die MediaStore.Files-Abfrage.

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 Ihre App auf einem Gerät mit Android 9 oder niedriger verwendet wird oder Ihre App ist vorübergehend ausgeschlossen. speichern, müssen Sie anfordern: READ_EXTERNAL_STORAGE Zugriff auf beliebige Mediendateien. Wenn du Mediendateien ändern möchtest, musst du anfordern: WRITE_EXTERNAL_STORAGE um eine Genehmigung zu erhalten.

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 Weitere Informationen zur Verwendung dieses Frameworks finden Sie unter Auf Dokumente und andere Dateien aus gemeinsamen Speicher.

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 Medienspeicher suchen

Um zuverlässiger auf Mediendateien zugreifen zu können, insbesondere wenn deine App URIs oder Dateien im Cache speichert, Daten aus dem Medienspeicher erhalten, prüfen Sie, ob sich die Version des Medienspeichers geändert hat. im Vergleich zu der letzten Synchronisierung Ihrer Mediendaten. Um diese Prüfung für Updates, Anruf getVersion() 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. Es ist nicht nötig, die -Version bei jeder Abfrage des Medienspeichers.

Gehen Sie nicht von Implementierungsdetails in Bezug auf die 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:

// 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)
    }
}
// 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.
  • Speichern Sie die Spaltenindexe im Cache, sodass Sie nicht getColumnIndexOrThrow() jedes Mal, wenn Sie eine Zeile aus dem Abfrageergebnisse verarbeiten.
  • Hängen Sie die ID wie in diesem Beispiel gezeigt 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 Sie CursorWrapper, um den Spaltennamen dynamisch in den Prozess Ihrer App zu übersetzen.

Miniaturansichten von Dateien laden

Zeigt Ihre App mehrere Mediendateien an und bittet der Nutzer, eine der ist es effizienter, die Vorschau zu laden, Versionen oder Miniaturansichten des anstelle der Dateien selbst.

Um die Miniaturansicht für eine bestimmte Mediendatei zu laden, verwenden Sie loadThumbnail() und die Größe des zu ladenden Thumbnails übergeben, wie in den folgenden Code-Snippet hinzu:

// Load thumbnail of a specific media item.
val thumbnail: Bitmap =
        applicationContext.contentResolver.loadThumbnail(
        content-uri, Size(640, 480), null)
// 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:

// 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".
}
// 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 wie die in das folgende Code-Snippet:

// Open a specific media item using InputStream.
val resolver = applicationContext.contentResolver
resolver.openInputStream(content-uri).use { stream ->
    // Perform operations on "stream".
}
// 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, Mit Android 11 (API-Level 30) und höher können Sie andere APIs als die MediaStore API für den Zugriff Mediendateien aus freigegebenem Speicher. Sie können stattdessen über eine 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 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. Um auf Mediendateien zuzugreifen, verwenden Sie native Dateimethoden unter Android 10 verwenden, müssen Sie auch READ_EXTERNAL_STORAGE Berechtigung.

Ü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. Mit dieser Prüfung kann Ihr bleiben die im Cache gespeicherten Daten der App mit den systemseitigen Anbieterdaten synchronisiert.

Leistung

Wenn Sie sequenzielle Lesevorgänge über direkte Dateipfade durchführen, der Leistung der Kampagne MediaStore API

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.

Spalte DATA

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. Achten Sie jedoch darauf, 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 DISPLAY_NAME und RELATIVE_PATH Spalten.

Speicher-Volumes

Apps, die auf Android 10 oder höher ausgerichtet sind, können auf den eindeutigen Namen zugreifen. die das System jedem externen Speicher-Volume zuweist. Dieses Benennungssystem können Sie Inhalte effizient organisieren und indexieren und haben die Kontrolle wo neue Mediendateien gespeichert werden.

Die folgenden Bände sind besonders nützlich:

  • Die VOLUME_EXTERNAL Volume bietet eine Ansicht aller freigegebenen Speicher-Volumes auf dem Gerät. Sie können den Inhalt dieses synthetischen Volumes lesen, aber nicht ändern.
  • Die VOLUME_EXTERNAL_PRIMARY Volume steht für das primäre freigegebene Speicher-Volume auf dem Gerät. Sie können den Inhalt dieses Volumes lesen und ändern.

Sie können weitere Volumes ermitteln, indem Sie MediaStore.getExternalVolumeNames():

val volumeNames: Set<String> = MediaStore.getExternalVolumeNames(context)
val firstVolumeName = volumeNames.iterator().next()
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üssen.

Fotos

Wenn Ihre Anwendung eingeschränkten Speicher verwendet, Standortinformationen standardmäßig ausgeblendet. Um auf diese Informationen zuzugreifen, führen Sie die folgenden Schritte aus:

  1. Fordern Sie die Berechtigung ACCESS_MEDIA_LOCATION im Manifest Ihrer App an.
  2. Rufe setRequireOriginal() auf und gib den URI des Fotos an, um die genauen Bytes des Fotos aus deinem MediaStore-Objekt abzurufen, wie im folgenden Code-Snippet gezeigt:

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

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)
}
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. Zum Beispiel soziale Netzwerke Mit Medien-Apps können Nutzer Fotos und Videos mit Freunden teilen.

Verwende zum Freigeben von Mediendateien einen content://-URI, wie in der Anleitung zur Contentanbieter erstellen.

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 eine App. Wenn Ihre App also eine Mediendatei erstellt, die im Fotos, Videos oder Audiodateien erfassen möchten, hat deine 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 eine bereits installierte Version der App.

Artikel hinzufügen

Wenn du einer vorhandenen Sammlung ein Medienelement hinzufügen möchtest, verwende Code, der dem folgenden ähnelt. Dieses Code-Snippet greift auf das Volume VOLUME_EXTERNAL_PRIMARY zu auf Geräten mit Android 10 oder höher. Das liegt daran, dass Sie auf diesen Geräten kann den Inhalt eines Volumes nur ändern, wenn es das primäre Volume ist, wie im Abschnitt Speichervolumen beschrieben.

// 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)
// 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 ausführt, Mediendateien sind, ist es nützlich, exklusiven Zugriff auf die Datei zu haben, verarbeitet werden. Auf Geräten mit Android 10 oder höher kann Ihre App erhalten Sie diesen exklusiven Zugriff, indem Sie den Wert IS_PENDING auf 1 setzen. Solange Ihre App nicht den Wert von IS_PENDING auf 0 zurücksetzen.

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:

// 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)
// 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 auf Speicherort der Datei angeben

Wenn deine App Medien auf einem Gerät mit Android 10 speichert, gilt Folgendes: sind die Medien standardmäßig nach ihrem Typ organisiert. Zum Beispiel ist standardmäßig werden Bilddateien im Environment.DIRECTORY_PICTURES Verzeichnis, das dem Sammlung MediaStore.Images.

Wenn Ihre App einen bestimmten Speicherort erkennt, an dem Dateien gespeichert werden können, z. B. als Fotoalbum "Pictures/MyVacationPictures" auswählen, können Sie MediaColumns.RELATIVE_PATH um dem System einen Hinweis zu geben, 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:

// 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)
// 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 begrenzter Speicher nicht verfügbar oder nicht aktiviert ist, wird der im das vorherige Code-Snippet auch für Dateien verwendet werden kann, die nicht Ihrer App gehören.

Im nativen Code aktualisieren

Wenn Sie Mediendateien mit nativen Bibliotheken schreiben müssen, übergeben Sie zugehörigen Dateideskriptor aus Ihrem Java- oder Kotlin-basierten Code in Ihren nativen Code.

Das folgende Code-Snippet zeigt, wie der Dateideskriptor eines Medienobjekts übergeben wird. in den nativen Code Ihrer App einfügen:

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

Andere Apps aktualisieren“ Mediendateien

Wenn Ihre Anwendung eingeschränkten Speicher verwendet, Mediendateien, die von einer anderen App zur Datei beigetragen haben, können normalerweise nicht aktualisiert werden. im Medienspeicher.

Sie können jedoch die Zustimmung des Nutzers zum Ändern der Datei einholen, indem Sie den RecoverableSecurityException die die Plattform wirft. Sie können dann den Nutzer bitten, Ihrer App Schreibzugriff auf das betreffende Element zu gewähren, wie im folgenden Code-Snippet gezeigt:

// 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)
    }
}
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 zur Verwaltung von Mediengruppen beschrieben. Dateien.

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:

// 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)
// 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 den createTrashRequest(). oder die Methode createDeleteRequest() wie im Abschnitt zur Verwaltung von Mediengruppen beschrieben. Dateien.

Wenn es für Ihre Anwendung einen anderen Anwendungsfall gibt, der nicht durch den begrenzten Speicher abgedeckt ist, reichen Sie einen Funktionsanfrage und vorübergehend deaktiviert Speicherplatz.

Updates für Mediendateien erkennen

Ihre App muss möglicherweise Speicher-Volumes mit Mediendateien identifizieren, die von Apps installiert werden im Vergleich zu einem vorherigen Zeitpunkt hinzugefügt oder geändert haben. Um diese Änderungen zu erkennen die zuverlässigsten sind, können Sie das gewünschte Speichervolumen getGeneration() Solange sich die Version des Media Stores nicht ändert, ist der Rückgabewert dieses mit der Zeit kontinuierlich ansteigt.

Insbesondere ist getGeneration() zuverlässiger als die Datumsangaben in Medienspalten. zum Beispiel 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 Einheitlichkeit auf allen Geräten und die Methoden vereinfachen damit Nutzer ihre Mediensammlungen verwalten können.

Die Methoden, die dieses "Batch-Update" bereitstellen Funktionen umfassen die Folgendes:

createWriteRequest()
Anfrage, dass der Nutzer Ihrer App Schreibzugriff auf die angegebene Gruppe von Mediendateien.
createFavoriteRequest()
Bitten Sie den Nutzer, die angegebenen Mediendateien als seine „Lieblingsmedien“ auf dem Gerät zu markieren. Jede App mit Lesezugriff auf diese Datei kann Sie sehen, dass der Nutzer die Datei als „Favorit“ 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 Aufrufen einer dieser Methoden erstellt das System eine PendingIntent-Objekt. Nach der Anwendung ruft diesen Intent auf und Nutzer sehen ein Dialogfeld, in dem sie um ihre Einwilligung für Ihre App gebeten werden. , um die angegebenen Mediendateien zu aktualisieren oder zu löschen.

So strukturierst du beispielsweise einen Aufruf an createWriteRequest():

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)
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 eingewilligt hat, fahren Sie mit der Medienbetrieb. Andernfalls erklären Sie dem Nutzer, warum Ihre App die Berechtigung benötigt:

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. */
            }
    }
}
@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 createFavoriteRequest(), createTrashRequest(), und createDeleteRequest()

Berechtigung für die Medienverwaltung

Nutzer vertrauen möglicherweise einer bestimmten App für die Medienverwaltung, z. B. häufig bearbeitete 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 erteilen. Mit dieser Berechtigung kann Ihre App Folgendes tun, ohne den Nutzer bei jedem Dateivorgang um Erlaubnis bitten zu müssen:

Gehen Sie dazu so vor:

  1. Deklarieren Sie die Berechtigung MANAGE_MEDIA und die Berechtigung READ_EXTERNAL_STORAGE in der Manifestdatei Ihrer App.

    Wenn createWriteRequest() ohne Bestätigungsdialogfeld aufgerufen werden soll, müssen Sie auch die Berechtigung ACCESS_MEDIA_LOCATION angeben.

  2. Zeige dem Nutzer in deiner App eine Benutzeroberfläche, in der er erklärt, warum er ihm Zugriff auf die Medienverwaltung deiner App.

  3. Rufen Sie die Methode ACTION_REQUEST_MANAGE_MEDIA Intent-Aktion. 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

Videos