Über freigegebenen Speicher auf Mediendateien zugreifen

Für eine verbesserte Nutzererfahrung ermöglichen viele Apps Nutzern, Medien auf einem externen Speicher-Volume bereitzustellen und darauf zuzugreifen. Das Framework stellt für Mediensammlungen einen optimierten Index namens Medienspeicher zur Verfügung. Damit können Nutzer diese Mediendateien leichter abrufen und aktualisieren. Auch nach der Deinstallation deiner App bleiben diese Dateien auf dem Gerät des Nutzers erhalten.

Zielgenaue Bildauswahl

Als Alternative zum Medienspeicher bietet die Android-Bildauswahl eine sichere, integrierte Möglichkeit, mit der Nutzer Mediendateien auswählen können, ohne Ihrer App Zugriff auf die gesamte Medienbibliothek gewähren zu müssen. Diese Funktion ist nur auf unterstützten Geräten verfügbar. Weitere Informationen finden Sie in der Anleitung zur Bildauswahl.

Mediengeschäft

Verwenden Sie für die Interaktion mit der Media Store-Abstraktion 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/ und Pictures/ gespeichert sind. Das System fügt diese Dateien der Tabelle MediaStore.Images hinzu.
  • Videos, die in den Verzeichnissen DCIM/, Movies/ und Pictures/ gespeichert sind Das System fügt diese Dateien der Tabelle MediaStore.Video hinzu.
  • Audiodateien, die in den Verzeichnissen Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ und Ringtones/ gespeichert sind. Außerdem erkennt das System Audioplaylists, die sich in den Verzeichnissen Music/ oder Movies/ befinden, sowie Sprachaufnahmen im Verzeichnis Recordings/. Das System fügt diese Dateien der Tabelle MediaStore.Audio hinzu. Das Verzeichnis Recordings/ 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 Tabelle MediaStore.Downloads gespeichert. Diese Tabelle ist für Android 9 (API-Level 28) und niedriger nicht verfügbar.

Der Medienspeicher enthält auch eine Sammlung namens MediaStore.Files. Der Inhalt hängt davon ab, ob deine App eingeschränkten Speicher verwendet, der in 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 aufzurufen. Wenn es jedoch eine bestimmte Anforderung gibt, 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 Anwendung erstellt wurden.
  • Wenn ein begrenzter Speicher nicht verfügbar ist oder nicht verwendet wird, enthält die Sammlung alle Arten von Mediendateien.

Erforderliche Berechtigungen anfordern

Bevor Sie Vorgänge für Mediendateien ausführen, prüfen Sie, ob Ihre Anwendung die erforderlichen Berechtigungen für den Zugriff auf diese Dateien deklariert hat. Gib jedoch keine Berechtigungen an, die deine App nicht benötigt oder verwendet.

Speicherberechtigungen

Ob Ihre App 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.

Auf eigene Mediendateien zugreifen

Auf Geräten mit Android 10 oder höher benötigen Sie keine speicherbezogenen Berechtigungen, um auf Mediendateien zuzugreifen und diese zu ändern, die Ihrer App gehören. Das gilt auch für Dateien in der Sammlung MediaStore.Downloads. Wenn Sie beispielsweise eine Kamera-App entwickeln, müssen Sie keine speicherbezogenen Berechtigungen anfordern, um auf die aufgenommenen Fotos zuzugreifen, da Ihre App Eigentümer der Bilder ist, die Sie in den Medienspeicher schreiben.

Auf Mediendateien anderer Apps zugreifen

Für den Zugriff auf Mediendateien, die von anderen Apps erstellt werden, müssen Sie die entsprechenden speicherbezogenen Berechtigungen deklarieren. Die Dateien müssen sich in einer der folgenden Mediensammlungen befinden:

Solange eine Datei über die Abfragen MediaStore.Images, MediaStore.Video oder MediaStore.Audio aufgerufen werden kann, kann sie auch mit der Abfrage MediaStore.Files aufgerufen 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 auf älteren Geräten sind zusätzliche Berechtigungen erforderlich

