Acessar arquivos de mídia do armazenamento compartilhado

Para oferecer uma experiência melhor ao usuário, muitos apps permitem que os eles contribuam e acessem a mídia disponível em um volume de armazenamento externo. O framework fornece um índice otimizado em coleções de mídia, chamado armazenamento de mídia, que permite aos usuários recuperar e atualizar esses arquivos com mais facilidade. Mesmo após a desinstalação do app, esses arquivos permanecem no dispositivo do usuário.

Seletor de fotos

Como alternativa ao uso do armazenamento de mídia, o seletor de fotos para Android oferece uma maneira segura e integrada para os usuários selecionarem arquivos de mídia sem precisar conceder ao app acesso a toda a biblioteca de mídia. Esse recurso está disponível apenas nos dispositivos com suporte. Para mais informações, consulte o guia Seletor de fotos.

Armazenamento de mídia

Para interagir com a abstração do armazenamento de mídia, use um objeto ContentResolver extraído do contexto do 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.
}

O sistema verifica automaticamente um volume de armazenamento externo e adiciona arquivos de mídia a estas coleções bem definidas:

  • Imagens, incluindo fotos e capturas de tela, armazenadas nos diretórios DCIM/ e Pictures/. O sistema adiciona esses arquivos à tabela MediaStore.Images.
  • Vídeos, armazenados nos diretórios DCIM/, Movies/ e Pictures/. O sistema adiciona esses arquivos à tabela MediaStore.Video.
  • Arquivos de áudio, armazenados nos diretórios Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ e Ringtones/. Além disso, o sistema reconhece playlists de áudio que estão nos diretórios Music/ ou Movies/, assim como as gravações de voz que estão no diretório Recordings/. O sistema adiciona esses arquivos à tabela MediaStore.Audio. O diretório Recordings/ não está disponível no Android 11 (nível 30 da API) e versões anteriores.
  • Arquivos transferidos por download, armazenados no diretório Download/. Em dispositivos com o Android 10 (API de nível 29) e versões mais recentes, esses arquivos são armazenados na tabela MediaStore.Downloads. Essa tabela não está disponível no Android 9 (nível 28 da API) e versões anteriores.

O armazenamento de mídia também inclui uma coleção com o nome MediaStore.Files. O conteúdo dela depende do app usar ou não o armazenamento com escopo, disponível em apps destinados ao Android 10 ou versões mais recentes.

  • Se o armazenamento com escopo estiver ativado, a coleção mostra apenas as fotos, vídeos e arquivos de áudio criados pelo app. A maioria dos desenvolvedores não precisa usar MediaStore.Files para ver arquivos de mídia de outros apps, mas, se você tiver um motivo específico para fazer isso, é possível declarar a permissão READ_EXTERNAL_STORAGE. No entanto, recomendamos que você use as APIs MediaStore para abrir arquivos que não foram criados pelo app.
  • Se o armazenamento com escopo estiver indisponível ou não estiver sendo usado, a coleção mostra todos os tipos de arquivo de mídia.

Solicitar as permissões necessárias

Antes de executar operações em arquivos de mídia, verifique se o app declarou as permissões necessárias para acessar esses arquivos. No entanto, tenha cuidado para não declarar permissões de que seu app não precisa ou que não usa.

Permissões de armazenamento

A permissão para seu app acessar o armazenamento depende se ele acessa apenas os próprios arquivos de mídia ou arquivos criados por outros apps.

Acessar seus próprios arquivos de mídia

Em dispositivos com o Android 10 ou versões mais recentes, você não precisa de permissões relacionadas ao armazenamento para acessar e modificar arquivos de mídia que pertencem ao seu app, incluindo arquivos na coleção MediaStore.Downloads. Caso esteja desenvolvendo um app de câmera, por exemplo, não é necessário solicitar permissões relacionadas ao armazenamento para acessar as fotos tiradas, porque o app é proprietário das imagens que você está gravando na mídia.

Acessar arquivos de mídia de outros apps

Para acessar arquivos de mídia criados por outros apps, é necessário declarar as permissões adequadas relacionadas ao armazenamento. Os arquivos precisam residir em uma das coleções de mídia abaixo:

Quando um arquivo fica visível nas consultas MediaStore.Images, MediaStore.Video ou MediaStore.Audio, ele também pode ser visualizado usando a MediaStore.Files.

O snippet de código abaixo demonstra como declarar as permissões de armazenamento adequadas:

<!-- 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" />

Permissões extras necessárias para apps executados em dispositivos legados

