Aby zapewnić użytkownikom lepsze wrażenia, wiele aplikacji umożliwia im dodawanie multimediów dostępnych na zewnętrznej pamięci masowej i uzyskiwanie do nich dostępu. Platforma udostępnia zoptymalizowany indeks kolekcji multimediów, nazywany magazynem multimediów, który umożliwia użytkownikom pobieranie i aktualizowanie plików multimedialnych. Nawet po odinstalowaniu aplikacji pliki te pozostają na urządzeniu użytkownika.
Selektor zdjęć
Zamiast korzystać z magazynu multimediów, użytkownicy mogą używać selektora zdjęć na Androidzie, który jest bezpiecznym, wbudowanym narzędziem do wybierania plików multimedialnych bez konieczności przyznawania aplikacji dostępu do całej biblioteki multimediów. Ta funkcja jest dostępna tylko na obsługiwanych urządzeniach. Więcej informacji znajdziesz w przewodniku po selektorze zdjęć.
Sklep z multimediami
Aby wejść w interakcję z abstrakcją magazynu multimediów, użyj obiektu ContentResolver pobranego 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 tych dobrze zdefiniowanych kolekcji:
- obrazy,w tym zdjęcia i zrzuty ekranu, które są przechowywane w katalogach
DCIM/iPictures/; System dodaje te pliki do tabeliMediaStore.Images. - Filmy, które są przechowywane w katalogach
DCIM/,Movies/iPictures/. System dodaje te pliki do tabeliMediaStore.Video. - Pliki audio, które są przechowywane w katalogach
Alarms/,Audiobooks/,Music/,Notifications/,Podcasts/iRingtones/. Dodatkowo system rozpoznaje playlisty audio, które znajdują się w katalogachMusic/lubMovies/, oraz nagrania głosowe, które znajdują się w kataloguRecordings/. System dodaje te pliki do tabeliMediaStore.Audio. KatalogRecordings/nie jest dostępny na Androidzie 11 (poziom API 30) i starszych wersjach. - pobrane pliki,które są przechowywane w katalogu
Download/; Na urządzeniach z Androidem 10 (poziom interfejsu API 29) i nowszym te pliki są przechowywane w tabeliMediaStore.Downloads. Ta tabela nie jest dostępna na Androidzie 9 (API na poziomie 28) i starszych wersjach.
Magazyn multimediów zawiera też kolekcję o nazwie MediaStore.Files. Jego zawartość zależy od tego, czy aplikacja korzysta z wydzielonej pamięci, która jest dostępna w aplikacjach kierowanych na Androida 10 lub nowszego.
- Jeśli ograniczony dostęp do miejsca na dane jest włączony, kolekcja zawiera tylko zdjęcia, filmy i pliki audio utworzone przez aplikację. Większość deweloperów nie musi używać uprawnienia
MediaStore.Files, aby wyświetlać pliki multimedialne z innych aplikacji. Jeśli jednak masz takie wymaganie, możesz zadeklarować uprawnienieREAD_EXTERNAL_STORAGE. Zalecamy jednak używanie interfejsów APIMediaStoredo otwierania plików, które nie zostały utworzone przez Twoją aplikację. - Jeśli ograniczony dostęp do miejsca na dane jest niedostępny lub nie jest używany, kolekcja zawiera wszystkie typy plików multimedialnych.
Poproś o niezbędne uprawnienia.
Zanim wykonasz operacje na plikach multimedialnych, upewnij się, że aplikacja ma zadeklarowane uprawnienia, których potrzebuje do uzyskania dostępu do tych plików. Pamiętaj jednak, aby nie deklarować uprawnień, których aplikacja nie potrzebuje ani nie używa.
Uprawnienia do Cloud Storage
To, czy aplikacja potrzebuje uprawnień dostępu do pamięci, zależy od tego, czy uzyskuje 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 potrzebujesz uprawnień związanych z pamięcią, aby uzyskiwać dostęp do plików multimedialnych, które należą do Twojej aplikacji, i je modyfikować. Dotyczy to też plików w kolekcji MediaStore.Downloads. Jeśli na przykład tworzysz aplikację do obsługi aparatu, nie musisz prosić o uprawnienia związane z pamięcią, aby uzyskać dostęp do zrobionych zdjęć, ponieważ aplikacja jest właścicielem obrazów, które zapisujesz w magazynie multimediów.
Dostęp do plików multimedialnych innych aplikacji
Aby uzyskać dostęp do plików multimedialnych utworzonych przez inne aplikacje, musisz zadeklarować odpowiednie uprawnienia związane z pamięcią, a pliki muszą znajdować się w jednej z tych kolekcji multimediów:
Jeśli plik jest widoczny w zapytaniach MediaStore.Images, MediaStore.Video lub MediaStore.Audio, jest też widoczny w zapytaniu MediaStore.Files.
Poniższy fragment kodu pokazuje, jak zadeklarować odpowiednie uprawnienia do pamięci:
<!-- 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" />
Dodatkowe uprawnienia wymagane w przypadku aplikacji działających na starszych urządzeniach
Jeśli aplikacja jest używana na urządzeniu z Androidem 9 lub starszym albo jeśli tymczasowo zrezygnowano w niej z wydzielonej pamięci, musisz poprosić o uprawnienie READ_EXTERNAL_STORAGE, aby uzyskać dostęp do dowolnego pliku multimedialnego. Jeśli chcesz modyfikować pliki multimedialne, musisz też poprosić o uprawnienie WRITE_EXTERNAL_STORAGE.
Do uzyskania dostępu do plików pobranych przez inne aplikacje wymagany jest Storage Access Framework
Jeśli aplikacja chce uzyskać dostęp do pliku w kolekcji MediaStore.Downloads, który nie został przez nią utworzony, musisz użyć platformy Storage Access Framework. Więcej informacji o korzystaniu z tej platformy znajdziesz w artykule Uzyskiwanie dostępu do dokumentów i innych plików z pamięci współdzielonej.
Dostęp do lokalizacji multimediów
Jeśli Twoja aplikacja jest kierowana na Androida 10 (API na poziomie 29) lub nowszego i musi pobierać nieocenzurowane metadane EXIF ze zdjęć, musisz zadeklarować uprawnienie ACCESS_MEDIA_LOCATION w pliku manifestu aplikacji, a następnie poprosić o to uprawnienie w czasie działania.
Sprawdzanie dostępności aktualizacji magazynu multimediów
Aby uzyskać bardziej niezawodny dostęp do plików multimedialnych, zwłaszcza jeśli aplikacja przechowuje w pamięci podręcznej identyfikatory URI lub dane z magazynu multimediów, sprawdź, czy wersja magazynu multimediów zmieniła się od czasu ostatniej synchronizacji danych multimedialnych. Aby sprawdzić dostępność aktualizacji, wywołaj funkcję getVersion().
Zwracana wersja to unikalny ciąg znaków, który zmienia się, gdy magazyn multimediów ulega znacznej zmianie. Jeśli zwrócona wersja różni się od ostatniej zsynchronizowanej wersji, ponownie przeskanuj i zsynchronizuj pamięć podręczną multimediów aplikacji.
Przeprowadź tę kontrolę 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.
Wykonywanie zapytania dotyczącego kolekcji multimediów
Aby znaleźć multimedia, które spełniają określony zestaw warunków, np. trwają co najmniej 5 minut, użyj instrukcji wyboru podobnej do SQL, takiej jak ta pokazana w tym fragmencie kodu:
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. - Zapisz indeksy kolumn w pamięci podręcznej, aby nie trzeba było wywoływać metody
getColumnIndexOrThrow()za każdym razem, gdy przetwarzasz wiersz z wyniku zapytania. - Dołącz identyfikator do identyfikatora URI treści, jak pokazano w tym przykładzie.
- Urządzenia z Androidem 10 lub nowszym wymagają nazw kolumn zdefiniowanych w interfejsie API
MediaStore. Jeśli zależna biblioteka w aplikacji oczekuje nazwy kolumny, która nie jest zdefiniowana w interfejsie API, np."MimeType", użyjCursorWrapper, aby dynamicznie przetłumaczyć 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, wydajniej jest wczytywać wersje testowe plików (miniatury) zamiast samych plików.
Aby wczytać miniaturę danego pliku multimedialnego, użyj metody loadThumbnail() i podaj rozmiar miniatury, którą chcesz wczytać, jak pokazano w tym fragmencie 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
Konkretna logika używana do otwierania pliku multimedialnego zależy od tego, czy treść multimedialna jest najlepiej reprezentowana jako deskryptor pliku, strumień pliku czy bezpośrednia ścieżka do pliku.
Deskryptor pliku
Aby otworzyć plik multimedialny za pomocą deskryptora pliku, użyj logiki podobnej do tej pokazanej w tym fragmencie 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 logiki podobnej do tej pokazanej w tym fragmencie 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 plików
Aby ułatwić współpracę aplikacji z bibliotekami multimediów innych firm, Android 11 (poziom API 30) i nowsze wersje umożliwiają korzystanie z interfejsów API innych niż MediaStore w celu uzyskiwania dostępu do plików multimedialnych z pamięci współdzielonej. Zamiast tego możesz uzyskać dostęp do plików multimedialnych bezpośrednio za pomocą jednego z tych interfejsów API:
FileAPI- Biblioteki natywne, takie jak
fopen()
Jeśli nie masz żadnych uprawnień związanych z pamięcią, możesz uzyskać dostęp do plików w katalogu aplikacji oraz do 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ń, występuje błąd FileNotFoundException.
Aby uzyskać dostęp do innych plików w pamięci współdzielonej na urządzeniu z Androidem 10 (poziom interfejsu API 29), zalecamy tymczasowe wyłączenie ograniczonego dostępu do miejsca na dane przez ustawienie requestLegacyExternalStorage na true w pliku manifestu aplikacji. Aby uzyskać dostęp do plików multimedialnych za pomocą metod plików natywnych na Androidzie 10, musisz też poprosić o uprawnienie READ_EXTERNAL_STORAGE.
Co należy wziąć pod uwagę podczas uzyskiwania dostępu do treści multimedialnych
Podczas uzyskiwania dostępu do treści multimedialnych pamiętaj o kwestiach omówionych w kolejnych sekcjach.
Dane w pamięci podręcznej
Jeśli aplikacja przechowuje w pamięci podręcznej identyfikatory URI lub dane z magazynu multimediów, okresowo sprawdzaj, czy są dostępne aktualizacje magazynu multimediów. Dzięki temu dane zapisane w pamięci podręcznej po stronie aplikacji są synchronizowane z danymi dostawcy po stronie systemu.
Wydajność
Gdy wykonujesz sekwencyjne odczyty plików multimedialnych przy użyciu bezpośrednich ścieżek do plików, wydajność jest porównywalna z wydajnością interfejsu API MediaStore.
Jeśli jednak wykonujesz losowe odczyty i zapisy plików multimedialnych przy użyciu bezpośrednich ścieżek plików, proces może być nawet 2 razy wolniejszy. W takich sytuacjach zalecamy użycie interfejsu API MediaStore.
Kolumna DATA
Gdy uzyskujesz dostęp do istniejącego pliku multimedialnego, możesz użyć w logice wartości z kolumny DATA. Dzieje się tak, ponieważ ta wartość ma prawidłową ścieżkę do pliku. Nie zakładaj jednak, że plik jest zawsze dostępny. Przygotuj się na obsługę błędów wejścia/wyjścia związanych z plikami.
Aby utworzyć lub zaktualizować plik multimedialny, nie używaj wartości z kolumny DATA. Zamiast tego użyj wartości z kolumn DISPLAY_NAME i RELATIVE_PATH.
Woluminy miejsca na dane
Aplikacje kierowane na Androida 10 lub nowszego mają dostęp do unikalnej nazwy, którą system przypisuje do każdego woluminu pamięci zewnętrznej. Ten system nazewnictwa pomaga efektywnie porządkować i indeksować treści oraz zapewnia kontrolę nad miejscem przechowywania nowych plików multimedialnych.
Warto pamiętać o tych woluminach:
- Wolumin
VOLUME_EXTERNALzapewnia widok wszystkich woluminów pamięci współdzielonej na urządzeniu. Możesz odczytać zawartość tego woluminu syntetycznego, ale nie możesz jej modyfikować. - Wolumin
VOLUME_EXTERNAL_PRIMARYreprezentuje główny wolumin pamięci współdzielonej na urządzeniu. Możesz odczytywać i modyfikować zawartość tego woluminu.
Inne woluminy możesz znaleźć, wywołując 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();
Lokalizacja, w której zostały zarejestrowane multimedia
Niektóre zdjęcia i filmy zawierają w metadanych informacje o lokalizacji, które wskazują miejsce, w którym zdjęcie zostało zrobione lub film został nagrany.
Sposób uzyskiwania dostępu do tych informacji o lokalizacji w aplikacji zależy od tego, czy potrzebujesz dostępu do informacji o lokalizacji zdjęcia czy filmu.
Fotografie
Jeśli aplikacja korzysta z ograniczonego dostępu do miejsca na dane, system domyślnie ukrywa informacje o lokalizacji. Aby uzyskać dostęp do tych informacji, wykonaj te czynności:
- Poproś o uprawnienie
ACCESS_MEDIA_LOCATIONw pliku manifestu aplikacji. Z obiektu
MediaStorepobierz dokładne bajty zdjęcia, wywołując metodęsetRequireOriginal()i przekazując identyfikator URI zdjęcia, jak pokazano w tym 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 klasy MediaMetadataRetriever, jak pokazano w tym fragmencie kodu. Aby korzystać z tej klasy, aplikacja nie musi prosić o 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 nawzajem plików multimedialnych. Na przykład aplikacje społecznościowe umożliwiają użytkownikom udostępnianie zdjęć i filmów znajomym.
Aby udostępniać pliki multimedialne, użyj identyfikatora URI content:// zgodnie z zaleceniami w przewodniku po tworzeniu mechanizmu dostarczania treści.
Atrybucja aplikacji w przypadku plików multimedialnych
Gdy ograniczony dostęp do miejsca na dane jest włączony w aplikacji kierowanej na Androida 10 lub nowszego, system przypisuje aplikację do każdego pliku multimedialnego, co określa, do których plików aplikacja może uzyskać dostęp, gdy nie poprosiła o żadne uprawnienia do miejsca na dane. Każdy plik może być przypisany tylko do jednej aplikacji. Jeśli więc aplikacja utworzy plik multimedialny, który zostanie zapisany w kolekcji zdjęć, filmów lub plików audio, będzie miała do niego dostęp.
Jeśli jednak użytkownik odinstaluje i ponownie zainstaluje aplikację, musisz poprosić o uprawnienie READ_EXTERNAL_STORAGE, aby uzyskać dostęp do plików, które zostały utworzone przez aplikację. Ta prośba o uprawnienia jest wymagana, ponieważ system uważa, że plik jest przypisany do wcześniej zainstalowanej wersji aplikacji, a nie do nowo zainstalowanej.
Gdy aplikacja kierowana na pakiet SDK 36 lub nowszy na urządzeniach z Androidem 16 lub nowszym poprosi o uprawnienia do zdjęć i filmów, użytkownicy, którzy zdecydują się ograniczyć dostęp do wybranych multimediów, zobaczą w selektorze zdjęć wszystkie zdjęcia należące do aplikacji. Użytkownicy mogą odznaczyć dowolne z tych wstępnie wybranych elementów, co spowoduje cofnięcie dostępu aplikacji do tych zdjęć i filmów.
Dodaj element
Aby dodać element multimedialny do istniejącej kolekcji, użyj kodu podobnego do tego: Ten fragment kodu uzyskuje dostęp do woluminu VOLUME_EXTERNAL_PRIMARY na urządzeniach z Androidem 10 lub nowszym. Dzieje się tak, ponieważ na tych urządzeniach możesz modyfikować zawartość woluminu tylko wtedy, gdy jest to wolumin podstawowy, jak opisano w sekcji Woluminy pamięci masowej.
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łączanie stanu oczekiwania plików multimedialnych
Jeśli aplikacja wykonuje potencjalnie czasochłonne operacje, takie jak zapisywanie plików multimedialnych, warto mieć wyłączny dostęp do pliku podczas jego przetwarzania. Na urządzeniach z Androidem 10 lub nowszym aplikacja może uzyskać wyłączny dostęp, ustawiając wartość flagi IS_PENDING na 1. Tylko Twoja aplikacja może wyświetlać plik, dopóki nie zmieni wartości IS_PENDING z powrotem na 0.
Poniższy fragment kodu bazuje na poprzednim fragmencie kodu. Ten fragment kodu pokazuje, jak używać flagi IS_PENDING podczas przechowywania długiego utworu w katalogu odpowiadającym 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);
Podaj wskazówkę dotyczącą lokalizacji pliku
Gdy aplikacja przechowuje multimedia na urządzeniu z Androidem 10, są one domyślnie porządkowane według typu. Na przykład domyślnie nowe pliki graficzne są umieszczane w katalogu Environment.DIRECTORY_PICTURES, który odpowiada kolekcji MediaStore.Images.
Jeśli aplikacja zna konkretną lokalizację, w której można przechowywać pliki, np. album ze zdjęciami o nazwie Pictures/MyVacationPictures, możesz ustawić MediaColumns.RELATIVE_PATH, aby podać systemowi wskazówkę, gdzie przechowywać nowo zapisane pliki.
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 ograniczony dostęp do miejsca na dane jest niedostępny lub niewłączony, proces pokazany w poprzednim fragmencie kodu działa też w przypadku plików, które nie należą do Twojej aplikacji.
Aktualizacja w kodzie natywnym
Jeśli chcesz zapisywać pliki multimedialne za pomocą bibliotek natywnych, przekaż powiązany z plikiem deskryptor pliku z kodu opartego na języku Java lub Kotlin do kodu natywnego.
Poniższy fragment kodu 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. }
Aktualizowanie plików multimedialnych innych aplikacji
Jeśli aplikacja korzysta z ograniczonego dostępu do miejsca na dane, zwykle nie może aktualizować pliku multimedialnego, który inna aplikacja dodała do magazynu multimediów.
Możesz jednak uzyskać zgodę użytkownika na modyfikowanie pliku, przechwytując wyjątek RecoverableSecurityException zgłaszany przez platformę. Następnie możesz poprosić użytkownika o przyznanie aplikacji dostępu do zapisu do tego konkretnego elementu, jak pokazano 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); } }
Wykonaj ten proces za każdym razem, gdy aplikacja musi zmodyfikować plik multimedialny, który nie został przez nią utworzony.
Jeśli aplikacja działa na Androidzie 11 lub nowszym, możesz też pozwolić użytkownikom na przyznanie jej dostępu do zapisu w grupie plików multimedialnych. Użyj metody createWriteRequest() zgodnie z opisem w sekcji dotyczącej zarządzania grupami plików multimedialnych.
Jeśli Twoja aplikacja ma inny przypadek użycia, który nie jest objęty ograniczonym dostępem do miejsca na dane, prześlij prośbę o dodanie funkcji i tymczasowo zrezygnuj z ograniczonego dostępu do miejsca na dane.
Usuń element
Aby usunąć z magazynu multimediów element, którego aplikacja już nie potrzebuje, użyj logiki podobnej do tej pokazanej 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 ograniczony dostęp do miejsca na dane jest niedostępny lub nie jest włączony, możesz użyć poprzedniego fragmentu kodu, aby usunąć pliki należące do innych aplikacji. Jeśli jednak ograniczony dostęp do miejsca na dane jest włączony, musisz przechwycić wyjątek RecoverableSecurityException dla każdego pliku, który aplikacja chce usunąć, zgodnie z opisem w sekcji dotyczącej aktualizowania elementów multimedialnych.
Jeśli aplikacja działa na Androidzie 11 lub nowszym, możesz pozwolić użytkownikom na wybranie grupy plików multimedialnych do usunięcia. Użyj metody createTrashRequest() lub createDeleteRequest() zgodnie z opisem w sekcji poświęconej zarządzaniu grupami plików multimedialnych.
Jeśli Twoja aplikacja ma inny przypadek użycia, który nie jest objęty ograniczonym dostępem do miejsca na dane, prześlij prośbę o dodanie funkcji i tymczasowo zrezygnuj z ograniczonego dostępu do miejsca na dane.
Wykrywanie aktualizacji plików multimedialnych
Aplikacja może wymagać identyfikacji woluminów pamięci zawierających pliki multimedialne, które zostały dodane lub zmodyfikowane przez aplikacje w porównaniu z poprzednim momentem. Aby wykrywać te zmiany w najbardziej niezawodny sposób, przekaż interesujący Cię wolumin pamięci do getGeneration().
Dopóki wersja magazynu multimediów się nie zmieni, wartość zwracana przez tę metodę będzie monotonicznie rosnąć w czasie.
W szczególności getGeneration() jest bardziej niezawodny niż daty w kolumnach multimediów, takich jak DATE_ADDED i DATE_MODIFIED.
Dzieje się tak, ponieważ wartości w tych kolumnach multimediów mogą się zmienić, gdy aplikacja wywoła metodę setLastModified() lub gdy użytkownik zmieni zegar systemowy.
Zarządzanie grupami plików multimedialnych
Na Androidzie 11 i nowszych możesz poprosić użytkownika o wybranie grupy plików multimedialnych, a następnie zaktualizować te pliki w ramach jednej operacji. Te metody zapewniają większą spójność na różnych urządzeniach i ułatwiają użytkownikom zarządzanie kolekcjami multimediów.
Metody, które zapewniają tę funkcję „aktualizacji zbiorczej”, to m.in.:
createWriteRequest()- Poproś użytkownika o przyznanie aplikacji uprawnień do zapisu określonej grupy plików multimedialnych.
createFavoriteRequest()- Poproś użytkownika o oznaczenie określonych plików multimedialnych jako „ulubionych” na urządzeniu. Każda aplikacja, która ma dostęp 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. thought Elementy w koszu są trwale usuwane po upływie określonego przez system czasu.
createDeleteRequest()Poproś użytkownika o natychmiastowe trwałe usunięcie określonych plików multimedialnych bez wcześniejszego umieszczania ich w koszu.
Po wywołaniu dowolnej z tych metod system tworzy obiekt PendingIntent. Gdy aplikacja wywoła tę intencję, użytkownicy zobaczą okno z prośbą o wyrażenie zgody na aktualizowanie lub usuwanie określonych plików multimedialnych przez aplikację.
Oto przykład struktury 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);
Oceń odpowiedź użytkownika. Jeśli użytkownik wyraził zgodę, przejdź do operacji na multimediach. W przeciwnym razie wyjaśnij użytkownikowi, dlaczego aplikacja potrzebuje uprawnień:
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. */ } } }
Możesz użyć tego samego ogólnego wzorca w przypadku createFavoriteRequest(), createTrashRequest() i createDeleteRequest().
Uprawnienia do zarządzania multimediami
Użytkownicy mogą ufać konkretnej aplikacji w zakresie zarządzania multimediami, np. częstego edytowania plików multimedialnych. Jeśli aplikacja jest kierowana na Androida 11 lub nowszego i nie jest domyślną aplikacją galerii na urządzeniu, musisz wyświetlić użytkownikowi okno potwierdzenia za każdym razem, gdy aplikacja próbuje zmodyfikować lub usunąć plik.
Jeśli Twoja aplikacja jest kierowana na Androida 12 (poziom API 31) lub nowszego, możesz poprosić użytkowników o przyznanie jej specjalnego uprawnienia do zarządzania multimediami. To uprawnienie umożliwia aplikacji wykonywanie tych czynności bez konieczności proszenia użytkownika o potwierdzenie każdej operacji na plikach:
- Modyfikowanie plików za pomocą
createWriteRequest(). - Przenoszenie plików do kosza i poza niego za pomocą
createTrashRequest(). - Usuń pliki za pomocą
createDeleteRequest().
Aby to zrobić, wykonaj następujące czynności:
Zadeklaruj uprawnienia
MANAGE_MEDIAiREAD_EXTERNAL_STORAGEw pliku manifestu aplikacji.Aby wywołać
createWriteRequest()bez wyświetlania okna potwierdzenia, zadeklaruj też uprawnienieACCESS_MEDIA_LOCATION.W aplikacji wyświetl użytkownikowi interfejs z wyjaśnieniem, dlaczego warto przyznać aplikacji dostęp do zarządzania multimediami.
Wywołaj działanie intencji
ACTION_REQUEST_MANAGE_MEDIA. Spowoduje to przejście do ekranu Aplikacje do zarządzania multimediami w ustawieniach systemu. Z tego miejsca użytkownicy mogą przyznać specjalny dostęp do aplikacji.
Przypadki użycia, które wymagają alternatywy dla magazynu multimediów
Jeśli Twoja aplikacja pełni głównie jedną z tych ról, rozważ alternatywę dla interfejsów API MediaStore.
Praca z innymi typami plików
Jeśli aplikacja działa z dokumentami i plikami, które nie zawierają wyłącznie treści multimedialnych, np. z plikami z rozszerzeniem EPUB lub PDF, użyj działania intencji ACTION_OPEN_DOCUMENT zgodnie z opisem w przewodniku na temat przechowywania dokumentów i innych plików oraz uzyskiwania do nich dostępu.
Udostępnianie plików w aplikacjach towarzyszących
Jeśli udostępniasz pakiet aplikacji towarzyszących, np. aplikację do obsługi wiadomości i aplikację do zarządzania profilem, skonfiguruj udostępnianie plików za pomocą identyfikatorów URI content://. Zalecamy też ten proces jako sprawdzoną metodę dotyczącą bezpieczeństwa.
Dodatkowe materiały
Więcej informacji o przechowywaniu multimediów i uzyskiwaniu do nich dostępu znajdziesz w tych materiałach.
Przykłady
- MediaStore, dostępny na GitHubie