Dostęp do plików multimedialnych z pamięci współdzielonej

Aby zapewnić użytkownikom lepsze wrażenia, wiele aplikacji umożliwia im publikowanie treści i korzystaj z multimediów dostępnych w pamięci zewnętrznej. Struktura zapewnia zoptymalizowany indeks do kolekcji multimediów, nazywany magazynem multimediów; który pozwala użytkownikom łatwiej pobierać i aktualizować te pliki multimedialne. Nawet po odinstalowaniu aplikacji pliki te pozostają na urządzeniu użytkownika.

Selektor zdjęć

Alternatywą dla sklepu z multimediami jest selektor zdjęć na Androidzie umożliwia użytkownikom wybieranie plików multimedialnych w bezpieczny, wbudowany sposób, bez , aby przyznać aplikacji dostęp do całej biblioteki multimediów. Ta opcja jest dostępna tylko na obsługiwanych urządzeniach. Więcej informacji: selektora zdjęć.

Sklep z multimediami

Aby wejść w interakcję z abstrakcją w magazynie multimediów, użyj funkcji ContentResolver obiektem, który pobrane z kontekstu aplikacji:

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

System automatycznie skanuje wolumin pamięci zewnętrznej i dodaje pliki multimedialne do następujących dobrze zdefiniowanych kolekcji:

  • Obrazy,w tym zdjęcia i zrzuty ekranu, które są przechowywane Katalogi DCIM/ i Pictures/. System doda te pliki do MediaStore.Images.
  • Filmy, które są przechowywane w DCIM/, Movies/ i Pictures/ i katalogów. System doda te pliki do MediaStore.Video.
  • Pliki audio są przechowywane w folderach Alarms/, Audiobooks/, Music/, Katalogi Notifications/, Podcasts/ i Ringtones/. System rozpoznaje też playlisty audio w katalogach Music/ lub Movies/ oraz nagrania głosowe w katalogu Recordings/. System doda te pliki do MediaStore.Audio. Katalog Recordings/ nie jest dostępny na Androidzie 11 (poziom interfejsu API 30) i mniejsza.
  • Pobrane pliki, które są przechowywane w katalogu Download/. Wł. urządzeń z Androidem 10 (poziom interfejsu API 29) lub nowszym, pliki te są przechowywane MediaStore.Downloads tabeli. Ta tabela nie jest dostępna w Androidzie 9 (poziom interfejsu API 28) i starszych.

Sklep z mediami ma również kolekcję o nazwie MediaStore.Files Zawartość tego pliku zależy od tego, czy Twoja aplikacja korzysta z ograniczonego miejsca na dane, dostępnego w przypadku aplikacji przeznaczonych na Androida 10 lub nowszego.

  • Jeśli włączone jest ograniczone miejsce na dane, kolekcja zawiera tylko zdjęcia, filmy, i pliki audio utworzone przez aplikację. Większość programistów nie musi MediaStore.Files, aby wyświetlać pliki multimedialne z innych aplikacji, ale jeśli masz lub określony wymóg, możesz zadeklarować READ_EXTERNAL_STORAGE uprawnienia. Zalecamy jednak, aby używać interfejsów API MediaStore do otwierania plików, które nie zostały utworzone przez Twoją aplikację.
  • Jeśli miejsce na dane o zakresie jest niedostępne lub nie jest używane, kolekcja wyświetla wszystkie różnych typów plików multimedialnych.

Poproś o wymagane uprawnienia

Zanim wykonasz operacje na plikach multimedialnych, upewnij się, że aplikacja ma zadeklarowane uprawnienia potrzebne do uzyskania dostępu do tych plików. Należy jednak uważać, aby nie zadeklarować uprawnienia, których aplikacja nie potrzebuje ani nie używa.

Uprawnienia do przechowywania danych

To, czy Twoja aplikacja potrzebuje uprawnień dostępu do pamięci, zależy od tego, czy ma dostęp tylko do własnych plików multimedialnych, czy też do plików utworzonych przez inne aplikacje.