Wenn Ihre App auf einem Gerät mit Android 9 oder niedriger verwendet wird oder wenn für Ihre App die Zugriffsbeschränkung für begrenzten Speicher vorübergehend deaktiviert wurde, müssen Sie die Berechtigung READ_EXTERNAL_STORAGE für den Zugriff auf eine Mediendatei anfordern. Wenn Sie Mediendateien ändern möchten, müssen Sie auch die Berechtigung WRITE_EXTERNAL_STORAGE anfordern.

Storage Access Framework für den Zugriff auf Downloads anderer Apps erforderlich

Wenn Ihre App auf eine Datei in der Sammlung MediaStore.Downloads zugreifen möchte, die nicht von ihr 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

Um zuverlässiger auf Mediendateien zuzugreifen, 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 getVersion() auf, um diese Suche nach Updates durchzuführen. 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, prüfen Sie den Medien-Cache Ihrer App noch einmal und synchronisieren Sie ihn dann noch einmal.

Führen Sie diese Prüfung beim Start des App-Prozesses durch. Sie müssen die Version nicht bei jeder Abfrage des Medienspeichers überprüfen.

Gehen Sie bezüglich der Versionsnummer nicht von Implementierungsdetails aus.

Mediensammlung abfragen

Um Medien zu finden, die eine bestimmte Gruppe von Bedingungen erfüllen, z. B. eine Dauer von mindestens 5 Minuten, verwenden Sie eine SQL-ähnliche Auswahlanweisung wie die im folgenden Code-Snippet gezeigte:

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 nicht jedes Mal getColumnIndexOrThrow() aufrufen müssen, wenn Sie eine Zeile aus dem Abfrageergebniss verarbeiten.
  • Hängen Sie die ID an den Inhalts-URI an, wie in diesem Beispiel gezeigt.
  • 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 Sie CursorWrapper, um den Spaltennamen im Prozess der Anwendung dynamisch zu übersetzen.

Miniaturansichten von Dateien laden

Wenn Ihre Anwendung mehrere Mediendateien anzeigt und der Nutzer aufgefordert wird, eine dieser Dateien auszuwählen, ist es effizienter, Vorschauversionen – oder Miniaturansichten – der Dateien statt 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

Die spezifische Logik zum Öffnen einer Mediendatei hängt davon ab, ob der Medieninhalt am besten als Dateideskriptor, als Dateistream oder als direkten Dateipfad dargestellt wird.

Dateideskriptor

Zum Öffnen einer Mediendatei mit einem Dateideskriptor verwenden Sie eine Logik, die der im folgenden Code-Snippet gezeigten ähnlich ist:

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

Zum Öffnen einer Mediendatei mit einem Datenstream verwenden Sie eine Logik ähnlich der im folgenden Code-Snippet:

Kotlin

// Open a specific media item using InputStream.
val resolver = applicationContext.contentResolver
resolver.openInputStream(content-uri).use { stream ->
    // Perform operations on "stream".
}

Java

// Open a specific media item using InputStream.
ContentResolver resolver = getApplicationContext()
        .getContentResolver();
try (InputStream stream = resolver.openInputStream(content-uri)) {
    // Perform operations on "stream".
}

Direkte Dateipfade

Damit Ihre App reibungslos mit Mediatheken von Drittanbietern funktioniert, können Sie mit Android 11 (API-Level 30) und höher andere APIs als die MediaStore API verwenden, um auf Mediendateien aus freigegebenem Speicher zuzugreifen. 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 mit der File API auf Dateien in Ihrem anwendungsspezifischen Verzeichnis sowie auf Mediendateien, die Ihrer Anwendung zugeordnet sind, zugreifen.

Wenn Ihre App versucht, über die File API auf eine Datei zuzugreifen, und nicht die erforderlichen Berechtigungen hat, wird ein FileNotFoundException ausgelöst.

