Accedi ai file multimediali dallo spazio di archiviazione condiviso

Per offrire un'esperienza utente migliore, molte app consentono agli utenti di contribuire e di accedere ai contenuti multimediali disponibili su un volume di archiviazione esterno. Il framework fornisce un indice ottimizzato nelle raccolte multimediali, chiamate media store, che consente agli utenti di recuperare e aggiornare più facilmente questi file multimediali. Anche dopo la disinstallazione, questi file rimangono sul dispositivo dell'utente.

Selettore di foto

In alternativa all'utilizzo del media store, lo strumento di selezione delle foto Android offre un modo sicuro e integrato con cui gli utenti possono selezionare i file multimediali senza dover concedere alla tua app l'accesso all'intera raccolta multimediale. Questa opzione è disponibile solo sui dispositivi supportati. Per ulteriori informazioni, consulta la guida del selettore di foto.

Media store

Per interagire con l'astrazione del Media Store, 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 unità di archiviazione esterna e aggiunge i file multimediali alle seguenti raccolte ben definite:

  • Immagini,incluse fotografie e screenshot, archiviate nelle directory DCIM/ e Pictures/. Il sistema aggiunge questi file alla tabella MediaStore.Images.
  • Video, memorizzati nelle directory DCIM/, Movies/ e Pictures/. Il sistema aggiunge questi file alla tabella MediaStore.Video.
  • File audio, archiviati nelle directory Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ e Ringtones/. Inoltre, il sistema riconosce le playlist audio che si trovano nelle directory Music/ o Movies/ e le registrazioni vocali che si trovano nella directory Recordings/. Il sistema aggiunge questi file alla tabella MediaStore.Audio. La directory Recordings/ 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 tabella MediaStore.Downloads. Questa tabella non è disponibile su Android 9 (livello API 28) e versioni precedenti.

Il media store include anche una raccolta chiamata MediaStore.Files. I suoi contenuti dipendono dall'utilizzo dello spazio di archiviazione basato su ambito da parte dell'app, disponibile per le app destinate ad Android 10 o versioni successive.

  • Se lo spazio di archiviazione limitato è attivato, la raccolta mostra solo le foto, i video e i file audio creati dalla tua app. La maggior parte degli sviluppatori non deve utilizzare MediaStore.Files per visualizzare i file multimediali di altre app, ma se hai un requisito specifico in tal senso, puoi dichiarare l'autorizzazione READ_EXTERNAL_STORAGE. Tuttavia, ti consigliamo di utilizzare le API MediaStore per aprire file non creati dalla tua app.
  • Se l'archiviazione con ambito non è disponibile o non viene utilizzata, la raccolta mostra tutti i tipi di file multimediali.

Richiedi le autorizzazioni necessarie

Prima di eseguire operazioni sui file multimediali, assicurati che l'app abbia dichiarato le autorizzazioni necessarie per accedere a questi file. Fai attenzione, però, a non dichiarare autorizzazioni di cui l'app non ha bisogno o che non utilizza.

Autorizzazioni per lo spazio di archiviazione

La necessità di un'app per accedere allo spazio di archiviazione varia a seconda che l'app acceda solo ai propri file multimediali o ai file creati da altre app.

Accedere ai tuoi file multimediali

Sui dispositivi con Android 10 o versioni successive, non sono necessarie le autorizzazioni relative allo spazio di archiviazione per accedere e modificare i file multimediali di proprietà della tua app, inclusi i file nella raccolta MediaStore.Downloads. Ad esempio, se stai sviluppando un'app per fotocamera, non è necessario richiedere le autorizzazioni relative allo spazio di archiviazione per accedere alle foto scattate perché la tua app è proprietaria delle immagini che stai scrivendo nel media store.

Accesso ai file multimediali di altre app

Per accedere ai file multimediali creati da altre app, devi dichiarare le autorizzazioni appropriate relative allo spazio di archiviazione e i file devono risiedere in una delle seguenti raccolte di contenuti multimediali:

Finché un file è visibile dalle query MediaStore.Images, MediaStore.Video o MediaStore.Audio, sarà visibile anche utilizzando la query MediaStore.Files.

Lo snippet di codice riportato di seguito mostra come dichiarare le autorizzazioni appropriate per l'archiviazione:

<!-- 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 precedenti

