- 28 de janeiro de 2020
- Blog
Android 10: Acesso a arquivos usando armazenamento por escopo
No primeiro artigo, vimos as mudanças no acesso a arquivos ao longo da evolução do Android e as alterações implementadas no Android 10 pelo armazenamento por escopo, que foi criado para a melhorar a privacidade do usuário e o controle das informações armazenadas pelos aplicativos, uma vez que ele limita o acesso a informações sensíveis do usuário por aplicativos que não deveriam ter acesso a essas informações e garante a exclusão automática de todos os dados criados pelo aplicativo quando o mesmo é desinstalado.
A seguir, mostramos como um desenvolvedor deve adaptar seus aplicativos para se adequar a esse novo modo de acesso a arquivos.
Obrigatoriedade das mudanças
Se um aplicativo Android tiver como target o Android 10, as mudanças do armazenamento por escopo já estão ativas. Porém, enquanto não é obrigatório suportar o armazenamento por escopo no Android 10, ele será um requisito para o Android 11.
Caso o desenvolvedor do aplicativo deseje esperar pelo Android 11 para implementar as mudanças de acesso a arquivos e manter o comportamento atual, basta ativar o modo legado, que pode ser ativado colocando android:requestLegacyExternalStorage=”true” no arquivo Manifest do aplicativo.
Acesso a arquivos que não precisam ser compartilhados com outros aplicativos
Mesmo sem habilitar o modo legado, se um aplicativo apenas criar arquivos para armazenamento de dados que não precisam ser compartilhados com outros aplicativos, como mídias ou downloads, nenhuma alteração será necessária em sua implementação. Os arquivos criados pelo aplicativo serão armazenados num sistema de arquivos virtual que será transparente para o aplicativo.
Porém, aplicativos que costumavam acessar dados que não são seus e não são arquivos de mídia, não conseguirão mais fazer esse acesso. O acesso a arquivos genéricos somente será possível através do SAF (Storage Access Framework), biblioteca de acesso ao armazenamento de Androids. Para mais detalhes sobre o uso do SAF consulte.
Entendendo o local de armazenamento
Na implementação do armazenamento por escopo temos o conceito de volume, que indica em qual dispositivo (memória principal ou cartão de memória) os arquivos devem ser lidos ou gravados. Para a memória principal, uma constante foi criada para indicar este local de armazenamento, que é MediaStore.VOLUME_EXTERNAL_PRIMARY.
Porém, para o cartão de memória, precisamos chamar MediaStore.getExternalVolumeNames para obter a lista de todos os volumes disponíveis:

Lista de todos os volumes disponíveis MediaStore.getExternalVolumeNames
Como criar novos arquivos de mídia ou Downloads
Quando usamos armazenamento por escopo, não é necessário solicitar nenhuma permissão ao usuário para efetuar a gravação de arquivos, tanto na memória principal quanto no cartão de memória. O acesso é feito utilizando-se o content provider do MediaStore, de forma que é necessário solicitar a criação do novo arquivo para, em seguida, ter acesso de escrita ao mesmo. Para mais detalhes do funcionamento do MediaStore consulte.
Criação de um novo arquivo:

Criação de um novo arquivo
Como vimos acima, passamos o nome do novo arquivo, o seu mime type e ligamos o flag IS_PENDING. Este flag é utilizado para avisar ao content provider que o arquivo ainda não está completo. Neste caso, quando qualquer aplicativo listar os arquivos disponíveis pelo MediaStore, os arquivos com este flag ativo não serão listados.
Como resultado, a variável item contém a URI do novo arquivo, que será utilizada para abri-lo para escrita e efetuar a gravação:

Variável item contém a URI do novo arquivo
Ao finalizar as alterações ao arquivo, mudamos o flag IS_PENDING indicando que o arquivo está completo e atualizamos o MediaStore:

Arquivo está completo e atualizamos o MediaStor
Listando os arquivos
Não foram introduzidas muitas mudanças para listar arquivos de mídia utilizando armazenamento por escopo. Deve-se efetuar uma query no MediaStore solicitando os arquivos desejados da mesma maneira que era possível efetuar em versões anteriores de Android:

Query no MediaStore
Uma mudança importante implementada a partir do Android 10 foi a introdução do conceito de proprietário de arquivo. O proprietário é o nome do pacote do aplicativo que criou este arquivo. Como os arquivos agora possuem um aplicativo proprietário, quando uma query é feita no MediaStore, somente os arquivos criados pelo próprio aplicativo são listados. Porém, em dispositivos que tiveram upgrade para o Android 10, os arquivos criados anteriormente não possuem um proprietário, não sendo mais listados.
Para listar seus arquivos de mídia criados antes de um upgrade do sistema ou até arquivos de mídia criados por outros aplicativos, deve-se solicitar a permissão READ_EXTERNAL_STORAGE. Quando essa permissão é concedida, todos os arquivos de mídia (áudio, vídeo, imagens) são listados e liberados para leitura.
Um detalhe importante é que, apesar de ser possível gravar arquivos na pasta Downloads utilizando o armazenamento por escopo, não é possível fazer leitura ou listar os arquivos utilizando o content provider. Para ter acesso a esses arquivos, deve-se utilizar o SAF.
Efetuando leitura em arquivos
Uma vez que temos a URI do arquivo que desejamos abrir — que pode ser obtida através de uma query no MediaStore como mostrado anteriormente—, podemos abrir o arquivo para leitura. Lembre-se que arquivos em que seu aplicativo não é a proprietária ou arquivos gerados em versões anteriores de Android só poderão ser lidos com a permissão de READ_EXTERNAL_STORAGE.

Permissão de READ_EXTERNAL_STORAGE
Renomeando e apagando arquivos
Como o acesso aos arquivos de mídia é feito pela MediaStore, para renomear ou apagar arquivos, basta fazer a alteração no content provider e as mudanças são refletidas no sistema de arquivos.
Para excluir um arquivo:

Excluir um arquivo
Para renomear um arquivo:

Renomear um arquivo
Porém, para ambos os casos, estas operações são permitidas somente nos arquivos que foram criados pelo próprio aplicativo. Acesso a um arquivo de mídia de outros aplicativos ou arquivos próprios criados em versão antiga de Android geram a exceção RecoverableSecurityException.
Quando isto acontece ainda é possível executar a operação, mas precisamos pedir permissão para o usuário para que a operação seja concluída. Para isto, precisamos tratar essa exceção e solicitar essa permissão:

Permissão para o usuário
Depois que o usuário confirmar ou negar o acesso a este arquivo, a resposta é obtida no método onActivityResult:

Método onActivityResult
Conclusão
As mudanças introduzidas no Android 10 para o armazenamento por escopo trouxeram O armazenamento por escopo também ofereceu a desenvolvedores uma solução única para acesso ao armazenamento principal e ao cartão SD.
Porém, mais uma vez, houve uma mudança grande nas APIs, obrigando os desenvolvedores a adaptar seus aplicativos para o Android 10 e manter implementações diferentes para cada versão do sistema operacional para manter a compatibilidade com versões anteriores.