Ao usar o app em um dispositivo com o Android 9 ou versões anteriores ou caso ele tenha desativado temporariamente o armazenamento com escopo, é necessário solicitar a permissão READ_EXTERNAL_STORAGE para acessar arquivos de mídia. Se quiser modificar arquivos de mídia, também é preciso solicitar a permissão WRITE_EXTERNAL_STORAGE.

Framework de acesso ao armazenamento necessário para acessar downloads de outros apps

Mais especificamente, caso o app queira acessar um arquivo da coleção MediaStore.Downloads que ele não criou, é necessário usar o framework de acesso ao armazenamento. Para saber mais sobre como usar esse framework, consulte Acessar documentos e outros arquivos do armazenamento compartilhado.

Permissão de localização de mídia

Se o app for destinado ao Android 10 (nível 29 da API) ou versões mais recentes e precisar recuperar metadados EXIF não editados de fotos, declare a permissão ACCESS_MEDIA_LOCATION no arquivo de manifesto do seu app e solicite essa permissão no tempo de execução.

Verificar se há atualizações no armazenamento de mídia

Para acessar arquivos de mídia de maneira mais confiável, principalmente se o app armazenar URIs ou dados do armazenamento de mídia em cache, verifique se a versão do armazenamento foi modificada desde a última sincronização dos dados de mídia. Para verificar se há atualizações, chame getVersion(). A versão retornada é uma string única que muda sempre que o armazenamento de mídia é consideravelmente modificado. Se a versão retornada for diferente da última versão sincronizada, busque e sincronize novamente o cache de mídia do app.

Conclua essa verificação durante a inicialização do processo do app. Não é necessário verificar a versão todas as vezes que o app consultar o armazenamento de mídia.

No entanto, é importante não pressupor quais são os detalhes de implementação relacionados ao número da versão.

Consultar uma coleção de mídia

Para encontrar uma mídia que esteja de acordo com um determinado conjunto de condições, por exemplo, duração de cinco minutos ou mais, use uma declaração de seleção SQL semelhante à mostrada no snippet de código a seguir.

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));
    }
}

Ao realizar essa consulta no seu app, lembre-se do seguinte:

  • Chame o método query() em uma linha de execução de worker.
  • Armazene os índices de coluna em cache para não precisar chamar getColumnIndexOrThrow() sempre que processar uma linha do resultado da consulta.
  • Anexe o ID ao URI de conteúdo, conforme mostrado neste exemplo.
  • Os dispositivos com o Android 10 e versões mais recentes exigem nomes de coluna definidos na API MediaStore. Se uma biblioteca dependente do seu app espera um nome de coluna indefinido na API, como "MimeType", use CursorWrapper para traduzir o nome da coluna de forma dinâmica no processo do app.

Carregar miniaturas de arquivos

Caso o app mostre vários arquivos de mídia e solicite que o usuário escolha um deles, é mais eficiente carregar versões de pré-lançamento (ou miniaturas) dos arquivos, em vez deles próprios.

Para carregar a miniatura de um arquivo de mídia, use loadThumbnail() e transmita o tamanho da miniatura que você quer carregar, conforme mostrado no snippet de código abaixo:

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);

Abrir um arquivo de mídia

A lógica específica usada para abrir um arquivo de mídia depende do fato de o conteúdo de mídia ser mais bem representado como um descritor de arquivo, um stream de arquivos ou um caminho direto para o arquivo.

Descritor do arquivo

Para abrir um arquivo de mídia com um descritor de arquivos, use uma lógica semelhante à mostrada no snippet de código a seguir.

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();
}

Fluxo de arquivos

Para abrir um arquivo de mídia com um fluxo de arquivos, use uma lógica semelhante à mostrada no snippet de código a seguir.

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".
}

Caminhos diretos para arquivos

Para que o app funcione melhor com bibliotecas de mídia de terceiros, o Android 11 (nível 30 da API) e versões mais recentes permitem que você use APIs diferentes da MediaStore para acessar arquivos de mídia do armazenamento compartilhado. Em vez disso, é possível acessar arquivos de mídia diretamente usando uma destas APIs:

  • A API File
  • Bibliotecas nativas, como fopen()

Caso não tenha permissões relacionadas ao armazenamento, é possível acessar arquivos no diretório específico do app, bem como arquivos de mídia atribuídos ao app, usando a API File.

Se o app tentar acessar um arquivo usando a API File e não tiver as permissões necessárias, uma FileNotFoundException é gerada.