Se la tua app viene usata su un dispositivo con Android 9 o versioni precedenti o se la tua app ha temporaneamente disattivato lo spazio di archiviazione nell'ambito, devi richiedere l'autorizzazione READ_EXTERNAL_STORAGE per accedere a qualsiasi file multimediale. Per modificare i file multimediali, devi richiedere anche l'autorizzazione WRITE_EXTERNAL_STORAGE.

Storage Access Framework è necessario 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 l'app, devi usare Storage Access Framework. Per scoprire 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 oscurati dalle foto, devi dichiarare l'autorizzazione ACCESS_MEDIA_LOCATION nel file manifest dell'app, quindi richiedere questa autorizzazione in fase di runtime.

Verificare la presenza di aggiornamenti del media store

Per accedere ai file multimediali in modo più affidabile, in particolare se l'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 della presenza di aggiornamenti, chiama getVersion(). La versione restituita è una stringa univoca che cambia ogni volta che l'archivio multimediale cambia sostanzialmente. Se la versione restituita è diversa dall'ultima versione sincronizzata, ripeti la scansione e la sincronizzazione della cache multimediale dell'app.

Completa questa verifica al momento dell'avvio del processo dell'app. Non è necessario controllare la versione ogni volta che esegui una query sul media store.

Non dare per scontato che i dettagli dell'implementazione siano relativi al numero di versione.

Esegui una query su una raccolta di contenuti multimediali

Per trovare i contenuti multimediali che soddisfano un determinato insieme di condizioni, ad esempio una durata di almeno 5 minuti, utilizza un'istruzione di selezione di tipo SQL simile a 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 worker.
  • 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 di contenuto, come mostrato in questo esempio.
  • I dispositivi con Android 10 e versioni successive richiedono nomi delle colonne definiti nell'API MediaStore. Se una libreria dipendente all'interno dell'app prevede un nome di colonna non definito nell'API, ad esempio "MimeType", utilizza CursorWrapper per tradurre in modo dinamico il nome della colonna nella procedura dell'app.

Carica le miniature dei file

Se la tua app mostra più file multimediali e richiede all'utente di scegliere uno di questi file, è più efficiente caricare le versioni in anteprima (o miniature) dei file anziché i file stessi.

Per caricare la miniatura per un determinato file multimediale, utilizza loadThumbnail() e trasmetti le dimensioni della miniatura che vuoi caricare, come mostrato nel seguente snippet di codice:

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 da utilizzare per aprire un file multimediale dipende dal fatto che i contenuti multimediali siano rappresentati al meglio come descrittore di file, stream di file o percorso file diretto.

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 tramite uno stream di file, utilizza una logica simile a quella mostrata nello snippet di codice riportato di seguito:

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 facilitare il funzionamento della tua app 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 accedere ai file multimediali direttamente utilizzando una delle seguenti API:

  • L'API File
  • Librerie native, come fopen()

Se non disponi di autorizzazioni relative allo spazio di archiviazione, puoi accedere ai file nella directory specifica dell'app e 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 con Android 10 (livello API 29), ti consigliamo di disattivare temporaneamente lo spazio di archiviazione limitato impostando requestLegacyExternalStorage su true nel file manifest della tua app. Per accedere ai file multimediali utilizzando i metodi dei file nativi su Android 10, devi richiedere anche l'autorizzazione READ_EXTERNAL_STORAGE.

Considerazioni sull'accesso ai contenuti multimediali

Quando accedi ai contenuti multimediali, tieni presente le considerazioni descritte nelle sezioni seguenti.

Dati memorizzati nella cache

Se l'app memorizza nella cache URI o dati del media store, verifica periodicamente la presenza di aggiornamenti al media store. Questo controllo consente ai dati memorizzati nella cache lato app di rimanere sincronizzati con i dati del provider lato sistema.

Esibizione

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 diretti, il processo può risultare fino a due volte più lento. In queste situazioni, consigliamo di utilizzare l'API MediaStore.

Colonna DATA

Quando accedi a un file multimediale esistente, puoi utilizzare il valore della colonna DATA nella logica. Il motivo è che questo valore ha un percorso file valido. Tuttavia, non dare per scontato 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 destinate ad Android 10 o versioni successive possono accedere al nome univoco assegnato dal sistema a ciascun volume di archiviazione esterna. Questo sistema di denominazione consente di organizzare e indicizzare in modo efficiente i contenuti e di controllare dove sono archiviati i nuovi file multimediali.

I seguenti volumi sono particolarmente utili da tenere presente:

  • 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 modificarne i contenuti.
  • Il volume VOLUME_EXTERNAL_PRIMARY rappresenta il volume di archiviazione condiviso principale sul dispositivo. Puoi leggere e modificare i contenuti di questo volume.

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

