Em dispositivos com o Android 4.4 (nível 19 da API) e versões mais recentes, seu app pode interagir com um provedor de documentos, incluindo volumes de armazenamento externo e baseado na nuvem, usando o framework de acesso ao armazenamento. Esse framework permite que os usuários interajam com um seletor de sistema para escolher um provedor de documentos e selecionar documentos específicos e outros arquivos para o app criar, abrir ou modificar.
Como o usuário está envolvido na seleção dos arquivos ou diretórios que o app pode acessar, esse mecanismo não exige nenhuma permissão do sistema, e o controle e a privacidade do usuário foram aprimorados. Além disso, esses arquivos, armazenados fora de um diretório específico do app e fora do armazenamento de mídia, permanecem no dispositivo após a desinstalação do app.
O uso do framework envolve estas etapas:
- Um app invoca uma intent que contém uma ação relacionada ao armazenamento. Essa ação corresponde a um caso de uso específico que o framework disponibiliza.
- O usuário vê um seletor de sistema que permite que ele procure um provedor de documentos e escolha um local ou documento em que a ação relacionada ao armazenamento ocorra.
- O app recebe acesso de leitura e gravação a um URI que representa o local ou documento escolhido pelo usuário. Com esse URI, o app pode executar operações no local escolhido.
Para oferecer suporte ao acesso a arquivos de mídia em dispositivos com o Android 9 (nível 28 da API) ou
versões anteriores, declare a permissão
READ_EXTERNAL_STORAGE
e defina maxSdkVersion
como 28
.
Este guia explica os diferentes casos de uso com suporte do framework para trabalhar com arquivos e outros documentos. Além disso, também explica como executar operações no local selecionado pelo usuário.
Casos de uso para acessar documentos e outros arquivos
O framework de acesso ao armazenamento oferece suporte aos casos de uso abaixo para acessar arquivos e outros documentos.
- Criar um novo arquivo
- A ação da intent
ACTION_CREATE_DOCUMENT
permite que os usuários salvem um arquivo em um local específico. - Abrir um documento ou arquivo
- A ação da intent
ACTION_OPEN_DOCUMENT
permite que os usuários selecionem um documento ou arquivo específico para abrir. - Conceder acesso ao conteúdo de um diretório
- A ação da intent
ACTION_OPEN_DOCUMENT_TREE
, disponível no Android 5.0 (nível 21 da API) e em versões mais recentes, permite que os usuários selecionem um diretório específico, concedendo ao app acesso a todos os arquivos e subdiretórios nesse diretório.
As seções a seguir dão orientações sobre como configurar cada caso de uso.
Criar um novo arquivo
Use a ação da intent
ACTION_CREATE_DOCUMENT
para carregar o seletor de arquivos do sistema e permitir que o usuário escolha um
local para gravar o conteúdo de um arquivo. Esse processo é semelhante ao usado nas caixas de diálogo "Salvar como" usadas por outros sistemas operacionais.
Observação:ACTION_CREATE_DOCUMENT
não é possível substituir um arquivo existente. Se o app tentar salvar um arquivo com o mesmo nome, o sistema acrescentará um número entre parênteses no final do nome do arquivo.
Por exemplo, se o app tentar salvar um arquivo com o nome
confirmation.pdf
em um diretório que já tenha um arquivo com o mesmo
nome, o sistema vai salvar o novo arquivo com o nome
confirmation(1).pdf
.
Ao configurar a intent, especifique o nome do arquivo e o tipo MIME e,
como opção, especifique o URI do arquivo ou diretório que o seletor de arquivos precisa
exibir ao carregar pela primeira vez usando o extra da
intent
EXTRA_INITIAL_URI
.
O snippet de código a seguir mostra como criar e invocar a intent de criação de arquivo:
Kotlin
// Request code for creating a PDF document. const val CREATE_FILE = 1 private fun createFile(pickerInitialUri: Uri) { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/pdf" putExtra(Intent.EXTRA_TITLE, "invoice.pdf") // Optionally, specify a URI for the directory that should be opened in // the system file picker before your app creates the document. putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) } startActivityForResult(intent, CREATE_FILE) }
Java
// Request code for creating a PDF document. private static final int CREATE_FILE = 1; private void createFile(Uri pickerInitialUri) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/pdf"); intent.putExtra(Intent.EXTRA_TITLE, "invoice.pdf"); // Optionally, specify a URI for the directory that should be opened in // the system file picker when your app creates the document. intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri); startActivityForResult(intent, CREATE_FILE); }
Abrir um arquivo
Seu app pode usar documentos como a unidade de armazenamento em que os usuários inserem dados que eles podem querer compartilhar com amigos ou importar para outros documentos. Vários exemplos incluem um usuário abrindo um documento de produtividade ou um livro salvo como um arquivo EPUB.
Nesses casos, permita que o usuário escolha o arquivo a ser aberto invocando a intent
ACTION_OPEN_DOCUMENT
,
que abre o app seletor de arquivos do sistema. Para mostrar apenas os tipos de
arquivos a que o app oferece suporte, especifique um tipo MIME. Como opção, você pode especificar o URI do arquivo que o seletor de arquivos exibirá ao carregar pela primeira vez usando o extra da intent EXTRA_INITIAL_URI
.
O snippet de código a seguir mostra como criar e invocar a intent para abrir um documento PDF:
Kotlin
// Request code for selecting a PDF document. const val PICK_PDF_FILE = 2 fun openFile(pickerInitialUri: Uri) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/pdf" // Optionally, specify a URI for the file that should appear in the // system file picker when it loads. putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) } startActivityForResult(intent, PICK_PDF_FILE) }
Java
// Request code for selecting a PDF document. private static final int PICK_PDF_FILE = 2; private void openFile(Uri pickerInitialUri) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/pdf"); // Optionally, specify a URI for the file that should appear in the // system file picker when it loads. intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri); startActivityForResult(intent, PICK_PDF_FILE); }
Restrições de acesso
No Android 11 (nível 30 da API) e versões mais recentes, não é possível usar a ação da intent
ACTION_OPEN_DOCUMENT
para solicitar que o usuário selecione arquivos
específicos destes diretórios:
- O diretório
Android/data/
e todos os subdiretórios - O diretório
Android/obb/
e todos os subdiretórios
Conceder acesso ao conteúdo de um diretório
Em geral, apps de gerenciamento de arquivos e criação de mídia gerenciam grupos de arquivos em uma
hierarquia de diretórios. Para oferecer esse recurso no app, use a ação da intent
ACTION_OPEN_DOCUMENT_TREE
,
que permite que o usuário conceda acesso a uma árvore de diretórios inteira,
com algumas exceções a partir do Android 11 (nível 30 da API). O app pode
acessar qualquer arquivo no diretório selecionado e em qualquer subdiretório.
Ao usar ACTION_OPEN_DOCUMENT_TREE
, o app recebe acesso somente aos
arquivos no diretório selecionado pelo usuário. Você não tem acesso aos arquivos de outros apps que residem fora desse diretório selecionado pelo usuário. Esse
acesso controlado permite que os usuários escolham exatamente qual conteúdo eles
se sentem à vontade para compartilhar com o app.
Além disso, como opção, você pode especificar o URI do arquivo que o seletor de arquivos
exibe ao carregar pela primeira vez usando o extra da
intent
EXTRA_INITIAL_URI
.
O snippet de código a seguir mostra como criar e invocar a intent para abrir um diretório:
Kotlin
fun openDirectory(pickerInitialUri: Uri) { // Choose a directory using the system's file picker. val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { // Optionally, specify a URI for the directory that should be opened in // the system file picker when it loads. putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) } startActivityForResult(intent, your-request-code) }
Java
public void openDirectory(Uri uriToLoad) { // Choose a directory using the system's file picker. Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); // Optionally, specify a URI for the directory that should be opened in // the system file picker when it loads. intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad); startActivityForResult(intent, your-request-code); }
Restrições de acesso
No Android 11 (nível 30 da API) e versões mais recentes, não é possível usar a ação da intent
ACTION_OPEN_DOCUMENT_TREE
para solicitar acesso a estes
diretórios:
- O diretório raiz do volume de armazenamento interno.
- O diretório raiz de cada volume do cartão SD que o fabricante do dispositivo considera confiável, independentemente do cartão ser emulado ou removível. Um volume confiável é aquele que um app pode acessar na maioria das vezes.
- O diretório
Download
Além disso, no Android 11 (nível 30 da API) e versões mais recentes, não é possível usar a ação da intent
ACTION_OPEN_DOCUMENT_TREE
para solicitar que o usuário selecione
arquivos específicos destes diretórios:
- O diretório
Android/data/
e todos os subdiretórios - O diretório
Android/obb/
e todos os subdiretórios
Realizar operações no local escolhido
Depois que o usuário selecionar um arquivo ou diretório usando o seletor de arquivos do sistema,
você pode extrair o URI do item selecionado usando o código abaixo em
onActivityResult()
:
Kotlin
override fun onActivityResult( requestCode: Int, resultCode: Int, resultData: Intent?) { if (requestCode == your-request-code && resultCode == Activity.RESULT_OK) { // The result data contains a URI for the document or directory that // the user selected. resultData?.data?.also { uri -> // Perform operations on the document using its URI. } } }
Java
@Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { if (requestCode == your-request-code && resultCode == Activity.RESULT_OK) { // The result data contains a URI for the document or directory that // the user selected. Uri uri = null; if (resultData != null) { uri = resultData.getData(); // Perform operations on the document using its URI. } } }
Ao receber uma referência ao URI do item selecionado, o app pode realizar várias operações nele. Por exemplo, você pode tanto acessar os metadados do item quanto editá-lo e excluí-lo.
As seções a seguir mostram como concluir ações nos arquivos selecionados pelo usuário.
Determinar as operações com suporte de um provedor
Diferentes provedores de conteúdo permitem que operações distintas sejam realizadas em
documentos, como os copiar ou visualizar a miniatura deles. Para determinar quais operações são compatíveis com um provedor, verifique o valor de Document.COLUMN_FLAGS
.
A IU do seu app pode mostrar apenas as opções que são compatíveis com o provedor.
Manutenção de permissões
Quando o app abre um arquivo para leitura ou gravação, o sistema fornece uma concessão de permissão do URI para aquele arquivo, que dura até o dispositivo do usuário ser reiniciado. Suponha, no entanto, que seu app seja de edição de imagens e você queira que os usuários acessem as cinco imagens com edições mais recentes, feitas diretamente do app. Se o dispositivo do usuário tiver sido reiniciado, você precisará enviá-lo de volta ao seletor do sistema para encontrar os arquivos.
Para preservar o acesso a arquivos em reinicializações de dispositivos e criar uma experiência do usuário melhor, o app pode receber a concessão de permissão de URI persistente oferecida pelo sistema, como mostrado no snippet de código a seguir:
Kotlin
val contentResolver = applicationContext.contentResolver val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION // Check for the freshest data. contentResolver.takePersistableUriPermission(uri, takeFlags)
Java
final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Check for the freshest data. getContentResolver().takePersistableUriPermission(uri, takeFlags);
Verificação de metadados de documentos
Quando tiver o URI de um documento, você terá acesso aos seus metadados. Esse snippet identifica os metadados de um documento especificado pelo URI e os registra:
Kotlin
val contentResolver = applicationContext.contentResolver fun dumpImageMetaData(uri: Uri) { // The query, because it only applies to a single document, returns only // one row. There's no need to filter, sort, or select fields, // because we want all fields for one document. val cursor: Cursor? = contentResolver.query( uri, null, null, null, null, null) cursor?.use { // moveToFirst() returns false if the cursor has 0 rows. Very handy for // "if there's anything to look at, look at it" conditionals. if (it.moveToFirst()) { // Note it's called "Display Name". This is // provider-specific, and might not necessarily be the file name. val displayName: String = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) Log.i(TAG, "Display Name: $displayName") val sizeIndex: Int = it.getColumnIndex(OpenableColumns.SIZE) // If the size is unknown, the value stored is null. But because an // int can't be null, the behavior is implementation-specific, // and unpredictable. So as // a rule, check if it's null before assigning to an int. This will // happen often: The storage API allows for remote files, whose // size might not be locally known. val size: String = if (!it.isNull(sizeIndex)) { // Technically the column stores an int, but cursor.getString() // will do the conversion automatically. it.getString(sizeIndex) } else { "Unknown" } Log.i(TAG, "Size: $size") } } }
Java
public void dumpImageMetaData(Uri uri) { // The query, because it only applies to a single document, returns only // one row. There's no need to filter, sort, or select fields, // because we want all fields for one document. Cursor cursor = getActivity().getContentResolver() .query(uri, null, null, null, null, null); try { // moveToFirst() returns false if the cursor has 0 rows. Very handy for // "if there's anything to look at, look at it" conditionals. if (cursor != null && cursor.moveToFirst()) { // Note it's called "Display Name". This is // provider-specific, and might not necessarily be the file name. String displayName = cursor.getString( cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); Log.i(TAG, "Display Name: " + displayName); int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); // If the size is unknown, the value stored is null. But because an // int can't be null, the behavior is implementation-specific, // and unpredictable. So as // a rule, check if it's null before assigning to an int. This will // happen often: The storage API allows for remote files, whose // size might not be locally known. String size = null; if (!cursor.isNull(sizeIndex)) { // Technically the column stores an int, but cursor.getString() // will do the conversion automatically. size = cursor.getString(sizeIndex); } else { size = "Unknown"; } Log.i(TAG, "Size: " + size); } } finally { cursor.close(); } }
Abrir um documento
Com uma referência ao URI de um documento, você pode abrir um documento para processamento adicional. Esta seção mostra exemplos de como abrir um bitmap e um fluxo de entrada.
Bitmap
O snippet de código abaixo mostra como abrir um arquivo
Bitmap
de acordo com o URI dele:
Kotlin
val contentResolver = applicationContext.contentResolver @Throws(IOException::class) private fun getBitmapFromUri(uri: Uri): Bitmap { val parcelFileDescriptor: ParcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r") val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor) parcelFileDescriptor.close() return image }
Java
private Bitmap getBitmapFromUri(Uri uri) throws IOException { ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); parcelFileDescriptor.close(); return image; }
Depois de abrir o bitmap, você pode o exibir em uma
ImageView
.
Fluxo de entrada
O snippet de código a seguir mostra como abrir um objeto InputStream de acordo com seu URI. Neste snippet, as linhas do arquivo estão sendo lidas em uma string:
Kotlin
val contentResolver = applicationContext.contentResolver @Throws(IOException::class) private fun readTextFromUri(uri: Uri): String { val stringBuilder = StringBuilder() contentResolver.openInputStream(uri)?.use { inputStream -> BufferedReader(InputStreamReader(inputStream)).use { reader -> var line: String? = reader.readLine() while (line != null) { stringBuilder.append(line) line = reader.readLine() } } } return stringBuilder.toString() }
Java
private String readTextFromUri(Uri uri) throws IOException { StringBuilder stringBuilder = new StringBuilder(); try (InputStream inputStream = getContentResolver().openInputStream(uri); BufferedReader reader = new BufferedReader( new InputStreamReader(Objects.requireNonNull(inputStream)))) { String line; while ((line = reader.readLine()) != null) { stringBuilder.append(line); } } return stringBuilder.toString(); }
Edição de um documento
Você pode usar o framework de acesso ao armazenamento para editar um documento de texto.
O snippet de código a seguir substitui o conteúdo do documento representado pelo URI fornecido:
Kotlin
val contentResolver = applicationContext.contentResolver private fun alterDocument(uri: Uri) { try { contentResolver.openFileDescriptor(uri, "w")?.use { FileOutputStream(it.fileDescriptor).use { it.write( ("Overwritten at ${System.currentTimeMillis()}\n") .toByteArray() ) } } } catch (e: FileNotFoundException) { e.printStackTrace() } catch (e: IOException) { e.printStackTrace() } }
Java
private void alterDocument(Uri uri) { try { ParcelFileDescriptor pfd = getActivity().getContentResolver(). openFileDescriptor(uri, "w"); FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor()); fileOutputStream.write(("Overwritten at " + System.currentTimeMillis() + "\n").getBytes()); // Let the document provider know you're done by closing the stream. fileOutputStream.close(); pfd.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
Excluir de um documento
Se você tiver o URI de um documento e as
Document.COLUMN_FLAGS
do documento contiverem
SUPPORTS_DELETE
,
é possível o excluir. Exemplo:
Kotlin
DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)
Java
DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);
Extrair um URI de mídia equivalente
O método
getMediaUri()
fornece um URI de armazenamento de mídia equivalente a um URI do provedor
de documentos. Os dois URIs se referem ao mesmo item. Com o URI de
armazenamento de mídia, é mais fácil acessar arquivos de mídia do armazenamento
compartilhado.
O método getMediaUri()
oferece suporte a URIs ExternalStorageProvider
. No
Android 12 (nível 31 da API) e versões mais recentes, o método também oferece suporte
a URIs MediaDocumentsProvider
.
Abrir um arquivo virtual
No Android 7.0 (nível 25 da API) e versões mais recentes, o app pode usar arquivos virtuais
disponibilizados pelo framework de acesso ao armazenamento. Mesmo que os arquivos virtuais não tenham uma representação binária, seu app pode abrir o conteúdo forçando a conversão deles em um tipo de arquivo diferente ou exibindo esses arquivos usando uma ação da intent ACTION_VIEW
.
Para abrir arquivos virtuais, seu app cliente precisa incluir uma lógica especial para gerenciá-los. Se você quiser acessar uma representação em bytes do arquivo (para visualizar o arquivo, por exemplo), é necessário solicitar um tipo MIME alternativo do provedor de documentos.
Depois que o usuário fizer uma seleção, use o URI nos dados dos resultados para determinar se o arquivo é virtual, conforme mostrado no snippet de código a seguir:
Kotlin
private fun isVirtualFile(uri: Uri): Boolean { if (!DocumentsContract.isDocumentUri(this, uri)) { return false } val cursor: Cursor? = contentResolver.query( uri, arrayOf(DocumentsContract.Document.COLUMN_FLAGS), null, null, null ) val flags: Int = cursor?.use { if (cursor.moveToFirst()) { cursor.getInt(0) } else { 0 } } ?: 0 return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0 }
Java
private boolean isVirtualFile(Uri uri) { if (!DocumentsContract.isDocumentUri(this, uri)) { return false; } Cursor cursor = getContentResolver().query( uri, new String[] { DocumentsContract.Document.COLUMN_FLAGS }, null, null, null); int flags = 0; if (cursor.moveToFirst()) { flags = cursor.getInt(0); } cursor.close(); return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0; }
Depois de verificar se o documento é um arquivo virtual, você poderá forçar o arquivo a um tipo MIME alternativo, como "image/png"
. O snippet de código a seguir mostra como verificar se um arquivo virtual pode ser representado como uma imagem e, em caso afirmativo, recebe um stream de entrada a partir do arquivo virtual:
Kotlin
@Throws(IOException::class) private fun getInputStreamForVirtualFile( uri: Uri, mimeTypeFilter: String): InputStream { val openableMimeTypes: Array<String>? = contentResolver.getStreamTypes(uri, mimeTypeFilter) return if (openableMimeTypes?.isNotEmpty() == true) { contentResolver .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null) .createInputStream() } else { throw FileNotFoundException() } }
Java
private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter) throws IOException { ContentResolver resolver = getContentResolver(); String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter); if (openableMimeTypes == null || openableMimeTypes.length < 1) { throw new FileNotFoundException(); } return resolver .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null) .createInputStream(); }
Outros recursos
Para saber mais sobre como armazenar e acessar documentos e outros arquivos, consulte os seguintes recursos.
Exemplos
- ActionOpenDocument (em inglês), disponível no GitHub.
- ActionOpenDocumentTree (em inglês), disponível no GitHub.