Para acessar outros arquivos no armazenamento compartilhado em um dispositivo com o Android 10 (nível 29 da API), recomendamos que você desative temporariamente o armazenamento com escopo configurando requestLegacyExternalStorage para true no arquivo de manifesto do app. Para acessar arquivos de mídia usando métodos de arquivos nativos no Android 10, também é necessário solicitar a permissão READ_EXTERNAL_STORAGE.

Considerações ao acessar conteúdo de mídia

Ao acessar o conteúdo de mídia, lembre-se das considerações discutidas nas seções abaixo.

Dados em cache

Caso o app armazene URIs ou dados do armazenamento de mídia em cache, verifique periodicamente se há atualizações no armazenamento de mídia. Essa verificação permite que os dados em cache do lado do app continuem sincronizados com os dados do provedor no lado do sistema.

Desempenho

Quando você executa leituras sequenciais de arquivos de mídia usando caminhos diretos para os arquivos, o desempenho é comparável ao da API MediaStore.

No entanto, quando você executa leituras e gravações aleatórias de arquivos de mídia usando caminhos de arquivo diretos, o processo pode ser até duas vezes mais lento. Nessas situações, recomendamos o uso da API MediaStore.

Coluna DATA

Ao acessar um arquivo de mídia existente, você pode usar o valor da coluna DATA na sua lógica. Isso ocorre porque esse valor tem um caminho de arquivo válido. No entanto, não presuma que o arquivo esteja sempre disponível. Prepare-se para lidar com qualquer erro de E/S baseado em arquivo que possa ocorrer.

Para criar ou atualizar um arquivo de mídia, por outro lado, não use o valor da coluna DATA. Em vez disso, use os valores das colunas DISPLAY_NAME e RELATIVE_PATH.

Volumes de armazenamento

Apps voltados ao Android 10 ou versões mais recentes podem acessar o nome único que o sistema atribui para cada volume de armazenamento externo. Esse sistema de nomenclatura ajuda você a organizar e indexar o conteúdo de maneira eficiente, além de oferecer controle sobre o local em que novos conteúdos são armazenados.

Os volumes abaixo são especialmente úteis:

  • O volume VOLUME_EXTERNAL oferece uma visualização de todos os volumes de armazenamento compartilhados no dispositivo. É possível ler o conteúdo desse volume sintético, mas não é possível o modificar.
  • O volume VOLUME_EXTERNAL_PRIMARY representa o volume de armazenamento compartilhado principal no dispositivo. É possível ler e modificar o conteúdo desse volume.

Você pode chamar MediaStore.getExternalVolumeNames() para descobrir outros volumes:

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

Local em que a mídia foi capturada

Alguns vídeos e fotos contêm informações de localização nos metadados, que mostram onde eles foram registrados.

A forma como você acessa essas informações de localização no seu app depende se você precisar ou não acessar as informações de uma foto ou de um vídeo.

Fotos

Caso o app use o armazenamento com escopo, o sistema oculta as informações de localização por padrão. Para acessar essas informações, siga estas etapas:

  1. Solicite a permissão ACCESS_MEDIA_LOCATION no manifesto do app.
  2. No objeto MediaStore, extraia os bytes exatos da foto chamando setRequireOriginal() e transmitindo o URI da foto, conforme mostrado no snippet de código abaixo.

    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];
    }
    

Vídeos

Para acessar as informações de localização nos metadados de um vídeo, use a classe MediaMetadataRetriever, conforme mostrado no snippet de código abaixo. O app não precisa solicitar outras permissões para usar essa 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);
}

Compartilhamento

Alguns apps permitem que os usuários compartilhem arquivos de mídia entre si. Por exemplo, apps de mídia social permitem que os usuários compartilhem fotos e vídeos com amigos.

Para compartilhar arquivos de mídia, use um URI content://, conforme recomendado no guia de criação de um provedor de conteúdo.

Atribuição de apps de arquivos de mídia

Quando o armazenamento com escopo está ativado para um app destinado ao Android 10 ou versões mais recentes, o sistema atribui um app a cada arquivo de mídia, o que determina os arquivos que o app pode acessar quando não tiver solicitado as permissões de armazenamento. Cada arquivo pode ser atribuído a apenas um app. Portanto, se o app criar um arquivo de mídia que é armazenado em coleções de mídia de arquivos de fotos, vídeos ou de áudio, o app vai ter acesso ao arquivo.

No entanto, se o usuário desinstalar e reinstalar o app, é necessário solicitar a permissão READ_EXTERNAL_STORAGE para acessar os arquivos criados originalmente pelo app. Essa solicitação de permissão é necessária porque o sistema considera o arquivo como atribuído à versão do app instalada anteriormente, em vez da recém-instalada.

