Per offrire un'esperienza utente più ricca, molte app consentono agli utenti di contribuire e accedere ai contenuti multimediali disponibili su un volume di archiviazione esterno. Il framework fornisce un indice ottimizzato delle raccolte multimediali, chiamato archivio multimediale, che consente agli utenti di recuperare e aggiornare più facilmente questi file multimediali. Anche dopo la disinstallazione dell'app, questi file rimangono sul dispositivo dell'utente.
Selettore di foto
In alternativa all'utilizzo di MediaStore, lo strumento di selezione delle foto di Android offre agli utenti un modo sicuro e integrato per selezionare i file multimediali senza dover concedere alla tua app l'accesso all'intera raccolta multimediale. Questa funzionalità è disponibile solo sui dispositivi supportati. Per saperne di più, consulta la guida al selettore di foto.
Negozio di contenuti multimediali
Per interagire con l'astrazione dello store multimediale, utilizza un oggetto
ContentResolver
che recuperi dal contesto della tua app:
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. }
Il sistema esegue automaticamente la scansione di un volume di archiviazione esterno e aggiunge i file multimediali alle seguenti raccolte ben definite:
- Immagini,tra cui fotografie e screenshot, archiviate nelle directory
DCIM/
ePictures/
. Il sistema aggiunge questi file alla tabellaMediaStore.Images
. - Video, archiviati nelle directory
DCIM/
,Movies/
ePictures/
. Il sistema aggiunge questi file alla tabellaMediaStore.Video
. - File audio, archiviati nelle directory
Alarms/
,Audiobooks/
,Music/
,Notifications/
,Podcasts/
eRingtones/
. Inoltre, il sistema riconosce le playlist audio che si trovano nelle directoryMusic/
oMovies/
, nonché le registrazioni vocali che si trovano nella directoryRecordings/
. Il sistema aggiunge questi file alla tabellaMediaStore.Audio
. La directoryRecordings/
non è disponibile su Android 11 (livello API 30) e versioni precedenti. - File scaricati,archiviati nella directory
Download/
. Sui dispositivi con Android 10 (livello API 29) e versioni successive, questi file vengono archiviati nella tabellaMediaStore.Downloads
. Questa tabella non è disponibile su Android 9 (livello API 28) e versioni precedenti.
Lo store multimediale include anche una raccolta chiamata
MediaStore.Files
. I suoi contenuti
dipendono dal fatto che la tua app utilizzi lo storage
isolato, disponibile sulle app che hanno come target
Android 10 o versioni successive.
- Se è attivato lo spazio di archiviazione isolato, la raccolta mostra solo le foto, i video
e i file audio creati dalla tua app. La maggior parte degli sviluppatori non ha bisogno di utilizzare
MediaStore.Files
per visualizzare i file multimediali di altre app, ma se hai un requisito specifico per farlo, puoi dichiarare l'autorizzazioneREAD_EXTERNAL_STORAGE
. Tuttavia, ti consigliamo di utilizzare le APIMediaStore
per aprire i file che la tua app non ha creato. - Se lo spazio di archiviazione isolato non è disponibile o non viene utilizzato, la raccolta mostra tutti i tipi di file multimediali.
Richiedere le autorizzazioni necessarie
Prima di eseguire operazioni sui file multimediali, assicurati che la tua app abbia dichiarato le autorizzazioni necessarie per accedere a questi file. Fai attenzione, però, a non dichiarare autorizzazioni di cui la tua app non ha bisogno o che non utilizza.
Autorizzazioni di archiviazione
La necessità di autorizzazioni per accedere allo spazio di archiviazione dipende dal fatto che l'app acceda solo ai propri file multimediali o ai file creati da altre app.
Accedere ai propri file multimediali
Sui dispositivi che eseguono Android 10 o versioni successive, non hai bisogno
di autorizzazioni relative all'archiviazione per accedere e modificare i file multimediali
di proprietà della tua app, inclusi i file nella raccolta MediaStore.Downloads
. Se stai sviluppando un'app fotocamera, ad esempio, non devi
richiedere autorizzazioni relative all'archiviazione per accedere alle foto che scatta, perché la tua
app è proprietaria delle immagini che scrivi nel media store.
Accedere ai file multimediali di altre app
Per accedere ai file multimediali creati da altre app, devi dichiarare le autorizzazioni appropriate relative all'archiviazione e i file devono risiedere in una delle seguenti raccolte multimediali:
Se un file è visualizzabile dalle query MediaStore.Images
,
MediaStore.Video
o MediaStore.Audio
, è visualizzabile anche utilizzando la query
MediaStore.Files
.
Il seguente snippet di codice mostra come dichiarare le autorizzazioni di archiviazione appropriate:
<!-- 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" />
Autorizzazioni aggiuntive necessarie per le app in esecuzione su dispositivi legacy
Se la tua app viene utilizzata su un dispositivo con Android 9 o versioni precedenti oppure se
ha temporaneamente disattivato l'archiviazione
isolata, devi
richiedere l'autorizzazione
READ_EXTERNAL_STORAGE
per accedere a qualsiasi file multimediale. Se vuoi modificare i file multimediali, devi
richiedere anche
l'autorizzazione
WRITE_EXTERNAL_STORAGE
.
Framework di accesso allo spazio di archiviazione richiesto per accedere ai download di altre app
Se la tua app vuole accedere a un file all'interno della raccolta MediaStore.Downloads
che non ha creato, devi utilizzare Storage Access Framework. Per saperne di più su come utilizzare questo framework, consulta Accedere a documenti e altri file dallo spazio di archiviazione condiviso.
Autorizzazione di accesso alla posizione dei contenuti multimediali
Se la tua app ha come target Android 10 (livello API 29) o versioni successive e deve
recuperare i metadati EXIF non modificati dalle foto, devi dichiarare l'autorizzazione
ACCESS_MEDIA_LOCATION
nel file manifest dell'app, quindi richiedere questa autorizzazione in fase di runtime.
Verificare la disponibilità di aggiornamenti dello store multimediale
Per accedere ai file multimediali in modo più affidabile, in particolare se la tua app memorizza nella cache gli URI o
i dati del media store, controlla se la versione del media store è cambiata
rispetto all'ultima sincronizzazione dei dati multimediali. Per eseguire questo controllo
degli aggiornamenti, chiama
getVersion()
.
La versione restituita è una stringa univoca che cambia ogni volta che lo store multimediale
subisce modifiche sostanziali. Se la versione restituita è diversa dall'ultima versione sincronizzata, esegui nuovamente la scansione e la sincronizzazione della cache multimediale dell'app.
Completa questo controllo al momento dell'avvio del processo dell'app. Non è necessario controllare la versione ogni volta che esegui una query nello store multimediale.
Non dare per scontati i dettagli di implementazione relativi al numero di versione.
Eseguire query su una raccolta di contenuti multimediali
Per trovare contenuti multimediali che soddisfano un determinato insieme di condizioni, ad esempio una durata di 5 minuti o più, utilizza un'istruzione di selezione simile a SQL come quella mostrata nel seguente snippet di codice:
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)); } }
Quando esegui una query di questo tipo nella tua app, tieni presente quanto segue:
- Chiama il metodo
query()
in un thread di lavoro. - Memorizza nella cache gli indici delle colonne in modo da non dover chiamare
getColumnIndexOrThrow()
ogni volta che elabori una riga dal risultato della query. - Aggiungi l'ID all'URI contenuto come mostrato in questo esempio.
- I dispositivi con Android 10 e versioni successive richiedono nomi
di colonne definiti nell'API
MediaStore
. Se una libreria dipendente all'interno della tua app prevede un nome di colonna non definito nell'API, ad esempio"MimeType"
, utilizzaCursorWrapper
per tradurre dinamicamente il nome della colonna nel processo della tua app.
Carica le miniature dei file
Se la tua app mostra più file multimediali e chiede all'utente di sceglierne uno, è più efficiente caricare le versioni di anteprima o le miniature dei file anziché i file stessi.
Per caricare la miniatura di un determinato file multimediale, utilizza
loadThumbnail()
e specifica le dimensioni della miniatura che vuoi caricare, come mostrato nello
snippet di codice seguente:
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);
Aprire un file multimediale
La logica specifica che utilizzi per aprire un file multimediale dipende dal fatto che i contenuti multimediali siano rappresentati al meglio come descrittore di file, flusso di file o percorso diretto del file.
Descrittore del file
Per aprire un file multimediale utilizzando un descrittore di file, utilizza una logica simile a quella mostrata nel seguente snippet di codice:
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(); }
Stream di file
Per aprire un file multimediale utilizzando un flusso di file, utilizza una logica simile a quella mostrata nel seguente snippet di codice:
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". }
Percorsi file diretti
Per consentire alla tua app di funzionare in modo più fluido con le librerie multimediali di terze parti,
Android 11 (livello API 30) e versioni successive ti consentono di utilizzare API diverse dall'API
MediaStore
per accedere
ai file multimediali dallo spazio di archiviazione condiviso. Puoi invece accedere direttamente ai file multimediali
utilizzando una delle seguenti API:
- L'API
File
- Librerie native, ad esempio
fopen()
Se non disponi di autorizzazioni relative all'archiviazione, puoi accedere ai file nella tua directory specifica dell'app, nonché ai file multimediali attribuiti alla tua app utilizzando l'API File
.
Se la tua app tenta di accedere a un file utilizzando l'API File
e non dispone delle autorizzazioni necessarie, si verifica un FileNotFoundException
.
Per accedere ad altri file nello spazio di archiviazione condiviso su un dispositivo che esegue Android 10 (livello API 29), ti consigliamo di disattivare temporaneamente lo spazio di archiviazione
isolato impostando
requestLegacyExternalStorage
su true
nel file manifest dell'app. Per accedere ai file multimediali utilizzando
i metodi di file nativi su Android 10, devi anche richiedere l'autorizzazione
READ_EXTERNAL_STORAGE
.
Considerazioni sull'accesso ai contenuti multimediali
Quando accedi ai contenuti multimediali, tieni presente le considerazioni trattate nelle sezioni seguenti.
Dati memorizzati nella cache
Se la tua app memorizza nella cache URI o dati dal media store, controlla periodicamente gli aggiornamenti del media store. Questo controllo consente di mantenere sincronizzati i dati memorizzati nella cache lato app con i dati del fornitore lato sistema.
Prestazioni
Quando esegui letture sequenziali di file multimediali utilizzando percorsi di file diretti, le
prestazioni sono paragonabili a quelle dell'API
MediaStore
.
Quando esegui letture e scritture casuali di file multimediali utilizzando percorsi di file diretti, tuttavia, il processo può essere fino a due volte più lento. In queste situazioni, ti consigliamo di utilizzare l'API MediaStore
.
Colonna DATA
Quando accedi a un file multimediale esistente, puoi utilizzare il valore della colonna
DATA
nella
tua logica. Questo perché questo valore ha un percorso del file valido. Tuttavia, non
presupporre che il file sia sempre disponibile. Preparati a gestire eventuali errori di I/O basati su file.
Per creare o aggiornare un file multimediale, invece, non utilizzare il valore della colonna
DATA
. Utilizza invece i valori delle colonne
DISPLAY_NAME
e
RELATIVE_PATH
.
Volumi di archiviazione
Le app che hanno come target Android 10 o versioni successive possono accedere al nome univoco che il sistema assegna a ogni volume di archiviazione esterno. Questo sistema di denominazione ti aiuta a organizzare e indicizzare in modo efficiente i contenuti e ti consente di controllare dove vengono archiviati i nuovi file multimediali.
I seguenti volumi sono particolarmente utili da tenere a mente:
- Il volume
VOLUME_EXTERNAL
fornisce una visualizzazione di tutti i volumi di archiviazione condivisi sul dispositivo. Puoi leggere i contenuti di questo volume sintetico, ma non puoi modificarli. - Il
VOLUME_EXTERNAL_PRIMARY
volume rappresenta il volume di archiviazione condiviso principale sul dispositivo. Puoi leggere e modificare i contenuti di questo volume.
Puoi scoprire altri volumi chiamando
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();
Luogo in cui sono stati acquisiti i contenuti multimediali
Alcune fotografie e alcuni video contengono informazioni sulla posizione nei metadati, che mostrano il luogo in cui è stata scattata una fotografia o in cui è stato registrato un video.
Il modo in cui accedi a queste informazioni sulla posizione nella tua app dipende dal fatto che tu debba accedere alle informazioni sulla posizione di una fotografia o di un video.
Fotografie
Se la tua app utilizza lo spazio di archiviazione isolato, il sistema nasconde le informazioni sulla posizione per impostazione predefinita. Per accedere a queste informazioni, segui questi passaggi:
- Richiedi l'autorizzazione
ACCESS_MEDIA_LOCATION
nel file manifest della tua app. Dall'oggetto
MediaStore
, recupera i byte esatti della fotografia chiamandosetRequireOriginal()
e passando l'URI della fotografia, come mostrato nello snippet di codice seguente: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]; }
Video
Per accedere alle informazioni sulla posizione all'interno dei metadati di un video, utilizza la classe
MediaMetadataRetriever
come mostrato nello snippet di codice seguente. La tua app non deve richiedere
autorizzazioni aggiuntive per utilizzare questa classe.
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); }
Condivisione
Alcune app consentono agli utenti di condividere file multimediali. Ad esempio, le app di social media consentono agli utenti di condividere foto e video con gli amici.
Per condividere file multimediali, utilizza un URI content://
, come consigliato nella guida alla
creazione di un content provider.
Attribuzione app dei file multimediali
Quando l'archiviazione isolata è attivata per un'app che ha come target Android 10 o versioni successive, il sistema attribuisce un'app a ogni file multimediale, il che determina i file a cui la tua app può accedere quando non ha richiesto alcuna autorizzazione di archiviazione. Ogni file può essere attribuito a una sola app. Pertanto, se la tua app crea un file multimediale archiviato nella raccolta multimediale di foto, video o file audio, la tua app ha accesso al file.
Se l'utente disinstalla e reinstalla la tua app, devi richiedere
READ_EXTERNAL_STORAGE
l'accesso ai file creati originariamente dalla tua app. Questa richiesta di autorizzazione è
necessaria perché il sistema considera il file attribuito alla
versione dell'app installata in precedenza, anziché a quella appena installata.
Quando un'app che ha come target l'SDK 36 o versioni successive su dispositivi con Android 16 o versioni successive chiede le autorizzazioni per foto e video, gli utenti che scelgono di limitare l'accesso ai contenuti multimediali selezionati vedranno le foto di proprietà dell'app preselezionate nel selettore di foto. Gli utenti possono deselezionare uno qualsiasi di questi elementi preselezionati, il che revoca l'accesso dell'app a queste foto e video.
Aggiungere un articolo
Per aggiungere un elemento multimediale a una raccolta esistente, utilizza un codice simile al seguente. Questo snippet di codice accede al volume VOLUME_EXTERNAL_PRIMARY
sui dispositivi con Android 10 o versioni successive. Questo perché su questi dispositivi puoi
modificare i contenuti di un volume solo se è il volume principale, come
descritto nella sezione Volumi di archiviazione.
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);
Attivare/disattivare lo stato In attesa per i file multimediali
Se la tua app esegue operazioni potenzialmente dispendiose in termini di tempo, come la scrittura su
file multimediali, è utile avere l'accesso esclusivo al file durante l'elaborazione. Sui dispositivi con Android 10 o versioni successive, la tua app può
ottenere questo accesso esclusivo impostando il valore del
flag IS_PENDING
su 1. Solo la tua app può visualizzare il file finché non modifica il valore di
IS_PENDING
riportandolo a 0.
Il seguente snippet di codice si basa su quello precedente. Questo
snippet mostra come utilizzare il flag IS_PENDING
quando memorizzi un brano lungo nella
directory corrispondente alla raccolta 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);
Fornire un suggerimento per la posizione del file
Quando la tua app memorizza contenuti multimediali su un dispositivo con Android 10, per
impostazione predefinita i contenuti multimediali vengono organizzati in base al tipo. Ad esempio, per impostazione predefinita i nuovi file immagine vengono inseriti nella directory Environment.DIRECTORY_PICTURES
, che corrisponde alla raccolta MediaStore.Images
.
Se la tua app conosce una posizione specifica in cui possono essere archiviati i file, ad esempio un album fotografico chiamato Pictures/MyVacationPictures
, puoi impostare MediaColumns.RELATIVE_PATH
per fornire al sistema un suggerimento su dove archiviare i file appena scritti.
Aggiornare un elemento
Per aggiornare un file multimediale di proprietà della tua app, utilizza un codice simile al seguente:
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);
Se lo spazio di archiviazione isolato non è disponibile o non è attivato, la procedura mostrata nello snippet di codice precedente funziona anche per i file che non sono di proprietà della tua app.
Aggiornamento nel codice nativo
Se devi scrivere file multimediali utilizzando librerie native, passa il descrittore di file associato dal codice basato su Java o Kotlin al codice nativo.
Il seguente snippet di codice mostra come passare il descrittore di file di un oggetto multimediale nel codice nativo della tua app:
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. }
Aggiornare i file multimediali di altre app
Se la tua app utilizza lo storage isolato, in genere non può aggiornare un file multimediale a cui ha contribuito un'altra app nel media store.
Puoi ottenere il consenso dell'utente per modificare il file intercettando
l'RecoverableSecurityException
generato dalla piattaforma. Puoi quindi richiedere all'utente di concedere alla tua app
l'accesso in scrittura a quell'elemento specifico, come mostrato nel seguente snippet di codice:
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); } }
Completa questa procedura ogni volta che la tua app deve modificare un file multimediale che non ha creato.
In alternativa, se la tua app viene eseguita su Android 11 o versioni successive, puoi
consentire agli utenti di concedere alla tua app l'accesso in scrittura a un gruppo di file multimediali. Utilizza il metodo
createWriteRequest()
, come descritto nella sezione su come gestire i gruppi di file multimediali.
Se la tua app ha un altro caso d'uso non coperto dallo spazio di archiviazione isolato, invia una richiesta di funzionalità e disattiva temporaneamente lo spazio di archiviazione isolato.
Rimuovere un elemento
Per rimuovere un elemento che la tua app non richiede più nell'archivio multimediale, utilizza una logica simile a quella mostrata nel seguente snippet di codice:
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);
Se lo spazio di archiviazione isolato non è disponibile o non è attivato, puoi utilizzare lo snippet di codice precedente per rimuovere i file di proprietà di altre app. Se è attivato lo spazio di archiviazione isolato,
tuttavia, devi rilevare un RecoverableSecurityException
per ogni file che
la tua app vuole rimuovere, come descritto nella sezione relativa all'aggiornamento degli elementi
multimediali.
Se la tua app viene eseguita su Android 11 o versioni successive, puoi consentire agli utenti
di scegliere un gruppo di file multimediali da rimuovere. Utilizza il metodo createTrashRequest()
o il metodo createDeleteRequest()
, come descritto nella sezione su come gestire i gruppi di file multimediali.
Se la tua app ha un altro caso d'uso non coperto dallo spazio di archiviazione isolato, invia una richiesta di funzionalità e disattiva temporaneamente lo spazio di archiviazione isolato.
Rilevare gli aggiornamenti ai file multimediali
La tua app potrebbe dover identificare i volumi di archiviazione contenenti file multimediali che le app
hanno aggiunto o modificato rispetto a un momento precedente. Per rilevare queste modifiche
nel modo più affidabile, passa il volume di archiviazione di interesse in
getGeneration()
.
Finché la versione dello store multimediale non cambia, il valore restituito di questo
metodo aumenta in modo monotono nel tempo.
In particolare, getGeneration()
è più solido delle date nelle colonne dei contenuti multimediali,
come
DATE_ADDED
e DATE_MODIFIED
.
Questo perché i valori delle colonne dei contenuti multimediali possono cambiare quando un'app chiama
setLastModified()
o quando
l'utente modifica l'orologio di sistema.
Gestire i gruppi di file multimediali
Su Android 11 e versioni successive, puoi chiedere all'utente di selezionare un gruppo di file multimediali, quindi aggiornarli in una singola operazione. Questi metodi offrono una migliore coerenza tra i dispositivi e semplificano la gestione delle raccolte multimediali per gli utenti.
I metodi che forniscono questa funzionalità di "aggiornamento batch" includono quanto segue:
createWriteRequest()
- Richiedi all'utente di concedere alla tua app l'accesso in scrittura al gruppo specificato di file multimediali.
createFavoriteRequest()
- Chiedi all'utente di contrassegnare i file multimediali specificati come alcuni dei suoi contenuti multimediali "preferiti" sul dispositivo. Qualsiasi app con accesso in lettura a questo file può vedere che l'utente ha contrassegnato il file come "preferito".
createTrashRequest()
Chiedi all'utente di inserire i file multimediali specificati nel cestino del dispositivo. Gli elementi nel cestino vengono eliminati definitivamente dopo un periodo di tempo definito dal sistema.
createDeleteRequest()
Richiedi all'utente di eliminare immediatamente i file multimediali specificati in modo definitivo, senza inserirli prima nel cestino.
Dopo aver chiamato uno di questi metodi, il sistema crea un oggetto
PendingIntent
. Dopo che la tua app
richiama questo intent, gli utenti vedono una finestra di dialogo che richiede il loro consenso per l'aggiornamento o l'eliminazione dei file multimediali specificati da parte della tua app.
Ad esempio, ecco come strutturare una chiamata a 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);
Valuta la risposta dell'utente. Se l'utente ha fornito il consenso, procedi con l'operazione multimediale. In caso contrario, spiega all'utente perché la tua app ha bisogno dell'autorizzazione:
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. */ } } }
Puoi utilizzare questo stesso pattern generale con
createFavoriteRequest()
,
createTrashRequest()
,
e
createDeleteRequest()
.
Autorizzazione per la gestione dei contenuti multimediali
Gli utenti potrebbero considerare una determinata app affidabile per la gestione dei contenuti multimediali, ad esempio per apportare modifiche frequenti ai file multimediali. Se la tua app ha come target Android 11 o versioni successive e non è l'app galleria predefinita del dispositivo, devi mostrare una finestra di dialogo di conferma all'utente ogni volta che la tua app tenta di modificare o eliminare un file.
Se la tua app ha come target Android 12 (livello API 31) o versioni successive, puoi richiedere agli utenti di concedere alla tua app l'accesso all'autorizzazione speciale gestione contenuti multimediali. Questa autorizzazione consente alla tua app di eseguire ognuna delle seguenti operazioni senza dover chiedere all'utente per ogni operazione sui file:
- Modifica i file utilizzando
createWriteRequest()
. - Spostare i file dentro e fuori dal cestino utilizzando
createTrashRequest()
. - Elimina i file utilizzando
createDeleteRequest()
.
Per farlo, segui questi passaggi:
Dichiara l'autorizzazione
MANAGE_MEDIA
e l'autorizzazioneREAD_EXTERNAL_STORAGE
nel file manifest della tua app.Per chiamare
createWriteRequest()
senza mostrare una finestra di dialogo di conferma, dichiara anche l'autorizzazioneACCESS_MEDIA_LOCATION
.Nella tua app, mostra all'utente una UI per spiegare perché potrebbe voler concedere l'accesso alla gestione dei contenuti multimediali alla tua app.
Richiama l'azione di intent
ACTION_REQUEST_MANAGE_MEDIA
. Gli utenti vengono indirizzati alla schermata App di gestione dei contenuti multimediali nelle impostazioni di sistema. Da qui, gli utenti possono concedere l'accesso speciale all'app.
Casi d'uso che richiedono un'alternativa al negozio di contenuti multimediali
Se la tua app svolge principalmente uno dei seguenti ruoli, valuta un'alternativa alle API MediaStore
.
Lavorare con altri tipi di file
Se la tua app funziona con documenti e file che non contengono esclusivamente contenuti multimediali, ad esempio file che utilizzano l'estensione EPUB o PDF, utilizza l'azione intent ACTION_OPEN_DOCUMENT
, come descritto nella guida alla memorizzazione e all'accesso a documenti e altri file.
Condivisione di file nelle app complementari
Nei casi in cui fornisci una suite di app complementari, ad esempio un'app di messaggistica e
un'app di profilo, configura la condivisione di file
utilizzando gli URI content://
. Consigliamo questo flusso di lavoro anche come best practice di sicurezza.
Risorse aggiuntive
Per saperne di più su come archiviare e accedere ai contenuti multimediali, consulta le seguenti risorse.
Campioni
- MediaStore, disponibile su GitHub