Dostęp do własnych plików multimedialnych

Na urządzeniach z Androidem 10 lub nowszym nie musisz mieć uprawnień związanych z pamięcią, aby uzyskać dostęp do plików multimedialnych należących do Twojej aplikacji i je modyfikować, w tym plików w zbiorze MediaStore.Downloads. Jeśli na przykład tworzysz aplikację aparatu, poproś o uprawnienia związane z przechowywaniem danych na dostęp do zrobionych zdjęć, ponieważ właścicielem obrazów, które zapisujesz do sklepu multimedialnego,

Dostęp do innych aplikacji pliki multimedialne

Aby uzyskać dostęp do plików multimedialnych tworzonych przez inne aplikacje, musisz zadeklarować odpowiednie uprawnienia związane z przechowywaniem, a pliki muszą znajdować się w jednym z te kolekcje multimediów:

Jeśli plik można wyświetlić w MediaStore.Images, MediaStore.Video lub MediaStore.Audio, jest on również widoczny przy użyciu parametru MediaStore.Files.

Ten fragment kodu pokazuje, jak zadeklarować właściwą pamięć masową uprawnienia:

<!-- 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" />

Potrzebne są dodatkowe uprawnienia aplikacjom działającym na starszych urządzeniach

Jeśli aplikacja jest używana na urządzeniu z Androidem 9 lub starszym albo aplikacja tymczasowo wycofała zakres , musisz poproś o READ_EXTERNAL_STORAGE. dostęp do plików multimedialnych. Jeśli chcesz zmodyfikować pliki multimedialne, musisz: poproś o WRITE_EXTERNAL_STORAGE również uprawnienie.

Storage Access Framework wymagany do uzyskiwania dostępu do plików pobranych z innych aplikacji

Jeśli aplikacja chce uzyskać dostęp do pliku z kolekcji MediaStore.Downloads które nie została utworzona przez Twoją aplikację, musisz użyć platformy Storage Access Framework. Aby się uczyć więcej informacji na temat korzystania z tej platformy zawiera artykuł Uzyskiwanie dostępu do dokumentów i innych plików pamięci współdzielonej.

Dostęp do lokalizacji multimediów

Jeśli aplikacja jest kierowana na Androida 10 (poziom interfejsu API 29) lub nowszego i wymaga aby pobrać nieusunięte metadane EXIF ze zdjęć, musisz zadeklarować parametr ACCESS_MEDIA_LOCATION uprawnienia w pliku manifestu aplikacji, a następnie poproś o nie w czasie działania aplikacji.

Sprawdź dostępność aktualizacji magazynu multimediów

Aby uzyskać bardziej niezawodny dostęp do plików multimedialnych, zwłaszcza jeśli aplikacja przechowuje identyfikatory URI lub danych z magazynu multimediów, sprawdź, czy wersja tego magazynu uległa zmianie w porównaniu z ostatnią synchronizacją danych multimedialnych. Aby przeprowadzić tę kontrolę dla: aktualizacje, połączenie getVersion() Zwracana wersja to unikalny ciąg znaków, który zmienia się za każdym razem, gdy magazyn multimediów znacząco się zmieni. Jeśli zwrócona wersja różni się od ostatnio zsynchronizowanej wersji, przeskanuj i zsynchronizuj ponownie pamięć podręczną.

Przeprowadź to sprawdzenie podczas uruchamiania procesu aplikacji. Nie musisz sprawdzać wersji za każdym razem, gdy wysyłasz zapytanie do magazynu multimediów.

Nie zakładaj żadnych szczegółów implementacji dotyczących numeru wersji.

Tworzenie zapytania do kolekcji multimediów

Aby znaleźć media, które spełniają określony zestaw warunków, np. mają czas trwania co najmniej 5 minut, użyj instrukcji wyboru podobnej do instrukcji SQL, takiej jak ta:

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