Adicionar um item

Para adicionar um item de mídia a uma coleção existente, use um código semelhante ao apresentado abaixo. Este snippet de código acessa o volume VOLUME_EXTERNAL_PRIMARY em dispositivos com o Android 10 ou versões mais recentes. Isso porque, nesses dispositivos, só é possível modificar o conteúdo de um volume se ele for o principal, conforme descrito na seção Volumes de armazenamento.

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);

Alternar status pendente para arquivos de mídia

Se o app realizar operações que podem ser demoradas, por exemplo, gravar em arquivos de mídia, é útil ter acesso exclusivo ao arquivo durante o processamento. Em dispositivos que executam o Android 10 ou versões mais recentes, seu app pode conseguir esse acesso exclusivo definindo o valor da sinalização IS_PENDING como 1. Até que o valor seja alterado de IS_PENDING para zero, apenas seu app pode ver o arquivo.

O snippet de código abaixo se baseia no anterior. Ele mostra como usar a flag IS_PENDING ao armazenar uma música longa no diretório correspondente à coleção 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);

Dar uma dica sobre a localização do arquivo

Quando seu app armazena mídia em um dispositivo com o Android 10, por padrão, a mídia é organizada com base no tipo. Por exemplo, novos arquivos de imagem são colocados no diretório Environment.DIRECTORY_PICTURES, que corresponde à coleção MediaStore.Images.

Se o app souber que há um local específico em que os arquivos precisam ser armazenados, por exemplo, um álbum de fotos com o nome Pictures/MyVacationPictures, você pode definir MediaColumns.RELATIVE_PATH para oferecer uma dica ao sistema sobre onde armazenar os arquivos recém-gravados.

Atualizar um item

Para atualizar um arquivo de mídia do seu app, use um código semelhante a este:

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 o armazenamento com escopo estiver indisponível ou desativado, o processo mostrado no snippet de código anterior também vai funcionar para arquivos que não são do app

Atualizar em código nativo

Caso você precise modificar arquivos de mídia usando bibliotecas nativas, transmita o descritor do arquivo associado do código baseado em Java ou Kotlin ao código nativo.

O snippet de código abaixo mostra como transmitir o descritor do arquivo de um objeto de mídia para o código nativo do 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.
}

Atualizar arquivos de mídia de outros apps

Em geral, caso seu app use o armazenamento com escopo, ele não pode atualizar um arquivo de mídia que tenha sido disponibilizado por outro app no armazenamento de mídia.

No entanto, é possível conseguir o consentimento do usuário para modificar o arquivo, capturando o RecoverableSecurityException gerado pela plataforma. Você pode então solicitar que o usuário conceda ao app acesso de gravação para esse item específico, conforme mostrado no snippet de código a seguir.

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);
    }
}

Faça esse processo sempre que o app precisar modificar um arquivo de mídia que ele não tenha criado.

Como alternativa, ao executar o app no Android 11 ou versões mais recentes, você pode permitir que os usuários concedam ao app acesso de gravação em um grupo de arquivos de mídia. Use o método createWriteRequest(), conforme descrito na seção sobre como gerenciar grupos de arquivos de mídia.

Se o app tiver outro caso de uso que não é abrangido pelo armazenamento com escopo, registre uma solicitação de recurso e desative temporariamente o armazenamento com escopo.

Remover um item

Para remover um item que não é mais necessário para seu app do armazenamento de mídia, use uma lógica semelhante à mostrada no snippet de código a seguir:

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);

Caso o armazenamento com escopo esteja indisponível ou desativado, você pode usar o snippet de código anterior para remover arquivos de outros apps. No entanto, se o armazenamento com escopo estiver ativado, você vai precisar capturar uma RecoverableSecurityException para cada arquivo que o app for remover, conforme descrito na seção sobre como atualizar itens de mídia.

Ao executar o app no Android 11 ou versões mais recentes, você pode permitir que os usuários escolham um grupo de arquivos de mídia para remover. Use o método createTrashRequest() ou createDeleteRequest(), conforme descrito na seção sobre como gerenciar grupos de arquivos de mídia.

Se o app tiver outro caso de uso que não é abrangido pelo armazenamento com escopo, registre uma solicitação de recurso e desative temporariamente o armazenamento com escopo.

Detectar atualizações de arquivos de mídia