Località in cui sono stati acquisiti i contenuti multimediali

Alcuni video e fotografie contengono informazioni sulla posizione nei metadati, che mostrano il luogo in cui è stata scattata una foto o dove è stato registrato un video.

La modalità di accesso a queste informazioni sulla posizione nella tua app varia a seconda che tu debba o meno accedere ai dati sulla posizione per una foto o per un video.

Fotografie

Se l'app utilizza l'archiviazione mirata, il sistema nasconde le informazioni sulla posizione per impostazione predefinita. Per accedere a queste informazioni, completa questi passaggi:

  1. Richiedi l'autorizzazione ACCESS_MEDIA_LOCATION nel file manifest dell'app.
  2. Dal tuo oggetto MediaStore, ricava i byte esatti della fotografia chiamando setRequireOriginal() e passando l'URI della fotografia, come mostrato nel seguente snippet di codice:

    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 nei metadati di un video, utilizza la classe MediaMetadataRetriever, come mostrato nello snippet di codice riportato di seguito. L'app non deve richiedere autorizzazioni aggiuntive per usare questo corso.

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 tra loro. 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 fornitore di contenuti.

Attribuzione dei file multimediali per le app

Quando lo spazio di archiviazione basato su ambito è attivato per un'app destinata ad Android 10 o versioni successive, il sistema attribuisce un'app a ogni file multimediale e questo determina i file a cui l'app può accedere quando non ha richiesto autorizzazioni per lo spazio di archiviazione. Ogni file può essere attribuito a una sola app. Di conseguenza, se l'app crea un file multimediale archiviato nella raccolta di foto, video o file audio, l'app ha accesso a quel file.

Se l'utente disinstalla e reinstalla la tua app, tuttavia, devi richiedere a READ_EXTERNAL_STORAGE di accedere ai file creati in origine dall'app. Questa richiesta di autorizzazione è obbligatoria perché il sistema considera il file attribuito alla versione dell'app installata in precedenza, anziché a quella appena installata.

Aggiungere un articolo

Per aggiungere un elemento multimediale a una raccolta esistente, utilizza un codice simile al seguente. Questo snippet di codice consente di accedere al volume VOLUME_EXTERNAL_PRIMARY sui dispositivi con Android 10 o versioni successive. Il motivo è che su questi dispositivi puoi modificare i contenuti di un volume solo se si tratta del 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);

Attiva/disattiva 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 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 viene ripristinato il valore di IS_PENDING su 0.

Il seguente snippet di codice si basa sullo snippet di codice 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);

Suggerisci la posizione del file

Quando la tua app archivia i 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 è possibile archiviare i file, ad esempio un album fotografico chiamato Pictures/MyVacationPictures, puoi impostare MediaColumns.RELATIVE_PATH in modo da fornire al sistema un suggerimento su dove archiviare i nuovi file scritti.

Aggiornare un articolo

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 l'archiviazione con ambito non è disponibile o non è abilitata, il processo mostrato nello snippet di codice precedente funziona anche per i file che non sono di proprietà della tua app.

Aggiorna nel codice nativo

Se devi scrivere file multimediali utilizzando librerie native, passa il descrittore del file associato al file dal codice basato su Java o Kotlin al tuo codice nativo.

Il seguente snippet di codice mostra come passare il descrittore di file di un oggetto multimediale nel codice nativo dell'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 l'app utilizza l'archiviazione con ambito, in genere non è in grado di aggiornare un file multimediale che un'app diversa ha contribuito al media store.

Tuttavia, puoi ottenere il consenso dell'utente per modificare il file rilevando il valore 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 l'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 all'app l'accesso in scrittura a un gruppo di file multimediali. Utilizza il metodo createWriteRequest(), come descritto nella sezione su come gestire gruppi di file multimediali.

Se la tua app ha un altro caso d'uso che non è coperto dallo spazio di archiviazione con ambito, invia una richiesta di funzionalità e disattiva temporaneamente lo spazio di archiviazione con ambito.

Rimuovere un elemento

Per rimuovere un elemento di cui la tua app non ha più bisogno nel media store, 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 l'archiviazione con ambito non è disponibile o non è attiva, puoi utilizzare lo snippet di codice precedente per rimuovere i file di proprietà di altre app. Se è abilitata l'archiviazione con ambito, devi però recuperare un RecoverableSecurityException per ogni file che l'app vuole rimuovere, come descritto nella sezione sull'aggiornamento degli elementi multimediali.