Podczas wykonywania takiego zapytania w aplikacji pamiętaj o tych kwestiach:

  • Wywołaj metodę query() w wątku instancji roboczej.
  • Umieść indeksy kolumn w pamięci podręcznej, aby nie trzeba było ich wywoływać getColumnIndexOrThrow() za każdym razem, gdy przetwarzasz wiersz z wyniku zapytania.
  • Dołącz identyfikator do identyfikatora URI treści, jak w tym przykładzie.
  • Urządzenia z Androidem 10 lub nowszym wymagają kolumny nazwy zdefiniowane w za pomocą interfejsu API MediaStore. Jeśli biblioteka zależna w aplikacji oczekuje kolumny która jest niezdefiniowana w interfejsie API, na przykład "MimeType", użyj CursorWrapper na dynamiczne przetłumacz nazwę kolumny w procesie aplikacji.

Wczytywanie miniatur plików

Jeśli aplikacja wyświetla wiele plików multimedialnych i prosi użytkownika o wybranie jednego z nich tych plików, lepiej będzie załadować podgląd. wersji – czyli miniatur – plików, a nie samych plików.

Aby wczytać miniaturę danego pliku multimedialnego, użyj funkcji loadThumbnail() i podaj rozmiar miniatury, którą chcesz wczytać, zgodnie z ten fragment kodu:

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);

Otwieranie pliku multimedialnego

Sposób otwierania pliku multimedialnego zależy od tego, czy treść multimedialną najlepiej jest przedstawić jako deskryptor pliku, strumień plików bezpośrednią ścieżkę pliku.

Deskryptor pliku

Aby otworzyć plik multimedialny za pomocą deskryptora pliku, wykonaj logikę podobną do przedstawionej w ten fragment kodu:

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();
}

Strumień plików

Aby otworzyć plik multimedialny za pomocą strumienia plików, użyj funkcji logicznej podobnej do przedstawionej w ten fragment kodu:

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

Bezpośrednie ścieżki do plików

Aby ułatwić płynne działanie aplikacji z bibliotekami multimediów innych firm, Android 11 (poziom interfejsu API 30) i nowsze umożliwiają korzystanie z interfejsów API innych niż MediaStore do uzyskiwania dostępu do plików multimedialnych z pamięci współdzielonej. Zamiast tego możesz uzyskać bezpośredni dostęp do plików multimedialnych przy użyciu jednego z tych interfejsów API:

  • Interfejs API File
  • Biblioteki natywne, takie jak fopen()

Jeśli nie masz uprawnień związanych z przechowywaniem, możesz uzyskać dostęp do plików w katalogu aplikacji oraz plików multimedialnych przypisanych do aplikacji za pomocą interfejsu API File.

Jeśli aplikacja próbuje uzyskać dostęp do pliku za pomocą interfejsu API File i nie ma niezbędnych uprawnień, FileNotFoundException.

Aby uzyskać dostęp do innych plików w pamięci współdzielonej na urządzeniu z Androidem 10 (interfejs API) poziomu 29), zalecamy tymczasowe wyłączenie miejsca na dane requestLegacyExternalStorage. do true w pliku manifestu aplikacji. Aby uzyskać dostęp do plików multimedialnych za pomocą natywnych metod plików na Androidzie 10, musisz też zażądać parametru READ_EXTERNAL_STORAGE uprawnienia.

Na co zwrócić uwagę podczas uzyskiwania dostępu do treści multimedialnych

Podczas uzyskiwania dostępu do treści multimedialnych pamiętaj o kwestii opisanych w dokumencie poniższych sekcji.

Dane w pamięci podręcznej

Jeśli aplikacja zapisuje w pamięci podręcznej identyfikatory URI lub dane z magazynu multimediów, okresowo sprawdzaj, czy nie ma w sklepie multimedialnym. Ta weryfikacja umożliwia dane po stronie aplikacji w pamięci podręcznej są synchronizowane z danymi dostawcy po stronie systemu.

Wydajność

