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/
ePictures/
. O sistema adiciona esses arquivos à tabelaMediaStore.Images
. - Vídeos, armazenados nos diretórios
DCIM/
,Movies/
ePictures/
. O sistema adiciona esses arquivos à tabelaMediaStore.Video
. - Arquivos de áudio, armazenados nos diretórios
Alarms/
,Audiobooks/
,Music/
,Notifications/
,Podcasts/
eRingtones/
. Além disso, o sistema reconhece playlists de áudio que estão nos diretóriosMusic/
ouMovies/
, assim como as gravações de voz que estão no diretórioRecordings/
. O sistema adiciona esses arquivos à tabelaMediaStore.Audio
. O diretórioRecordings/
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 tabelaMediaStore.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ãoREAD_EXTERNAL_STORAGE
. No entanto, recomendamos que você use as APIsMediaStore
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"
, useCursorWrapper
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:
- Solicite a permissão
ACCESS_MEDIA_LOCATION
no manifesto do app. No objeto
MediaStore
, extraia os bytes exatos da foto chamandosetRequireOriginal()
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:
- Modifique arquivos usando
createWriteRequest()
. - Mova os arquivos para dentro e para fora da lixeira usando
createTrashRequest()
. - Exclua arquivos usando
createDeleteRequest()
.
Para isso, siga estes passos:
Declare as novas permissões
MANAGE_MEDIA
eREAD_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ãoACCESS_MEDIA_LOCATION
.No app, exiba uma interface ao usuário para explicar por que ele pode querer conceder acesso de gerenciamento de mídia.
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://
. Além disso, recomendamos 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
- MediaStore, disponível no GitHub (em inglês).