Aby zapewnić użytkownikom lepsze wrażenia, wiele aplikacji umożliwia im przesyłanie i wyświetlanie multimediów dostępnych na zewnętrznym woluminie pamięci masowej. Framework udostępnia zoptymalizowany indeks kolekcji multimediów, zwany magazynem multimediów, który ułatwia użytkownikom pobieranie i aktualizowanie tych plików. Nawet po odinstalowaniu aplikacji pliki te pozostają na urządzeniu użytkownika.
Selektor zdjęć
Narzędzie selektora zdjęć na Androidzie to bezpieczny, wbudowany sposób na wybieranie 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ęć.
Magazyn multimediów
Aby korzystać z abstrakcji sklepu z multimediami, użyj obiektu ContentResolver
, który pobierzesz 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 zewnętrzny wolumin pamięci 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 doda te pliki do tabeliMediaStore.Images
. - Filmy, które są przechowywane w katalogach
DCIM/
,Movies/
iPictures/
. System doda te pliki do tabeliMediaStore.Video
. - Pliki audio, które są przechowywane w katalogach
Alarms/
,Audiobooks/
,Music/
,Notifications/
,Podcasts/
iRingtones/
. System rozpoznaje też playlisty audio znajdujące się w katalogachMusic/
lubMovies/
oraz nagrania głosowe znajdujące się w kataloguRecordings/
. System doda te pliki do tabeliMediaStore.Audio
. KatalogRecordings/
nie jest dostępny w Androidzie 11 (poziom interfejsu API 30) i starszych wersjach. - Pobrane pliki,które są przechowywane w katalogu
Download/
. Na urządzeniach z Androidem 10 (interfejs API na poziomie 29) i nowszym te pliki są przechowywane w tabeliMediaStore.Downloads
. Ta tabela nie jest dostępna w Androidzie 9 (poziom API 28) i starszych.
W bibliotece multimediów znajduje się też kolekcja o nazwie MediaStore.Files
. Jego zawartość zależy od tego, czy aplikacja korzysta z pamięci o ograniczonym zakresie, która jest dostępna w aplikacjach przeznaczonych na Androida 10 lub nowszego.
- Jeśli pamięć o ograniczonym zakresie jest włączona, kolekcja zawiera tylko zdjęcia, filmy i pliki audio utworzone przez Twoją aplikację. Większość deweloperów nie musi używać
MediaStore.Files
, aby wyświetlać pliki multimedialne z innych aplikacji, ale jeśli masz konkretne wymagania w tym zakresie, możesz zadeklarować uprawnienieREAD_EXTERNAL_STORAGE
. Zalecamy jednak używanie interfejsów API do otwierania plików, które nie zostały utworzone przez Twoją aplikację.MediaStore
- Jeśli pamięć o ograniczonym zakresie jest niedostępna lub nie jest używana, kolekcja zawiera wszystkie typy plików multimedialnych.
Poproś o niezbędne uprawnienia
Zanim wykonasz operacje na plikach multimedialnych, upewnij się, że aplikacja zadeklarowała uprawnienia potrzebne do uzyskania dostępu do tych plików. Uważaj jednak, aby nie deklarować uprawnień, których Twoja aplikacja nie potrzebuje ani nie używa.
Uprawnienia dostępu do pamięci
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 również 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 zdjęć, które robi, ponieważ Twoja aplikacja jest właścicielem obrazów zapisywanych w repozytorium 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:
Dopóki plik jest widoczny w wynikach zapytań MediaStore.Images
, MediaStore.Video
lub MediaStore.Audio
, jest też widoczny w wynikach zapytania MediaStore.Files
.
Ten fragment kodu pokazuje, jak zadeklarować odpowiednie uprawnienia do przechowywania:
<!-- 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 Twoja aplikacja jest używana na urządzeniu z Androidem 9 lub starszym albo jeśli tymczasowo zrezygnowała z pamięci o ograniczonym zakresie, 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 uprawnienia WRITE_EXTERNAL_STORAGE
.
Aby uzyskać dostęp do pobranych plików z innych aplikacji, musisz użyć platformy Storage Access Framework
Jeśli aplikacja chce uzyskać dostęp do pliku w MediaStore.Downloads
kolekcji, który nie został przez nią utworzony, musi użyć 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 w multimediach
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 nie w czasie działania aplikacji.
Sprawdzanie dostępności aktualizacji sklepu z multimediami
Aby uzyskać bardziej niezawodny dostęp do plików multimedialnych, zwłaszcza jeśli aplikacja buforuje identyfikatory URI lub dane ze sklepu z multimediami, sprawdź, czy wersja sklepu z multimediami uległa zmianie w porównaniu z ostatnią synchronizacją danych multimedialnych. Aby sprawdzić dostępność aktualizacji, wywołaj funkcję getVersion()
.
Zwracana wersja to unikalny ciąg znaków, który zmienia się za każdym razem, gdy sklep z multimediami ulega istotnym zmianom. Jeśli zwrócona wersja różni się od ostatniej zsynchronizowanej wersji, ponownie przeskanuj i zsynchronizuj pamięć podręczną multimediów aplikacji.
Przeprowadź to sprawdzenie podczas uruchamiania procesu aplikacji. Nie musisz sprawdzać wersji za każdym razem, gdy wysyłasz zapytanie do sklepu z multimediami.
Nie zakładaj żadnych szczegółów wdrożenia dotyczących numeru wersji.
Wysyłanie zapytań do kolekcji multimediów
Aby znaleźć multimedia spełniające określony zestaw warunków, np. trwające co najmniej 5 minut, użyj instrukcji wyboru podobnej do SQL, takiej jak ta w poniższym 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 roboczym. - Zapisz w pamięci podręcznej indeksy kolumn, aby nie trzeba było wywoływać funkcji
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 i nowszym wymagają nazw kolumn, które są zdefiniowane w interfejsie
MediaStore
API. Jeśli biblioteka zależna 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, zamiast samych plików lepiej wczytać ich wersje podglądu, czyli miniatury.
Aby wczytać miniaturę danego pliku multimedialnego, użyj
loadThumbnail()
i przekaż 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 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, która jest przedstawiona 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, która jest pokazana 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 do plików
Aby aplikacja lepiej współpracowała z bibliotekami multimediów innych firm, Android 11 (poziom API 30) i nowsze wersje umożliwiają korzystanie z interfejsów API innych niż interfejs MediaStore
do 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:
- Interfejs API
File
- Biblioteki natywne, np.
fopen()
Jeśli nie masz żadnych uprawnień związanych z pamięcią, możesz uzyskać dostęp do plików w katalogu aplikacji, a także do plików multimedialnych przypisanych do aplikacji za pomocą interfejsu File
API.
Jeśli aplikacja spróbuje uzyskać dostęp do pliku za pomocą interfejsu File
API, a nie będzie mieć odpowiednich uprawnień, wystąpi FileNotFoundException
.
Aby uzyskać dostęp do innych plików w pamięci współdzielonej na urządzeniu z Androidem 10 (API na poziomie 29), zalecamy tymczasowe wyłączenie pamięci o ograniczonym zakresie przez ustawienie wartości true
w pliku manifestu aplikacji.requestLegacyExternalStorage
Aby uzyskać dostęp do plików multimedialnych za pomocą natywnych metod plików na Androidzie 10, musisz też poprosić o uprawnienie READ_EXTERNAL_STORAGE
.
Co 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 sekcjach poniżej.
Dane w pamięci podręcznej
Jeśli Twoja aplikacja buforuje identyfikatory URI lub dane z magazynu multimediów, okresowo sprawdzaj, czy w magazynie multimediów nie ma aktualizacji. Dzięki temu sprawdzeniu dane buforowane po stronie aplikacji pozostają zsynchronizowane z danymi dostawcy po stronie systemu.
Wydajność
Podczas sekwencyjnego odczytywania plików multimedialnych przy użyciu bezpośrednich ścieżek do plików wydajność jest porównywalna z wydajnością interfejsu MediaStore
API.
Jednak podczas losowego odczytu i zapisu plików multimedialnych przy użyciu bezpośrednich ścieżek plików proces może być nawet 2 razy wolniejszy. W takich sytuacjach zalecamy korzystanie z interfejsu MediaStore
API.
Kolumna DATA
Gdy uzyskasz dostęp do istniejącego pliku multimedialnego, możesz użyć wartości z kolumny DATA
w logice. 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 kolumny DATA
. Zamiast tego użyj wartości z kolumn DISPLAY_NAME
i RELATIVE_PATH
.
Woluminy miejsca na dane
Aplikacje przeznaczone na urządzenia z Androidem 10 lub nowszym mają dostęp do unikalnej nazwy, którą system przypisuje do każdego woluminu pamięci zewnętrznej. Ten system nazewnictwa ułatwia organizowanie i indeksowanie treści oraz daje kontrolę nad miejscem przechowywania nowych plików multimedialnych.
Warto pamiętać o tych wartościach:
VOLUME_EXTERNAL
Wolumin zawiera widok wszystkich woluminów pamięci współdzielonej na urządzeniu. Możesz odczytać zawartość tego woluminu syntetycznego, ale nie możesz jej modyfikować.VOLUME_EXTERNAL_PRIMARY
Głośność reprezentuje główną udostępnioną pamięć masową na urządzeniu. Możesz czytać i modyfikować zawartość tego woluminu.
Inne woluminy możesz odkryć, 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();
Lokalizacja, w której zarejestrowano multimedia
Niektóre zdjęcia i filmy zawierają w metadanych informacje o lokalizacji, które wskazują miejsce, w którym zostało zrobione zdjęcie lub nagrany film.
Sposób uzyskiwania dostępu do tych informacji o lokalizacji w aplikacji zależy od tego, czy chcesz uzyskać dostęp do informacji o lokalizacji zdjęcia czy filmu.
Fotografie
Jeśli Twoja aplikacja korzysta z ograniczonego dostępu do pamięci, system domyślnie ukrywa informacje o lokalizacji. Aby uzyskać dostęp do tych informacji, wykonaj te czynności:
- Poproś o uprawnienie
ACCESS_MEDIA_LOCATION
w pliku manifestu aplikacji. Z obiektu
MediaStore
pobierz dokładne bajty zdjęcia, wywołując funkcję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 używać 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 aplikacje społecznościowe umożliwiają użytkownikom udostępnianie zdjęć i filmów znajomym.
Aby udostępniać pliki multimedialne, użyj content://
identyfikatora URI, zgodnie z zaleceniami w przewodniku po tworzeniu dostawcy treści.
Atrybucja plików multimedialnych w aplikacji
Gdy w przypadku aplikacji kierowanej na Androida 10 lub nowszego włączone jest pamięć o ograniczonym zakresie, system przypisuje aplikację do każdego pliku multimedialnego. Określa to, do których plików aplikacja może uzyskać dostęp, gdy nie poprosiła o żadne uprawnienia do pamięci. Każdy plik może być przypisany tylko do jednej aplikacji. Jeśli więc aplikacja utworzy plik multimedialny, który jest przechowywany w kolekcji zdjęć, filmów lub plików audio, będzie mieć do niego dostęp.
Jeśli jednak użytkownik odinstaluje i ponownie zainstaluje aplikację, musisz poprosićREAD_EXTERNAL_STORAGE
o dostęp do plików, które aplikacja utworzyła pierwotnie. To żądanie uprawnień jest wymagane, 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 w wersji 36 lub nowszej 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ęć wstępnie wybrane 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.
Dodawanie elementu
Aby dodać element multimedialny do istniejącej kolekcji, użyj kodu podobnego do poniższego. Ten fragment kodu uzyskuje dostęp do VOLUME_EXTERNAL_PRIMARY
głośności na urządzeniach z Androidem 10 lub nowszym. Na tych urządzeniach możesz modyfikować zawartość woluminu tylko wtedy, gdy jest on woluminem podstawowym, co opisano w sekcji Woluminy pamięci.
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);
Włączanie i wyłączanie stanu oczekiwania w przypadku 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ć ten 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 jest rozwinięciem poprzedniego fragmentu. 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 zapisuje multimedia na urządzeniu z Androidem 10, domyślnie są one porządkowane według typu. Na przykład domyślnie nowe pliki obrazów 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ć wartość MediaColumns.RELATIVE_PATH
, aby podać 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 przedstawiony w poprzednim fragmencie kodu działa również w przypadku plików, których aplikacja nie jest właścicielem.
Aktualizacja w kodzie natywnym
Jeśli musisz zapisywać pliki multimedialne za pomocą bibliotek natywnych, przekaż powiązany deskryptor pliku z kodu opartego na Javie lub Kotlinie do kodu natywnego.
Ten 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 zakresowego dostępu do pamięci, zwykle nie może aktualizować pliku multimedialnego, który inna aplikacja dodała do sklepu z multimediami.
Możesz jednak uzyskać zgodę użytkownika na modyfikowanie pliku, przechwytując wyjątek RecoverableSecurityException
, który zgłasza platforma. Następnie możesz poprosić użytkownika o przyznanie aplikacji uprawnień do zapisu w tym konkretnym elemencie, 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); } }
Wykonuj tę procedurę za każdym razem, gdy aplikacja musi zmodyfikować plik multimedialny, którego nie utworzyła.
Jeśli Twoja aplikacja działa na Androidzie 11 lub nowszym, możesz też zezwolić użytkownikom na przyznanie jej uprawnień do zapisu w grupie plików multimedialnych. Skorzystaj z metody createWriteRequest()
opisanej w sekcji dotyczącej zarządzania grupami plików multimedialnych.
Jeśli Twoja aplikacja ma inne zastosowanie, które nie jest objęte pamięcią o ograniczonym zakresie, prześlij prośbę o dodanie funkcji i tymczasowo zrezygnuj z pamięci o ograniczonym zakresie.
Usuwanie elementu
Aby usunąć z magazynu multimediów element, który nie jest już potrzebny w aplikacji, użyj logiki podobnej do tej, która jest pokazana 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 pamięć o ograniczonym zakresie jest niedostępna lub nie jest włączona, możesz użyć powyższego fragmentu kodu, aby usunąć pliki należące do innych aplikacji. Jeśli jednak pamięć o ograniczonym zakresie jest włączona, musisz przechwycić RecoverableSecurityException
dla każdego pliku, który aplikacja chce usunąć, zgodnie z opisem w sekcji dotyczącej aktualizowania elementów multimedialnych.
Jeśli Twoja aplikacja działa na Androidzie 11 lub nowszym, możesz umożliwić użytkownikom wybór grupy plików multimedialnych do usunięcia. Skorzystaj z metody createTrashRequest()
lub createDeleteRequest()
zgodnie z opisem w sekcji dotyczącej zarządzania grupami plików multimedialnych.
Jeśli Twoja aplikacja ma inne zastosowanie, które nie jest objęte pamięcią o ograniczonym zakresie, prześlij prośbę o dodanie funkcji i tymczasowo zrezygnuj z pamięci o ograniczonym zakresie.
wykrywać aktualizacje plików multimedialnych;
Aplikacja może potrzebować identyfikowania woluminów pamięci zawierających pliki multimedialne dodane lub zmodyfikowane przez aplikacje w porównaniu z poprzednim momentem. Aby jak najdokładniej wykrywać te zmiany, przekaż wolumin pamięci, który Cię interesuje, do funkcji getGeneration()
.
Dopóki wersja sklepu z multimediami się nie zmieni, wartość zwracana przez tę metodę będzie z czasem monotonicznie rosnąć.
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 kolumnie multimediów mogą się zmieniać, gdy aplikacja wywołuje funkcję
setLastModified()
lub gdy
użytkownik zmienia zegar systemowy.
Zarządzanie grupami plików multimedialnych
Na urządzeniach z Androidem 11 lub nowszym 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ą funkcję „aktualizacji zbiorczej”, to:
createWriteRequest()
- Poproś użytkownika o przyznanie aplikacji uprawnień do zapisu w określonej grupie plików multimedialnych.
createFavoriteRequest()
- Poprosić 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 sprawdzić, czy użytkownik oznaczył go jako „ulubiony”.
createTrashRequest()
Poproś użytkownika o umieszczenie wskazanych plików multimedialnych w koszu urządzenia. 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 umieszczania ich wcześniej w koszu.
Po wywołaniu dowolnej z tych metod system tworzy obiekt PendingIntent
. Gdy aplikacja wywoła ten zamiar, użytkownicy zobaczą okno z prośbą o zgodę na aktualizację lub usunięcie określonych plików multimedialnych przez aplikację.
Oto przykład struktury wywołania funkcji 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);
Ocena odpowiedzi użytkownika. Jeśli użytkownik wyraził zgodę, kontynuuj operację na pliku multimedialnym. W przeciwnym razie wyjaśnij użytkownikowi, dlaczego Twoja 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. w przypadku częstych edycji plików multimedialnych. Jeśli Twoja aplikacja jest kierowana na Androida 11 lub nowszego i nie jest domyślną aplikacją galerii na urządzeniu, musisz wyświetlać 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 (API na poziomie 31) lub nowszego, możesz poprosić użytkowników o przyznanie jej specjalnego uprawnienia zarządzania multimediami. To uprawnienie umożliwia aplikacji wykonywanie każdej z tych czynności bez konieczności wyświetlania użytkownikowi prośby o potwierdzenie każdej operacji na pliku:
- Modyfikuj pliki za pomocą
createWriteRequest()
. - Przenoszenie plików do kosza i z kosza za pomocą
createTrashRequest()
. - Usuń pliki za pomocą
createDeleteRequest()
.
Aby to zrobić, wykonaj te czynności:
Zadeklaruj uprawnienia
MANAGE_MEDIA
iREAD_EXTERNAL_STORAGE
w pliku manifestu aplikacji.Aby zadzwonić pod numer
createWriteRequest()
bez wyświetlania okna potwierdzenia, zadeklaruj też uprawnienieACCESS_MEDIA_LOCATION
.W aplikacji wyświetl interfejs, który wyjaśni użytkownikowi, dlaczego warto przyznać aplikacji dostęp do zarządzania multimediami.
Wywołaj działanie intencji
ACTION_REQUEST_MANAGE_MEDIA
. Użytkownik zostanie przeniesiony na ekran Aplikacje do zarządzania multimediami w ustawieniach systemu. Użytkownicy mogą tam przyznać aplikacji specjalny dostęp.
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 MediaStore
API.
Praca z innymi typami plików
Jeśli Twoja aplikacja obsługuje dokumenty i pliki, które nie zawierają wyłącznie treści multimedialnych, np. pliki z rozszerzeniem EPUB lub PDF, użyj działania intencji ACTION_OPEN_DOCUMENT
, zgodnie z opisem w przewodniku po przechowywaniu i otwieraniu dokumentów oraz innych plików.
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ę profilową, skonfiguruj udostępnianie plików za pomocą content://
URI. Zalecamy też ten proces jako sprawdzoną metodę zapewniania bezpieczeństwa.
Dodatkowe materiały
Więcej informacji o przechowywaniu i uzyskiwaniu dostępu do multimediów znajdziesz w tych materiałach.
Próbki
- MediaStore, dostępny w GitHubie