Podczas sekwencyjnego odczytu plików multimedialnych przy użyciu bezpośrednich ścieżek do plików skuteczność reklam jest porównywalna z wynikami MediaStore API.

Gdy wykonujesz losowe odczyty i zapisy plików multimedialnych przy użyciu bezpośrednich ścieżek, ten proces może jednak trwać nawet dwa razy dłużej. W takich sytuacjach zalecamy użycie interfejsu API MediaStore.

Kolumna DATA

Gdy otworzysz istniejący plik multimedialny, możesz użyć wartości atrybutu DATA kolumna w to Twoja logika. Wynika to z faktu, że ta wartość ma prawidłową ścieżkę pliku. Nie zakładaj jednak, że plik jest zawsze dostępny. Przygotowanie się na każde rozwiązanie błędów wejścia-wyjścia.

Aby utworzyć lub zaktualizować plik multimedialny, nie używaj wartości atrybutu DATA. Zamiast tego użyj wartości atrybutu DISPLAY_NAME oraz RELATIVE_PATH kolumny.

Woluminy miejsca na dane

Aplikacje kierowane na Androida 10 lub nowszego mają dostęp do unikalnej nazwy które system przypisze do każdego woluminu pamięci zewnętrznej. Ten system nazw pomaga sprawnie porządkować i indeksować treści oraz daje kontrolę nad miejsce przechowywania nowych plików multimedialnych.

Szczególnie warto pamiętać o tych tomach:

  • Objęt VOLUME_EXTERNALpokazuje wszystkie objętości współdzielonego miejsca na dane na urządzeniu. Możesz przeczytać zawartość tego woluminu syntetycznego, ale nie możesz jej modyfikować.
  • VOLUME_EXTERNAL_PRIMARY reprezentuje główną pamięć współdzieloną na urządzeniu. Dostępne opcje do odczytywania i modyfikowania zawartości tego woluminu.

Możesz odkryć inne tomy, dzwoniąc pod numer MediaStore.getExternalVolumeNames():

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();

Miejsce, w którym zarejestrowano multimedia

Niektóre zdjęcia i filmy zawierają w metadanych informacje o lokalizacji. które pokazuje miejsce, w którym zrobiono zdjęcie lub gdzie został nagrany film. zostały nagrane.

Sposób uzyskiwania dostępu do tych informacji o lokalizacji w aplikacji zależy od tego, czy muszą uzyskać dostęp do informacji o lokalizacji zdjęcia lub filmu.

Fotografie

Jeśli aplikacja korzysta z ograniczonego miejsca na dane, system domyślnie ukrywa informacje o lokalizacji. Aby uzyskać dostęp do tych informacji, wykonaj te czynności:

  1. Poproś o ACCESS_MEDIA_LOCATION w pliku manifestu aplikacji.
  2. Aby uzyskać dokładną liczbę bajtów zdjęcia na podstawie obiektu MediaStore, połączenia setRequireOriginal(). i podając identyfikator URI zdjęcia, tak jak w poniższym fragmencie kodu:

    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];
    }
    

Filmy

Aby uzyskać dostęp do informacji o lokalizacji w metadanych filmu, użyj MediaMetadataRetriever zgodnie z poniższym fragmentem kodu. Aby korzystać z tej klasy, aplikacja nie musi prosić o żadne dodatkowe uprawnienia.

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

Udostępnianie

Niektóre aplikacje umożliwiają użytkownikom udostępnianie sobie plików multimedialnych. Na przykład społecznościowe aplikacje do multimediów umożliwiają użytkownikom udostępnianie zdjęć i filmów znajomym.

Do udostępniania plików multimedialnych używaj identyfikatora URI obiektu content:// zgodnie z zaleceniami w przewodniku tworząc dostawcę treści.

Atrybucja aplikacji plików multimedialnych