Für den Zugriff auf andere Dateien im freigegebenen Speicher auf einem Gerät, auf dem Android 10 (API-Level 29) ausgeführt wird, empfehlen wir, den begrenzten Speicher vorübergehend zu deaktivieren. Dazu setzen Sie requestLegacyExternalStorage in der Manifestdatei Ihrer App auf true. Wenn Sie unter Android 10 mit nativen Dateimethoden auf Mediendateien zugreifen möchten, müssen Sie auch die Berechtigung READ_EXTERNAL_STORAGE anfordern.

Überlegungen beim Zugriff auf Medieninhalte

Beachten Sie beim Zugriff auf Medieninhalte die in den folgenden Abschnitten beschriebenen Überlegungen.

Daten im Cache

Wenn Ihre App URIs oder Daten aus dem Medienspeicher im Cache speichert, prüfen Sie regelmäßig, ob Updates für den Medienspeicher verfügbar sind. Dadurch bleiben die im Cache der Anwendung gespeicherten Daten mit den systemseitigen Anbieterdaten synchron.

Leistung

Wenn Sie sequenzielle Lesevorgänge von Mediendateien mit direkten Dateipfaden durchführen, ist die Leistung mit der Leistung 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 Fällen empfehlen wir stattdessen die Verwendung der MediaStore API.

Spalte DATEN

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. Sie sollten jedoch nicht davon ausgehen, dass die Datei immer verfügbar ist. Seien Sie auf den Umgang mit dateibasierten E/A-Fehlern vorbereitet.

Verwenden Sie zum Erstellen oder Aktualisieren einer Mediendatei jedoch nicht den Wert der Spalte DATA. Verwenden Sie stattdessen die Werte der Spalten DISPLAY_NAME und RELATIVE_PATH.

Speicher-Volumes

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

Die folgenden Bände sind besonders hilfreich:

  • 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_EXTERNAL_PRIMARY-Volume stellt das primäre Volume mit freigegebenem Speicher auf dem Gerät dar. 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();

Ort, an dem Medien aufgenommen wurden

Die Metadaten einiger Fotos und Videos enthalten Standortinformationen, die angeben, wo ein Foto oder Video aufgenommen wurde.

Wie Sie in Ihrer 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 App bezogenen Speicher verwendet, blendet das System Standortinformationen standardmäßig aus. So greifen Sie auf diese Informationen zu:

  1. Fordern Sie im Manifest Ihrer App die Berechtigung ACCESS_MEDIA_LOCATION an.
  2. Rufen Sie aus dem MediaStore-Objekt die genauen Bytes des Fotos ab, indem Sie setRequireOriginal() aufrufen und den URI des Fotos übergeben, wie im folgenden Code-Snippet gezeigt:

    Kotlin

    val photoUri: Uri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex)
    )
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri)
    contentResolver.openInputStream(photoUri)?.use { stream ->
        ExifInterface(stream).run {
            // If lat/long is null, fall back to the coordinates (0, 0).
            val latLong = latLong ?: doubleArrayOf(0.0, 0.0)
        }
    }
    

    Java

    Uri photoUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex));
    
    final double[] latLong;
    
    // Get location data using the Exifinterface library.
    // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted.
    photoUri = MediaStore.setRequireOriginal(photoUri);
    InputStream stream = getContentResolver().openInputStream(photoUri);
    if (stream != null) {
        ExifInterface exifInterface = new ExifInterface(stream);
        double[] returnedLatLong = exifInterface.getLatLong();
    
        // If lat/long is null, fall back to the coordinates (0, 0).
        latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
        // Don't reuse the stream associated with
        // the instance of "ExifInterface".
        stream.close();
    } else {
        // Failed to load the stream, so return the coordinates (0, 0).
        latLong = new double[2];
    }
    

Videos

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 zur Verwendung dieser Klasse keine zusätzlichen Berechtigungen anfordern.

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

Bei einigen Apps können Nutzer Mediendateien untereinander teilen. In Social-Media-Apps können Nutzer beispielsweise Fotos und Videos mit Freunden teilen.