Seu app pode precisar identificar volumes de armazenamento contendo arquivos de mídia que tenham sido adicionados ou modificados por outros apps. Para detectar essas mudanças de maneira mais confiável, transmita o volume de armazenamento em questão para getGeneration(). Desde que a versão do armazenamento de mídia não mude, o valor de retorno desse método sempre aumenta ao longo do tempo.

Especificamente, getGeneration() é mais robusto que as datas em colunas de mídia, como DATE_ADDED e DATE_MODIFIED Isso ocorre porque os valores da coluna de mídia podem mudar quando um app chama setLastModified() ou quando o usuário muda o relógio do sistema.

Gerenciar grupos de arquivos de mídia

No Android 11 e versões mais recentes, é possível solicitar ao usuário para selecionar um grupo de arquivos de mídia e os atualizar em uma única operação. Esses métodos oferecem melhor consistência entre diferentes dispositivos e facilitam o gerenciamento das coleções de mídia por parte dos usuários.

Os métodos que oferecem essa função de atualização em lote incluem:

createWriteRequest()
Solicita que o usuário conceda acesso de gravação ao app para o grupo especificado de arquivos de mídia.
createFavoriteRequest()
Solicita que o usuário marque os arquivos de mídia especificados como "favoritos" no dispositivo. Todo app com acesso de leitura pode ver que o usuário marcou o arquivo como "favorito".
createTrashRequest()

Solicita que o usuário coloque os arquivos de mídia especificados na lixeira do dispositivo. Os itens são excluídos permanentemente após um período definido pelo sistema.

createDeleteRequest()

Solicita que o usuário exclua permanentemente os arquivos de mídia especificados imediatamente sem os colocar na lixeira antes.

Depois de chamar um desses métodos, o sistema cria um objeto PendingIntent. Depois que o app invoca essa intent, os usuários veem uma caixa de diálogo que solicita o consentimento deles para que o app atualize ou exclua os arquivos de mídia especificados.

Por exemplo, veja como estruturar uma chamada para 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);

Avalie a resposta do usuário. Se ele conceder permissão, prossiga para a operação de mídia. Caso contrário, explique ao usuário por que seu app precisa da permissão:

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. */
        }
    }
}

Você pode usar esse mesmo padrão geral com createFavoriteRequest(), createTrashRequest() e createDeleteRequest().

Permissão de gerenciamento de mídia

Os usuários podem confiar em um app específico para executar o gerenciamento de mídia, como fazer edições frequentes em arquivos de mídia. Se o app for direcionado ao Android 11 ou versões mais recentes e não for o app de galeria padrão do dispositivo, é necessário exibir uma caixa de diálogo de confirmação para o usuário sempre que o app tentar modificar ou excluir um arquivo.

Caso o app seja direcionado ao Android 12 (nível 31 da API) ou versões mais recentes, solicite que o usuário conceda acesso especial ao gerenciamento de mídia. Essa permissão aceita que o app realize cada uma das ações abaixo sem precisar exibir uma solicitação ao usuário para cada operação de arquivo:

Para isso, siga estes passos:

  1. Declare as novas permissões MANAGE_MEDIA e READ_EXTERNAL_STORAGE no arquivo de manifesto do app.

    Para chamar createWriteRequest() sem mostrar uma caixa de diálogo de confirmação, declare também a permissão ACCESS_MEDIA_LOCATION.

  2. No app, exiba uma interface ao usuário para explicar por que ele pode querer conceder acesso de gerenciamento de mídia.

  3. Invoque a ação da intent ACTION_REQUEST_MANAGE_MEDIA. Ela levará os usuários para a tela Apps de gerenciamento de mídia nas configurações do sistema. Lá, os usuários podem autorizar o acesso.

Casos de uso que exigem uma alternativa ao armazenamento de mídia

Se o app executa principalmente uma das funções abaixo, considere uma alternativa às APIs MediaStore.

Como trabalhar com outros tipos de arquivos

Se o app trabalha com documentos e arquivos que não contêm exclusivamente conteúdo de mídia, por exemplo, as extensões EPUB ou PDF, use a ação da intent ACTION_OPEN_DOCUMENT, conforme descrito no guia sobre como armazenar e acessar documentos e outros arquivos.

Compartilhamento de arquivos em apps complementares

Nos casos em que você oferece um conjunto de apps complementares, por exemplo, um app de mensagens e um de perfil, configure o compartilhamento de arquivos usando URIs content://. Também indicamos esse fluxo de trabalho como uma prática recomendada de segurança.

Outros recursos

Para ver mais informações sobre como armazenar e acessar mídia, consulte os recursos a seguir.

Exemplos

Vídeos