Gdy ograniczone miejsce na dane jest włączone dla aplikacji kierowanej na Androida 10 lub nowszego system przypisuje atrybut do każdego pliku multimedialnego, co określa, do jakich plików aplikacja ma dostęp nie zażądał żadnych uprawnień do przechowywania danych. Każdy plik można przypisać tylko jedną aplikację. Jeśli aplikacja utworzy plik multimedialny przechowywany w aplikacji do gromadzenia zdjęć, filmów i plików audio, aplikacja ma dostęp do .

Jeśli jednak użytkownik odinstaluje i ponownie zainstaluje Twoją aplikację, musisz o to poprosić READ_EXTERNAL_STORAGE aby uzyskać dostęp do plików utworzonych przez Ciebie w aplikacji. To żądanie uprawnień jest wymagane, ponieważ system uważa, że plik jest przypisany do wcześniej zainstalowanej wersji aplikacji, a nie do nowo zainstalowanej.

Dodawanie elementu

Aby dodać element multimedialny do istniejącej kolekcji, użyj kodu podobnego do następujących po sobie. Ten fragment kodu uzyskuje dostęp do woluminu VOLUME_EXTERNAL_PRIMARY na urządzeniach z Androidem 10 lub nowszym. To dlatego, że na tych urządzeniach treści można modyfikować tylko wtedy, gdy jest to wolumin główny, opisane w sekcji Woluminy miejsca na dane.

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);

Przełącz stan oczekiwania na plik multimedialny

Jeśli aplikacja wykonuje potencjalnie czasochłonne operacje, np. pisze plików multimedialnych, warto mieć wyłączny dostęp do pliku przetworzono. Na urządzeniach z Androidem 10 lub nowszym aplikacja może: aby uzyskać dostęp do tego wyjątkowego dostępu, ustaw wartość atrybutu IS_PENDING flagę na 1. Tylko Twoja aplikacja może wyświetlać plik, dopóki aplikacja nie zmieni wartości atrybutu IS_PENDING wróci do 0.

Poniższy fragment kodu jest oparty na poprzednim fragmencie. Ten pokazuje, jak używać flagi IS_PENDING przy przechowywaniu długiego utworu w katalog odpowiadający kolekcji MediaStore.Audio:

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);

Podpowiedź dotycząca lokalizacji pliku

Gdy Twoja aplikacja przechowuje multimedia na urządzeniu z Androidem 10, Domyślnie multimedia są uporządkowane według typu. Na przykład domyślnie nowy plików graficznych są umieszczane w Environment.DIRECTORY_PICTURES który odpowiada Kolekcja MediaStore.Images.

Jeśli aplikacja ma informacje o konkretnej lokalizacji, w której mogą być przechowywane pliki, na przykład jako album zdjęć o nazwie Pictures/MyVacationPictures, możesz ustawić MediaColumns.RELATIVE_PATH. aby udostępnić systemowi wskazówkę dotyczącą miejsca przechowywania nowo zapisanych plików.

Aktualizowanie elementu

Aby zaktualizować plik multimedialny należący do aplikacji, użyj kodu podobnego do tego:

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);

Jeśli pamięć o ograniczonym zakresie jest niedostępna lub nie jest włączona, proces pokazany w poprzedni fragment kodu działa również w przypadku plików, które nie należą do Twojej aplikacji.

Zaktualizuj w kodzie natywnym

Jeśli chcesz zapisywać pliki multimedialne przy użyciu bibliotek natywnych, przekaż w parametrze z deskryptora pliku z kodu opartego na Javie lub Kotlinie kodu natywnego.

Fragment kodu poniżej pokazuje, jak przekazać deskryptor pliku obiektu multimedialnego do kodu natywnego aplikacji:

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

Aktualizuj w innych aplikacjach pliki multimedialne

Jeśli aplikacja wykorzystuje ograniczone miejsce na dane, zazwyczaj nie jest w stanie zaktualizować pliku multimedialnego przesłanego przez inną aplikację i sklepie z multimediami.