Verwenden Sie zum Freigeben von Mediendateien einen content://-URI, wie in der Anleitung zum Erstellen eines Contentanbieters empfohlen.

App-Attribution von Mediendateien

Wenn für eine App, die auf Android 10 oder höher ausgerichtet ist, begrenzter Speicher aktiviert ist, weist das System jeder Mediendatei eine App zu. Dadurch wird festgelegt, auf welche Dateien Ihre App zugreifen kann, wenn keine Speicherberechtigungen angefordert wurden. 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 die App jedoch deinstalliert und neu installiert, müssen Sie READ_EXTERNAL_STORAGE anfordern, um auf die Dateien zuzugreifen, die ursprünglich von der App erstellt wurden. Diese Berechtigungsanfrage ist erforderlich, da das System die Datei als der zuvor installierten Version der App und nicht der neu installierten Version zuordnet.

Artikel hinzufügen

Verwenden Sie Code ähnlich dem folgenden, um einer vorhandenen Sammlung ein Medienelement hinzuzufügen. Dieses Code-Snippet greift auf das Volume VOLUME_EXTERNAL_PRIMARY auf Geräten mit Android 10 oder höher zu. Das liegt daran, 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);

Ein/Aus-Schaltfläche „Ausstehend“ für Mediendateien

Wenn Ihre Anwendung potenziell zeitaufwendige Vorgänge ausführt, z. B. das Schreiben in Mediendateien, ist es nützlich, während der Verarbeitung exklusiver Zugriff auf die Datei zu haben. Auf Geräten mit Android 10 oder höher kann Ihre App diesen exklusiven Zugriff erhalten. Dazu setzen Sie den Wert des Flags IS_PENDING auf 1. Nur Ihre Anwendung kann die Datei ansehen, bis sie den Wert von IS_PENDING wieder in 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);

Den Speicherort der Datei angeben

Wenn Ihre 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 Anwendung 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 zum Speichern der neu geschriebenen Dateien zu geben.

Artikel aktualisieren

Verwenden Sie zum Aktualisieren einer Mediendatei, die Ihrer App gehört, 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 Prozess auch für Dateien, deren Eigentümer Ihre Anwendung nicht ist.

Im nativen Code aktualisieren

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

Das folgende Code-Snippet zeigt, wie 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 einen bezogenen Speicher verwendet, kann sie normalerweise keine Mediendatei aktualisieren, die von einer anderen App zum Medienspeicher beigetragen hat.

Sie können jedoch die Nutzereinwilligung zum Ändern der Datei einholen, indem Sie das RecoverableSecurityException erfassen, das von der Plattform ausgegeben wird. Anschließend können Sie den Nutzer auffordern, 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 durch, wenn Ihre Anwendung eine Mediendatei ändern muss, die nicht von ihr erstellt wurde.

Wenn Ihre App unter Android 11 oder höher ausgeführt wird, können Sie aber auch zulassen, dass Nutzer Ihrer App Schreibzugriff auf eine Gruppe von Mediendateien gewähren. Verwenden Sie die Methode createWriteRequest(), wie im Abschnitt zum Verwalten von Gruppen von Mediendateien beschrieben.

Wenn es für Ihre Anwendung einen anderen Anwendungsfall gibt, der nicht durch bereichsspezifischen Speicher abgedeckt ist, reichen Sie eine Funktionsanfrage ein und deaktivieren Sie diesen Speicher vorübergehend.

Elemente entfernen

Wenn Sie einen Artikel entfernen möchten, den Ihre App nicht mehr im Medienspeicher benötigt, verwenden Sie eine ähnliche 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 jedoch aktiviert ist, müssen Sie für jede Datei, die Ihre App entfernen möchte, ein RecoverableSecurityException-Objekt abrufen, wie im Abschnitt zum Aktualisieren von Medienelementen beschrieben.

Wenn Ihre App unter Android 11 oder höher ausgeführt wird, können Nutzer eine Gruppe von Mediendateien auswählen lassen, die entfernt werden sollen. Verwenden Sie die Methode createTrashRequest() oder die Methode createDeleteRequest(), wie im Abschnitt zum Verwalten von Gruppen von Mediendateien beschrieben.