Se la tua app funziona su Android 11 o versioni successive, puoi consentire agli utenti di scegliere un gruppo di file multimediali da rimuovere. Utilizza il metodo createTrashRequest() o createDeleteRequest(), come descritto nella sezione su come gestire gruppi di file multimediali.

Se la tua app ha un altro caso d'uso che non è coperto dallo spazio di archiviazione con ambito, invia una richiesta di funzionalità e disattiva temporaneamente lo spazio di archiviazione con ambito.

Rileva gli aggiornamenti dei 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 in modo più affidabile, trasferisci il volume di archiviazione che ti interessa a getGeneration(). Finché la versione del media store non cambia, il valore restituito di questo metodo aumenta monotonicamente nel tempo.

In particolare, getGeneration() è più efficace delle date nelle colonne multimediali, ad esempio DATE_ADDED e DATE_MODIFIED. Questo perché i valori delle colonne multimediali possono cambiare quando un'app chiama setLastModified() o quando l'utente modifica l'orologio di sistema.

Gestire gruppi di file multimediali

Su Android 11 e versioni successive, puoi chiedere all'utente di selezionare un gruppo di file multimediali e di aggiornarli in una singola operazione. Questi metodi offrono una migliore coerenza tra i dispositivi e semplificano la gestione delle raccolte di contenuti multimediali per gli utenti.

I metodi che forniscono questa funzionalità di "aggiornamento collettivo" includono quanto segue:

createWriteRequest()
Richiedi all'utente di concedere alla tua app l'accesso in scrittura al gruppo di file multimediali specificato.
createFavoriteRequest()
Richiedi all'utente di contrassegnare i file multimediali specificati come alcuni dei suoi contenuti multimediali "preferiti" sul dispositivo. Qualsiasi app con accesso in lettura al file può vedere che l'utente lo ha contrassegnato come "preferito".
createTrashRequest()

Richiedi all'utente di posizionare i file multimediali specificati nel cestino del dispositivo. Gli elementi nel cestino vengono eliminati definitivamente dopo un periodo di tempo definito dal sistema.

createDeleteRequest()

Chiedi all'utente di eliminare immediatamente e in modo definitivo i file multimediali specificati, senza spostarli preventivamente nel cestino.

Dopo aver chiamato uno di questi metodi, il sistema crea un oggetto PendingIntent. Dopo che l'app richiama questo intent, gli utenti visualizzano una finestra di dialogo che richiede il consenso all'aggiornamento o all'eliminazione dei file multimediali specificati per l'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 dato il consenso, procedi con l'operazione con i contenuti multimediali. 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 lo stesso pattern generale con createFavoriteRequest(), createTrashRequest() e createDeleteRequest().

Autorizzazione per la gestione dei contenuti multimediali

Gli utenti potrebbero considerare attendibile una determinata app per eseguire la gestione dei contenuti multimediali, ad esempio 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 l'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'autorizzazione speciale per la gestione dei contenuti multimediali. Questa autorizzazione consente alla tua app di eseguire le seguenti operazioni senza dover chiedere all'utente ogni operazione relativa ai file:

Per farlo, segui questi passaggi:

  1. Dichiara l'autorizzazione MANAGE_MEDIA e l'autorizzazione READ_EXTERNAL_STORAGE nel file manifest dell'app.

    Per chiamare createWriteRequest() senza mostrare una finestra di dialogo di conferma, dichiara anche l'autorizzazione ACCESS_MEDIA_LOCATION.

  2. Nella tua app, mostra all'utente una UI per spiegare perché potrebbe voler concedere alla tua app l'accesso alla gestione dei media.

  3. Richiama l'azione dell'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 media store

Se la tua app esegue 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 includono esclusivamente contenuti multimediali, ad esempio file che utilizzano l'estensione file EPUB o PDF, utilizza l'azione intent ACTION_OPEN_DOCUMENT, come descritto nella guida per archiviare e accedere a documenti e altri file.

Condivisione di file nelle app companion

Se fornisci una suite di app companion, ad esempio un'app di messaggistica e un'app di profilo, configura la condivisione di file utilizzando gli URI content://. Questo flusso di lavoro è consigliato anche come best practice per la sicurezza.

Risorse aggiuntive

Per ulteriori informazioni su come archiviare e accedere ai contenuti multimediali, consulta le risorse seguenti.

Campioni

Video