Możesz jednak uzyskać zgodę użytkownika na modyfikowanie pliku, przechwytując RecoverableSecurityException które rzuca platforma. Następnie możesz poprosić użytkownika o przyznanie aplikacji. uprawnienia do zapisu tego konkretnego elementu, tak jak pokazano to w tym fragmencie kodu:

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

Wykonuj tę procedurę za każdym razem, gdy aplikacja musi zmodyfikować plik multimedialny, który nie utworzył.

Jeśli Twoja aplikacja działa na Androidzie 11 lub nowszym, zezwolić użytkownikom na przyznawanie aplikacji uprawnień do zapisu w grupie plików multimedialnych. Użyj createWriteRequest() zgodnie z opisem w sekcji dotyczącej zarządzania grupami mediów .

Jeśli Twoja aplikacja ma inny przypadek użycia, który nie jest objęty zakresem pamięci masowej, prześlij prośbę o dodanie funkcji oraz tymczasowo zrezygnuj z zakresu

Usuwanie elementów

Aby usunąć produkt, którego aplikacja nie potrzebuje już w sklepie multimedialnym, użyj funkcji logicznej podobny do pokazanego w tym fragmencie kodu:

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);

Jeśli miejsce na dane o ograniczonym zakresie jest niedostępne lub nie jest włączone, możesz użyć poprzedzającego fragmentu kodu do usuwania plików należących do innych aplikacji. Jeśli włączone jest ograniczone miejsce na dane, jednak musisz wychwytywać RecoverableSecurityException dla każdego pliku, który usunąć aplikację, zgodnie z opisem w sekcji aktualizacji multimediów elementy.

Jeśli Twoja aplikacja działa na Androidzie 11 lub nowszym, możesz zezwolić użytkownikom na wybranie grupy plików multimedialnych do usunięcia. Korzystanie z createTrashRequest() lub createDeleteRequest() zgodnie z opisem w sekcji zarządzania grupami mediów .

Jeśli Twoja aplikacja ma inny przypadek użycia, który nie jest objęty zakresem pamięci masowej, prześlij prośbę o dodanie funkcji oraz tymczasowo zrezygnuj z zakresu

Wykrywanie aktualizacji plików multimedialnych

Aplikacja może wymagać określenia woluminów pamięci zawierających pliki multimedialne, które aplikacje dodane lub zmodyfikowane w porównaniu z poprzednim punktem w czasie. Aby wykrywać te zmiany w najbardziej niezawodny sposób, przekaż interesujący Cię wolumen miejsca na dane do funkcji getGeneration(). Dopóki wersja magazynu multimediów się nie zmieni, wartość zwracana przez tę metodę będzie monotonicznie wzrastać z czasem.

Kolumna getGeneration() działa lepiej niż daty w kolumnach multimediów, na przykład DATE_ADDED. i DATE_MODIFIED. Dzieje się tak, ponieważ wartości w kolumnach z multimediami mogą się zmieniać, gdy aplikacja wywołuje funkcję setLastModified() lub gdy użytkownik zmienia zegar systemowy.

Zarządzanie grupami plików multimedialnych

W Androidzie 11 i nowszych możesz poprosić użytkownika o wybranie grupy plików multimedialnych, a potem zaktualizować te pliki w jednej operacji. Te metody zapewniają większą spójność między urządzeniami, a metody ułatwiają aby użytkownicy mogli zarządzać swoimi kolekcjami multimediów.

Metody, które zapewniają tę funkcję „zbiorcze aktualizacji”, to:

createWriteRequest()
Poproś użytkownika o przyznanie aplikacji dostępu do zapisu w określonej grupie plików multimedialnych.
createFavoriteRequest()
Poproś użytkownika o oznaczenie wybranych plików multimedialnych jako „ulubionych” na urządzeniu. Każda aplikacja z dostępem do odczytu tego pliku może zobaczyć, że użytkownik oznaczył go jako „ulubiony”.
createTrashRequest()

Poproś użytkownika o umieszczenie określonych plików multimedialnych w koszu urządzenia. Elementy w koszu są trwale usuwane po czasie określonym przez system okresu przejściowego.