Wenn es für Ihre Anwendung einen anderen Anwendungsfall gibt, der nicht durch bereichsspezifischen Speicher abgedeckt ist, reichen Sie eine Funktionsanfrage ein und deaktivieren Sie diesen Speicher vorübergehend.

Aktualisierungen von Mediendateien erkennen

Ihre Anwendung muss möglicherweise Speicher-Volumes identifizieren, die Mediendateien im Vergleich zu einem früheren Zeitpunkt enthalten, die von Apps hinzugefügt oder geändert wurden. Übergeben Sie das gewünschte Speichervolumen an getGeneration(), damit diese Änderungen zuverlässig erkannt werden. Solange sich die Medienspeicherversion nicht ändert, erhöht sich der Rückgabewert dieser Methode mit der Zeit monoton.

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 Anwendung setLastModified() aufruft oder wenn 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.

Folgende Methoden bieten diese Batch-Update-Funktion:

createWriteRequest()
Nutzer anfordern, um Ihrer Anwendung Schreibzugriff auf die angegebene Gruppe von Mediendateien zu gewähren.
createFavoriteRequest()
Nutzer auffordern, die angegebenen Mediendateien als einige ihrer „Favoriten“ auf dem Gerät zu markieren Jede App, die Lesezugriff auf diese Datei hat, 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()

Sie können den Nutzer auffordern, 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 ihre Einwilligung gebeten werden, die angegebenen Mediendateien durch Ihre App zu aktualisieren oder zu löschen.

So strukturieren Sie beispielsweise einen Aufruf von createWriteRequest():

Kotlin

val urisToModify = /* A collection of content URIs to modify. */
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
        urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
    null, 0, 0, 0)

Java

List<Uri> urisToModify = /* A collection of content URIs to modify. */
PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver,
                  urisToModify);

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.getIntentSender(),
    EDIT_REQUEST_CODE, null, 0, 0, 0);

Bewerten Sie die Antwort 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 zur Medienverwaltung

Nutzer vertrauen möglicherweise einer bestimmten App für die Medienverwaltung und nehmen sie z. B. häufig vor, indem sie Mediendateien bearbeiten. 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 anfordern, dass Nutzer Ihrer App Zugriff auf die spezielle Berechtigung Medienverwaltung gewähren. Mit dieser Berechtigung kann Ihre App die folgenden Aktionen ausführen, ohne den Nutzer für jeden Dateivorgang auffordern zu müssen:

Führen Sie dazu die folgenden Schritte aus:

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

    Zum Aufrufen von createWriteRequest() ohne Anzeige eines Bestätigungsdialogfelds müssen Sie auch die Berechtigung ACCESS_MEDIA_LOCATION deklarieren.

  2. Zeigen Sie dem Nutzer in Ihrer App eine UI, in der er erklärt, warum er Medienverwaltungszugriff auf Ihre Anwendung gewähren möchte.

  3. 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 Medienspeicher erfordern

Wenn Ihre App hauptsächlich eine der folgenden Rollen ausübt, sollten Sie eine Alternative zu den MediaStore APIs in Betracht ziehen.

Mit anderen Dateitypen arbeiten

Wenn Ihre Anwendung mit Dokumenten und Dateien arbeitet, die nicht ausschließlich Medieninhalte enthalten, z. B. Dateien mit der Dateiendung EPUB oder PDF, verwenden Sie die Intent-Aktion ACTION_OPEN_DOCUMENT, wie in der Anleitung zum Speichern und Zugreifen auf Dokumente und andere Dateien beschrieben.

Dateifreigabe in Companion-Apps

Wenn Sie eine Reihe von Companion-Apps bereitstellen, z. B. eine Messaging-App und eine Profil-App, richten Sie die Dateifreigabe mithilfe von content://-URIs ein. Wir empfehlen diesen Workflow auch 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