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 te pliki pozostają na urządzeniu użytkownika.
Selektor zdjęć
Alternatywą dla sklepu z multimediami jest selektor zdjęć na Androidzie, który jest bezpiecznym, wbudowanym narzędziem umożliwiającym użytkownikom 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: 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:
val projection = arrayOf(media-database-columns-to-retrieve ) val selection =sql-where-clause-with-placeholder-variables val selectionArgs =values-of-placeholder-variables val sortOrder =sql-order-by-clause applicationContext.contentResolver.query( MediaStore.media-type .Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder )?.use { cursor -> while (cursor.moveToNext()) { // Use an ID column from the projection to get // a URI representing the media item itself. } }
String[] projection = new String[] {media-database-columns-to-retrieve }; String selection =sql-where-clause-with-placeholder-variables ; String[] selectionArgs = new String[] {values-of-placeholder-variables }; String sortOrder =sql-order-by-clause ; Cursor cursor = getApplicationContext().getContentResolver().query( MediaStore.media-type .Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder ); while (cursor.moveToNext()) { // Use an ID column from the projection to get // a URI representing the media item itself. }
System automatycznie skanuje wolumin pamięci zewnętrznej i dodaje pliki multimedialne do następujących dobrze zdefiniowanych kolekcji:
- Obrazy, w tym fotografie i zrzuty ekranu, które są przechowywane w katalogach
DCIM/
iPictures/
. System doda te pliki doMediaStore.Images
. - Filmy, które są przechowywane w katalogach
DCIM/
,Movies/
iPictures/
. System doda te pliki doMediaStore.Video
. - Pliki audio, które są przechowywane w katalogach
Alarms/
,Audiobooks/
,Music/
,Notifications/
,Podcasts/
iRingtones/
. System rozpoznaje też playlisty audio w katalogachMusic/
lubMovies/
oraz nagrania głosowe w kataloguRecordings/
. System dodaje te pliki do tabeliMediaStore.Audio
. KatalogRecordings/
jest niedostępny w Androidzie 11 (poziom interfejsu API 30) i starszych wersjach. - pobrane pliki 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 (poziom interfejsu API 28) i starszych.
Sklep z mediami ma również kolekcję o nazwie
MediaStore.Files
Jego zawartość zależy od tego, czy Twoja aplikacja korzysta z ograniczonego miejsca na dane, dostępnego w aplikacjach przeznaczonych na Androida 10 lub nowszego.
- Jeśli ograniczona pamięć jest włączona, kolekcja zawiera tylko zdjęcia, filmy i pliki audio utworzone przez aplikację. Większość deweloperów nie musi używać uprawnienia
MediaStore.Files
do wyświetlania plików multimedialnych z innych aplikacji, ale jeśli jest to wymagane, możesz zadeklarować uprawnienieREAD_EXTERNAL_STORAGE
. Zalecamy jednak, aby używać interfejsów APIMediaStore
do otwierania plików, które nie zostały utworzone przez Twoją aplikację. - Jeśli ograniczone miejsce na dane jest niedostępne lub nie jest używane, kolekcja zawiera wszystkie typy plików multimedialnych.
Proś 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 aplikacja potrzebuje uprawnień dostępu do pamięci, zależy od tego, czy ma do niej dostęp tylko własne pliki multimedialne lub pliki utworzone w innych aplikacjach.
Dostęp do własnych plików multimedialnych
Na urządzeniach z Androidem 10 lub nowszym nie musisz
uprawnienia związane z pamięcią na dostęp do plików multimedialnych i ich modyfikowanie,
należy do Twojej aplikacji, w tym pliki w folderze MediaStore.Downloads
kolekcji. 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 jest widoczny w zapytaniach MediaStore.Images
, MediaStore.Video
lub MediaStore.Audio
, jest też widoczny w zapytaniu 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" />
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 wyłączono ograniczoną pamięć, musisz poprosić o READ_EXTERNAL_STORAGE
dostęp do dowolnego pliku multimedialnego. Jeśli chcesz modyfikować pliki multimedialne, musisz poprosić o uprawnienia WRITE_EXTERNAL_STORAGE
.
Platforma dostępu do pamięci masowej wymagana do uzyskania dostępu do innych aplikacji pobrane pliki
Jeśli aplikacja chce uzyskać dostęp do pliku w zbiorze MediaStore.Downloads
, którego nie utworzyła, musi użyć interfejsu 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 wspólnego miejsca na dane.
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 Twoja aplikacja przechowuje w pamięci podręcznej URI lub dane z magazynu multimediów, sprawdź, czy wersja magazynu multimediów zmieniła się w porównaniu z czasem ostatniej synchronizacji danych multimediów. Aby sprawdzić dostępność aktualizacji, zadzwoń pod numer getVersion()
.
Zwrócona wersja to unikalny ciąg znaków, który zmienia się za każdym razem, gdy zasób multimediów ulegnie znacznej zmianie. Jeśli zwrócona wersja różni się od ostatnio zsynchronizowanej
wersji, przeskanuj i zsynchronizuj ponownie pamięć podręczną.
Przeprowadź ten proces 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.
Wysyłanie 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:
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your // app didn't create. // Container for information about each video. data class Video(val uri: Uri, val name: String, val duration: Int, val size: Int ) val videoList = mutableListOf<Video>() val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Video.Media.getContentUri( MediaStore.VOLUME_EXTERNAL ) } else { MediaStore.Video.Media.EXTERNAL_CONTENT_URI } val projection = arrayOf( MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE ) // Show only videos that are at least 5 minutes in duration. val selection = "${MediaStore.Video.Media.DURATION} >= ?" val selectionArgs = arrayOf( TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString() ) // Display videos in alphabetical order based on their display name. val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC" val query = ContentResolver.query( collection, projection, selection, selectionArgs, sortOrder ) query?.use { cursor -> // Cache column indices. val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME) val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION) val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE) while (cursor.moveToNext()) { // Get values of columns for a given video. val id = cursor.getLong(idColumn) val name = cursor.getString(nameColumn) val duration = cursor.getInt(durationColumn) val size = cursor.getInt(sizeColumn) val contentUri: Uri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id ) // Stores column values and the contentUri in a local object // that represents the media file. videoList += Video(contentUri, name, duration, size) } }
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your // app didn't create. // Container for information about each video. class Video { private final Uri uri; private final String name; private final int duration; private final int size; public Video(Uri uri, String name, int duration, int size) { this.uri = uri; this.name = name; this.duration = duration; this.size = size; } } List<Video> videoList = new ArrayList<Video>(); Uri collection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL); } else { collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } String[] projection = new String[] { MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE }; String selection = MediaStore.Video.Media.DURATION + " >= ?"; String[] selectionArgs = new String[] { String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES)); }; String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + " ASC"; try (Cursor cursor = getApplicationContext().getContentResolver().query( collection, projection, selection, selectionArgs, sortOrder )) { // Cache column indices. int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID); int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME); int durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION); int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE); while (cursor.moveToNext()) { // Get values of columns for a given video. long id = cursor.getLong(idColumn); String name = cursor.getString(nameColumn); int duration = cursor.getInt(durationColumn); int size = cursor.getInt(sizeColumn); Uri contentUri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id); // Stores column values and the contentUri in a local object // that represents the media file. videoList.add(new Video(contentUri, name, duration, size)); } }
Wykonując takie zapytanie 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 w sposób pokazany 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żyjCursorWrapper
na dynamiczne przetłumacz nazwę kolumny w procesie aplikacji.
wczytywanie miniatur plików;
Jeśli Twoja aplikacja wyświetla wiele plików multimedialnych i prosi użytkownika o wybranie jednego z nich, wydajniej jest wczytać wersje podglądowe (czyli miniatury) plików, a nie same pliki.
Aby wczytać miniaturę danego pliku multimedialnego, użyj funkcji
loadThumbnail()
i podaj rozmiar miniatury, którą chcesz wczytać, zgodnie z
ten fragment kodu:
// Load thumbnail of a specific media item. val thumbnail: Bitmap = applicationContext.contentResolver.loadThumbnail(content-uri , Size(640, 480), null)
// Load thumbnail of a specific media item. Bitmap thumbnail = getApplicationContext().getContentResolver().loadThumbnail(content-uri , new Size(640, 480), null);
Otwieranie pliku multimedialnego
Konkretna logika używana do otwierania pliku multimedialnego zależy od tego, czy treści multimedialne są najlepiej reprezentowane jako deskryptor pliku, strumień plików czy bezpośrednia ścieżka pliku.
Deskryptor pliku
Aby otworzyć plik multimedialny za pomocą deskryptora pliku, zastosuj logikę podobną do tej w ten fragment kodu:
// Open a specific media item using ParcelFileDescriptor. val resolver = applicationContext.contentResolver // "rw" for read-and-write. // "rwt" for truncating or overwriting existing file contents. val readOnlyMode = "r" resolver.openFileDescriptor(content-uri , readOnlyMode).use { pfd -> // Perform operations on "pfd". }
// Open a specific media item using ParcelFileDescriptor. ContentResolver resolver = getApplicationContext() .getContentResolver(); // "rw" for read-and-write. // "rwt" for truncating or overwriting existing file contents. String readOnlyMode = "r"; try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(content-uri , readOnlyMode)) { // Perform operations on "pfd". } catch (IOException e) { e.printStackTrace(); }
Strumień plików
Aby otworzyć plik multimedialny za pomocą strumienia plików, użyj funkcji logicznej podobnej do przedstawionej w ten fragment kodu:
// Open a specific media item using InputStream. val resolver = applicationContext.contentResolver resolver.openInputStream(content-uri ).use { stream -> // Perform operations on "stream". }
// Open a specific media item using InputStream. ContentResolver resolver = getApplicationContext() .getContentResolver(); try (InputStream stream = resolver.openInputStream(content-uri )) { // Perform operations on "stream". }
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, np.
fopen()
Jeśli nie masz żadnych uprawnień związanych z przechowywaniem, 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 File
API, ale nie ma odpowiednich uprawnień, wystąpi błąd 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ą metod plików natywnych w Androidzie 10, musisz też poprosić o uprawnienia READ_EXTERNAL_STORAGE
.
Uwagi dotyczące dostępu do treści multimedialnych
Podczas korzystania z treści multimedialnych należy wziąć pod uwagę kwestie omówione w następnych sekcjach.
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. Dzięki temu dane w pamięci podręcznej po stronie aplikacji będą zgodne z danymi dostawcy po stronie systemu.
Wydajność
Gdy odczytujesz sekwencyjnie pliki multimedialne za pomocą bezpośrednich ścieżek plików, wydajność jest porównywalna z interfejsem API MediaStore
.
Jednak podczas losowego odczytu i zapisu plików multimedialnych za pomocą bezpośrednich ścieżek plików proces może być nawet dwukrotnie 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ć wartości w kolumnie DATA
w swojej logice. Wynika to z faktu, że ta wartość ma prawidłową ścieżkę pliku. Nie zakładaj jednak, że plik jest zawsze dostępny. Przygotuj się na obsługę wszelkich błędów we/wy plików, które mogą wystąpić.
Aby utworzyć lub zaktualizować plik multimedialny, nie używaj wartości atrybutu
DATA
. Zamiast tego użyj wartości w kolumnach DISPLAY_NAME
i RELATIVE_PATH
.
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.
Warto pamiętać przede wszystkim o tych woluminach:
- Objęt
VOLUME_EXTERNAL
pokazuje wszystkie objętości współdzielonego miejsca na dane na urządzeniu. Możesz czytać zawartość tego syntetycznego woluminu, ale nie możesz jej modyfikować. -
VOLUME_EXTERNAL_PRIMARY
reprezentuje główną pamięć współdzieloną na urządzeniu. Dostępne opcje odczytywanie i modyfikowanie zawartości tego woluminu.
Inne wolumeny możesz znaleźć, dzwoniąc na numer MediaStore.getExternalVolumeNames()
:
val volumeNames: Set<String> = MediaStore.getExternalVolumeNames(context) val firstVolumeName = volumeNames.iterator().next()
Set<String> volumeNames = MediaStore.getExternalVolumeNames(context); String firstVolumeName = volumeNames.iterator().next();
Lokalizacja, w której zostały zarejestrowane 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 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:
- Poproś o przyznanie uprawnienia
ACCESS_MEDIA_LOCATION
w pliku manifestu aplikacji. Aby uzyskać dokładną liczbę bajtów zdjęcia na podstawie obiektu
MediaStore
, połączeniasetRequireOriginal()
. i podając identyfikator URI zdjęcia, tak jak w poniższym fragmencie kodu:val photoUri: Uri = Uri.withAppendedPath( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getString(idColumnIndex) ) // Get location data using the Exifinterface library. // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted. photoUri = MediaStore.setRequireOriginal(photoUri) contentResolver.openInputStream(photoUri)?.use { stream -> ExifInterface(stream).run { // If lat/long is null, fall back to the coordinates (0, 0). val latLong = latLong ?: doubleArrayOf(0.0, 0.0) } }
Uri photoUri = Uri.withAppendedPath( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getString(idColumnIndex)); final double[] latLong; // Get location data using the Exifinterface library. // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted. photoUri = MediaStore.setRequireOriginal(photoUri); InputStream stream = getContentResolver().openInputStream(photoUri); if (stream != null) { ExifInterface exifInterface = new ExifInterface(stream); double[] returnedLatLong = exifInterface.getLatLong(); // If lat/long is null, fall back to the coordinates (0, 0). latLong = returnedLatLong != null ? returnedLatLong : new double[2]; // Don't reuse the stream associated with // the instance of "ExifInterface". stream.close(); } else { // Failed to load the stream, so return the coordinates (0, 0). latLong = new double[2]; }
Filmy
Aby uzyskać dostęp do informacji o lokalizacji w metadanych filmu, użyj klasy MediaMetadataRetriever
, jak pokazano w tym fragmencie kodu. Aplikacja nie musi o to prosić
wszelkie dodatkowe uprawnienia do korzystania z tych zajęć.
val retriever = MediaMetadataRetriever() val context = applicationContext // Find the videos that are stored on a device by querying the video collection. val query = ContentResolver.query( collection, projection, selection, selectionArgs, sortOrder ) query?.use { cursor -> val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) while (cursor.moveToNext()) { val id = cursor.getLong(idColumn) val videoUri: Uri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id ) extractVideoLocationInfo(videoUri) } } private fun extractVideoLocationInfo(videoUri: Uri) { try { retriever.setDataSource(context, videoUri) } catch (e: RuntimeException) { Log.e(APP_TAG , "Cannot retrieve video file", e) } // Metadata uses a standardized format. val locationMetadata: String? = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION) }
MediaMetadataRetriever retriever = new MediaMetadataRetriever(); Context context = getApplicationContext(); // Find the videos that are stored on a device by querying the video collection. try (Cursor cursor = context.getContentResolver().query( collection, projection, selection, selectionArgs, sortOrder )) { int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID); while (cursor.moveToNext()) { long id = cursor.getLong(idColumn); Uri videoUri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id); extractVideoLocationInfo(videoUri); } } private void extractVideoLocationInfo(Uri videoUri) { try { retriever.setDataSource(context, videoUri); } catch (RuntimeException e) { Log.e(APP_TAG , "Cannot retrieve video file", e); } // Metadata uses a standardized format. String locationMetadata = retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_LOCATION); }
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.
Do udostępniania plików multimedialnych używaj identyfikatora URI obiektu content://
zgodnie z zaleceniami w przewodniku
tworząc dostawcę treści.
Atrybucja plików multimedialnych w aplikacji
Gdy wyspecjalizowane miejsce na dane jest włączone w przypadku aplikacji kierowanej na Androida 10 lub nowszego, system przypisuje aplikację do każdego pliku multimedialnego, co określa pliki, do których aplikacja może uzyskać dostęp, gdy nie poprosi o żadne uprawnienia do miejsca na dane. Każdy plik może być przypisany tylko do jednej aplikacji. Jeśli więc Twoja aplikacja utworzy plik multimedialny, który jest przechowywany w zbiorze multimediów (zdjęć, filmów lub plików audio), aplikacja ma do niego dostęp.
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. Ta prośba o przyznanie uprawnień jest
wymagane, ponieważ system uznaje plik za przypisanie do zmiennej
wcześniej zainstalowanej wersji, a nie nowo zainstalowanej.
Dodawanie elementu
Aby dodać element multimedialny do istniejącej kolekcji, użyj kodu podobnego do tego: Ten fragment kodu uzyskuje dostęp do głośności 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.
// Add a specific media item. val resolver = applicationContext.contentResolver // Find all audio files on the primary external storage device. val audioCollection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Audio.Media.getContentUri( MediaStore.VOLUME_EXTERNAL_PRIMARY ) } else { MediaStore.Audio.Media.EXTERNAL_CONTENT_URI } // Publish a new song. val newSongDetails = ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3") } // Keep a handle to the new song's URI in case you need to modify it // later. val myFavoriteSongUri = resolver .insert(audioCollection, newSongDetails)
// Add a specific media item. ContentResolver resolver = getApplicationContext() .getContentResolver(); // Find all audio files on the primary external storage device. Uri audioCollection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { audioCollection = MediaStore.Audio.Media .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); } else { audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } // Publish a new song. ContentValues newSongDetails = new ContentValues(); newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3"); // Keep a handle to the new song's URI in case you need to modify it // later. Uri myFavoriteSongUri = resolver .insert(audioCollection, newSongDetails);
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. Dopóki aplikacja nie zmieni wartości parametru IS_PENDING
z powrotem na 0, tylko ona będzie mogła wyświetlać plik.
Poniższy fragment kodu jest oparty na poprzednim fragmencie. Ten fragment kodu pokazuje, jak użyć flagi IS_PENDING
podczas przechowywania długiego utworu w katalogu odpowiadającym kolekcji MediaStore.Audio
:
// Add a media item that other apps don't see until the item is // fully written to the media store. val resolver = applicationContext.contentResolver // Find all audio files on the primary external storage device. val audioCollection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Audio.Media.getContentUri( MediaStore.VOLUME_EXTERNAL_PRIMARY ) } else { MediaStore.Audio.Media.EXTERNAL_CONTENT_URI } val songDetails = ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3") put(MediaStore.Audio.Media.IS_PENDING, 1) } val songContentUri = resolver.insert(audioCollection, songDetails) // "w" for write. resolver.openFileDescriptor(songContentUri, "w", null).use { pfd -> // Write data into the pending audio file. } // Now that you're finished, release the "pending" status and let other apps // play the audio track. songDetails.clear() songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0) resolver.update(songContentUri, songDetails, null, null)
// Add a media item that other apps don't see until the item is // fully written to the media store. ContentResolver resolver = getApplicationContext() .getContentResolver(); // Find all audio files on the primary external storage device. Uri audioCollection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { audioCollection = MediaStore.Audio.Media .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); } else { audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } ContentValues songDetails = new ContentValues(); songDetails.put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3"); songDetails.put(MediaStore.Audio.Media.IS_PENDING, 1); Uri songContentUri = resolver .insert(audioCollection, songDetails); // "w" for write. try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(songContentUri, "w", null)) { // Write data into the pending audio file. } // Now that you're finished, release the "pending" status and let other apps // play the audio track. songDetails.clear(); songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0); resolver.update(songContentUri, songDetails, null, null);
Podpowiedź dotycząca lokalizacji pliku
Gdy aplikacja przechowuje multimedia na urządzeniu z Androidem 10, domyślnie są one 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:
// Updates an existing media item. val mediaId = // MediaStore.Audio.Media._ID of item to update. val resolver = applicationContext.contentResolver // When performing a single item update, prefer using the ID. val selection = "${MediaStore.Audio.Media._ID} = ?" // By using selection + args you protect against improper escaping of // values. val selectionArgs = arrayOf(mediaId.toString()) // Update an existing song. val updatedSongDetails = ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3") } // Use the individual song's URI to represent the collection that's // updated. val numSongsUpdated = resolver.update( myFavoriteSongUri, updatedSongDetails, selection, selectionArgs)
// Updates an existing media item. long mediaId = // MediaStore.Audio.Media._ID of item to update. ContentResolver resolver = getApplicationContext() .getContentResolver(); // When performing a single item update, prefer using the ID. String selection = MediaStore.Audio.Media._ID + " = ?"; // By using selection + args you protect against improper escaping of // values. Here, "song" is an in-memory object that caches the song's // information. String[] selectionArgs = new String[] { getId().toString() }; // Update an existing song. ContentValues updatedSongDetails = new ContentValues(); updatedSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3"); // Use the individual song's URI to represent the collection that's // updated. int numSongsUpdated = resolver.update( myFavoriteSongUri, updatedSongDetails, selection, selectionArgs);
Jeśli pamięć o ograniczonym zakresie jest niedostępna lub nie jest włączona, proces pokazany w poprzedni fragment 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 przy użyciu bibliotek natywnych, przekaż w parametrze z deskryptora pliku z kodu opartego na Javie lub Kotlinie do kodu natywnego.
Fragment kodu poniżej pokazuje, jak przekazać deskryptor pliku obiektu multimedialnego do kodu natywnego aplikacji:
val contentUri: Uri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cursor.getLong(BaseColumns._ID)) val fileOpenMode = "r" val parcelFd = resolver.openFileDescriptor(contentUri, fileOpenMode) val fd = parcelFd?.detachFd() // Pass the integer value "fd" into your native code. Remember to call // close(2) on the file descriptor when you're done using it.
Uri contentUri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cursor.getLong(Integer.parseInt(BaseColumns._ID))); String fileOpenMode = "r"; ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(contentUri, fileOpenMode); if (parcelFd != null) { int fd = parcelFd.detachFd(); // Pass the integer value "fd" into your native code. Remember to call // close(2) on the file descriptor when you're done using it. }
Aktualizuj w innych aplikacjach pliki multimedialne
Jeśli aplikacja korzysta z ograniczonego miejsca na dane, zwykle nie może zaktualizować pliku multimedialnego, który został przesłany do magazynu multimediów przez inną aplikację.
Możesz jednak uzyskać zgodę użytkownika na modyfikację pliku, przechwytując RecoverableSecurityException
, który platforma zwraca. Następnie możesz poprosić użytkownika o przyznanie aplikacji uprawnień do zapisu w danym elemencie, jak pokazano w tym fragmencie kodu:
// Apply a grayscale filter to the image at the given content URI. try { // "w" for write. contentResolver.openFileDescriptor(image-content-uri , "w")?.use { setGrayscaleFilter(it) } } catch (securityException: SecurityException) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val recoverableSecurityException = securityException as? RecoverableSecurityException ?: throw RuntimeException(securityException.message, securityException) val intentSender = recoverableSecurityException.userAction.actionIntent.intentSender intentSender?.let { startIntentSenderForResult(intentSender,image-request-code , null, 0, 0, 0, null) } } else { throw RuntimeException(securityException.message, securityException) } }
try { // "w" for write. ParcelFileDescriptor imageFd = getContentResolver() .openFileDescriptor(image-content-uri , "w"); setGrayscaleFilter(imageFd); } catch (SecurityException securityException) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { RecoverableSecurityException recoverableSecurityException; if (securityException instanceof RecoverableSecurityException) { recoverableSecurityException = (RecoverableSecurityException)securityException; } else { throw new RuntimeException( securityException.getMessage(), securityException); } IntentSender intentSender =recoverableSecurityException.getUserAction() .getActionIntent().getIntentSender(); startIntentSenderForResult(intentSender,image-request-code , null, 0, 0, 0, null); } else { throw new RuntimeException( securityException.getMessage(), securityException); } }
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:
// Remove a specific media item. val resolver = applicationContext.contentResolver // URI of the image to remove. val imageUri = "..." // WHERE clause. val selection = "..." val selectionArgs = "..." // Perform the actual removal. val numImagesRemoved = resolver.delete( imageUri, selection, selectionArgs)
// Remove a specific media item. ContentResolver resolver = getApplicationContext() getContentResolver(); // URI of the image to remove. Uri imageUri = "..."; // WHERE clause. String selection = "..."; String[] selectionArgs = "..."; // Perform the actual removal. int numImagesRemoved = resolver.delete( imageUri, selection, selectionArgs);
Jeśli ograniczona pamięć 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 masz włączone ograniczone przechowywanie, musisz przechwycić zdarzenie RecoverableSecurityException
dla każdego pliku, który aplikacja chce usunąć, zgodnie z opisem w sekcji Aktualizowanie elementów multimediów.
Jeśli Twoja aplikacja działa na Androidzie 11 lub nowszym, możesz zezwolić użytkownikom
wybrać grupę 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 inne zastosowanie, które nie jest objęte ograniczonym dostępem do pamięci, prześlij prośbę o dodanie funkcji i tymczasowo wyłącz ograniczony dostęp do pamięci.
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.
Szczególnie getGeneration()
jest bardziej niezawodny niż daty w kolumnach multimediów, takich jak DATE_ADDED
i DATE_MODIFIED
.
Dzieje się tak, ponieważ wartości w kolumnach multimediów mogą się zmieniać, gdy aplikacja wywołuje
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 z plików multimedialnych, a następnie zaktualizuj je 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 zapewniające tę „aktualizację zbiorczą” funkcje obejmują :
createWriteRequest()
- Poproś użytkownika o przyznanie uprawnień do zapisu w określonej grupie plików multimedialnych.
createFavoriteRequest()
- Poproś użytkownika o oznaczenie określonych plików multimedialnych jako „ulubione” z multimediów zapisanych 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 przeniesienie wskazanych plików multimedialnych do kosza na urządzeniu. Elementy w koszu są trwale usuwane po czasie określonym przez system okresu przejściowego.
createDeleteRequest()
Poproś użytkownika o bezpośrednie trwałe usunięcie określonych plików multimedialnych bez umieszczania ich wcześniej w koszy.
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.
Oto przykład tworzenia wywołania funkcji createWriteRequest()
:
val urisToModify = /* A collection of content URIs to modify. */ val editPendingIntent = MediaStore.createWriteRequest(contentResolver, urisToModify) // Launch a system prompt requesting user permission for the operation. startIntentSenderForResult(editPendingIntent.intentSender,EDIT_REQUEST_CODE , null, 0, 0, 0)
List<Uri> urisToModify = /* A collection of content URIs to modify. */ PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver, urisToModify); // Launch a system prompt requesting user permission for the operation. startIntentSenderForResult(editPendingIntent.getIntentSender(),EDIT_REQUEST_CODE , null, 0, 0, 0);
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 potrzebuje tego uprawnienia:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { ... when (requestCode) {EDIT_REQUEST_CODE -> if (resultCode == Activity.RESULT_OK) { /* Edit request granted; proceed. */ } else { /* Edit request not granted; explain to the user. */ } } }
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { ... if (requestCode ==EDIT_REQUEST_CODE ) { if (resultCode == Activity.RESULT_OK) { /* Edit request granted; proceed. */ } else { /* Edit request not granted; explain to the user. */ } } }
Tego samego ogólnego wzoru możesz używać w przypadku atrybutów createFavoriteRequest()
, createTrashRequest()
i createDeleteRequest()
.
Uprawnienia do zarządzania multimediami
Użytkownicy mogą ufać danej aplikacji, że będzie ona zarządzać multimediami, np. często edytować pliki multimedialne. Jeśli Twoja aplikacja jest kierowana na Androida 11 lub nowszego i nie jest domyślną aplikacją galerii na urządzeniu, musi wyświetlać użytkownikowi okno potwierdzenia za każdym razem, gdy próbuje zmodyfikować lub usunąć plik.
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 wszystkich tych czynności bez konieczności wyświetlania prośby użytkownika w przypadku każdej operacji pliku:
- Modyfikować pliki za pomocą
createWriteRequest()
. - Przenoś pliki do i z kosza przy użyciu
createTrashRequest()
- Usuń pliki za pomocą
createDeleteRequest()
Aby to zrobić:
Zadeklaruj uprawnienia
MANAGE_MEDIA
iREAD_EXTERNAL_STORAGE
w pliku manifestu aplikacji.Aby wywołać
createWriteRequest()
bez wyświetlania okna potwierdzenia, zadeklaruj też uprawnienieACCESS_MEDIA_LOCATION
.Wyświetl w aplikacji interfejs użytkownika, aby wyjaśnić, dlaczego warto przyznać aplikacji uprawnienia do zarządzania multimediami.
Wywołaj działanie
ACTION_REQUEST_MANAGE_MEDIA
intencji. Użytkownicy zobaczą ekran Aplikacje do zarządzania multimediami w ustawieniach systemu. Tutaj użytkownicy mogą przyznać aplikacji specjalny dostęp.
Przypadki użycia wymagające alternatywy dla magazynu multimediów
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 tworzenia profili, 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
- MediaStore, dostępny w GitHubie