createDeleteRequest()

Poproś użytkownika o trwałe usunięcie określonych plików multimedialnych bez konieczności umieszczania ich w koszu.

Po wywołaniu dowolnej z tych metod system tworzy PendingIntent. Po aplikacji wywołuje tę intencję, użytkownicy widzą okno z prośbą o zgodę na wykorzystanie danych w przypadku Twojej aplikacji aby zaktualizować lub usunąć wybrane pliki multimedialne.

Tak na przykład wygląda struktura wywołania 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);

oceniać odpowiedź użytkownika, Jeśli użytkownik wyraził zgodę, kontynuuj operację związaną z mediami. W przeciwnym razie wyjaśnij użytkownikowi, dlaczego aplikacja wymaga atrybutu uprawnienie:

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

Tego samego ogólnego wzorca możesz używać w przypadku createFavoriteRequest() createTrashRequest(), oraz createDeleteRequest().

Uprawnienia do zarządzania multimediami

Użytkownicy mogą ufać konkretnej aplikacji, ponieważ może zarządzać multimediami, np. tworzyć częste edytowanie plików multimedialnych. Jeśli Twoja aplikacja jest kierowana Androida 11 lub nowszego i nie jest domyślną aplikacją galerii na urządzeniu; musisz pokazywać użytkownikowi okno potwierdzenia za każdym razem, gdy aplikacja modyfikować i usuwać pliki.

Jeśli Twoja aplikacja jest kierowana na Androida 12 (poziom interfejsu API 31) lub nowszego, możesz poprosić o to użytkownicy przyznają Twojej aplikacji specjalne uprawnienia do zarządzania multimediami. Ten umożliwia aplikacji wykonywanie tych czynności bez konieczności wyświetlania prośby użytkownika w przypadku każdej operacji pliku:

W tym celu wykonaj następujące czynności:

  1. Zadeklaruj parametr Uprawnienie MANAGE_MEDIA oraz READ_EXTERNAL_STORAGE uprawnienia w pliku manifestu aplikacji.

    Aby zadzwonić pod numer createWriteRequest() bez pokazywania potwierdzenia zadeklaruj parametr ACCESS_MEDIA_LOCATION. bez zgody użytkownika.

  2. Wyświetl w aplikacji interfejs użytkownika, aby wyjaśnić, dlaczego warto przyznać aplikacji uprawnienia do zarządzania multimediami.

  3. Wywołaj metodę ACTION_REQUEST_MANAGE_MEDIA działanie intencji. Użytkownicy zobaczą ekran Aplikacje do zarządzania multimediami w ustawieniach systemu. Tutaj użytkownicy mogą przyznać specjalne uprawnienia dostępu aplikacji.

Przypadki użycia, które wymagają alternatywy dla sklepu multimedialnego

Jeśli Twoja aplikacja pełni głównie jedną z poniższych ról, zastanów się nad: alternatywę dla interfejsów API MediaStore.

Praca z innymi typami plików

Jeśli aplikacja współpracuje z dokumentami i plikami, które nie zawierają wyłącznie multimediów np. plików z rozszerzeniem EPUB lub PDF, użyj funkcji Działanie intencji ACTION_OPEN_DOCUMENT, zgodnie z opisem w przewodniku po przechowywaniu danych oraz dostęp do dokumentów i innych .

Udostępnianie plików w aplikacjach towarzyszących

Jeśli udostępniasz zestaw aplikacji towarzyszących, takich jak aplikacja do obsługi wiadomości i aplikacja do obsługi profilu, skonfiguruj udostępnianie plików za pomocą adresów URI content://. Zalecamy też ten proces jako sprawdzoną metodę zapewniania bezpieczeństwa.

Dodatkowe materiały

Więcej informacji o przechowywaniu multimediów i uzyskiwaniu do nich dostępu znajdziesz w tych artykułach: i zasobami Google Cloud.

Próbki

Filmy