O Android usa um sistema de arquivos
parecido com sistemas de arquivos em disco de outras plataformas. Esta lição explica
como trabalhar com o sistema de arquivos Android para ler e gravar arquivos com as APIs File
.
Um objeto File é adequado para ler ou gravar grandes quantidades de dados em
ordem crescente sem saltar de um registro a outro. Por exemplo, é bom para arquivos de imagens ou
qualquer troca executada em uma rede.
Esta lição demonstra como executar tarefas básicas relacionadas a arquivos em seu aplicativo.
Supõe-se que você já esteja familiarizado com os fundamentos do sistema de arquivos Linux e com
APIs de entrada/saída de arquivos padrão no java.io.
Escolher entre armazenamento interno e externo
Todos os dispositivos Android têm duas áreas de armazenamento de arquivos: armazenamento “interno” e “externo”. Estes nomes têm origem no início do Android, quando a maior parte dos dispositivos oferecia memória embutida não volátil (armazenamento interno), além de uma mídia de armazenamento removível, como cartões micro SD (armazenamento externo). Alguns dispositivos dividem o espaço de armazenamento permanente em partições “interna” e “externa”. Assim, mesmo sem uma mídia de armazenamento removível, sempre há dois espaços de armazenamento e o comportamento da API permanece inalterado independentemente da remoção do armazenamento externo. A lista a seguir resume as principais informações sobre cada tipo de espaço de armazenamento.
Armazenamento interno:
- Está sempre disponível.
- Os arquivos salvos lá são acessíveis somente pelo seu aplicativo.
- Quando o usuário desinstala o aplicativo, o sistema exclui todos os arquivos do aplicativo salvos no armazenamento interno.
O armazenamento interno funciona melhor quando você deseja garantir que o usuário e outros aplicativos não tenham acesso aos seus arquivos.
Armazenamento externo:
- Não está sempre disponível porque o usuário pode montar o armazenamento externo, como um armazenamento USB, e, em alguns casos, removê-lo do dispositivo.
- É de leitura universal, ou seja, arquivos salvos aqui podem ser lidos em outros dispositivos.
- Quando o usuário desinstala o aplicativo, o sistema exclui todos os arquivos do aplicativo salvos aqui
apenas se estiverem salvos no diretório de
getExternalFilesDir().
O armazenamento externo é o melhor local para arquivos que não exigem acesso restrito e para os arquivos que você deseja compartilhar com outros aplicativos ou permitir que o usuário acesse em um computador.
Observação: Antes do Android N, arquivos internos podiam tornar-se acessíveis a outros
aplicativo ao relaxar as permissões do sistema de arquivos. Isso não ocorre mais. Se você deseja
tornar o conteúdo de um arquivo privado acessível a outros aplicativos, o aplicativo pode usar o
FileProvider. Consulte Compartilhamento de arquivos.
Dica: Embora os aplicativos estejam instalados para armazenamento interno por
padrão, você pode especificar o atributo android:installLocation no seu manifesto para que seja possível instalar
seu aplicativo no armazenamento externo. Os usuários se beneficiam dessa opção quando o tamanho do APK é muito grande e
o espaço no armazenamento externo é maior do que no interno. Para obter mais
informações, consulte Local de instalação do aplicativo.
Obter permissões para armazenamento externo
Para gravar no armazenamento externo, você deve solicitar a
permissão WRITE_EXTERNAL_STORAGE no arquivo de manifesto:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
Cuidado:
atualmente, todos aplicativos podem ler o armazenamento externo
sem precisar de permissão especial. No entanto, isso será alterado em lançamentos futuros. Se seu aplicativo precisar
ler o armazenamento externo (mas não gravar nele), você deverá declarar a permissão. READ_EXTERNAL_STORAGE. Para garantir que o aplicativo continue
a funcionar adequadamente, declare essa permissão agora antes que as mudanças entrem em vigor.
<manifest ...>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
</manifest>
No entanto, se seu aplicativo usa a permissão WRITE_EXTERNAL_STORAGE
, já existe uma permissão implícita para leitura do armazenamento externo.
Não é necessária permissão para salvar arquivos no armazenamento interno. Seu aplicativo sempre terá permissão para ler e gravar arquivos no diretório de armazenamento interno.
Salvar um arquivo no armazenamento interno
Ao salvar um arquivo no armazenamento interno, você pode obter o diretório adequado como um
File chamando um destes dois métodos:
getFilesDir()- Retorna um
Fileque representa um diretório interno para o aplicativo. getCacheDir()- Retorna um
Fileque representa um diretório interno para os arquivos de cache temporários do aplicativo. Certifique-se de excluir cada arquivo assim que não for mais necessário e estabeleça um limite de tamanho razoável para a quantidade de memória usada em um determinado período, como 1 MB. Se o sistema começar a ficar com pouco espaço de armazenamento, ele poderá excluir arquivos de cache sem avisar.
Para criar um novo arquivo em um desses diretórios, você pode usar o construtor File() passando o File fornecido por um
dos métodos acima que especifica o diretório de armazenamento interno. Por exemplo:
File file = new File(context.getFilesDir(), filename);
Outra opção é chamar openFileOutput() para obter um FileOutputStream
que grava em um arquivo em seu diretório interno. O exemplo a seguir
mostra como gravar texto em um arquivo:
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
Ou, se precisar armazenar alguns arquivos em cache, você deve usar createTempFile(). Por exemplo, o método a seguir extrai o
nome do arquivo de um URL e cria um arquivo com o mesmo nome
no diretório de cache interno do aplicativo.
public File getTempFile(Context context, String url) {
File file;
try {
String fileName = Uri.parse(url).getLastPathSegment();
file = File.createTempFile(fileName, null, context.getCacheDir());
} catch (IOException e) {
// Error while creating file
}
return file;
}
Observação:
o diretório de armazenamento interno do aplicativo é especificado
pelo nome do pacote do aplicativo em um local específico do sistema de arquivos Android.
Teoricamente, outros aplicativos poderão ler seus arquivos internos se você definir
o arquivo para modo leitura. No entanto, o outro aplicativo também precisa conhecer o nome do pacote
do seu aplicativo e os nomes dos arquivos. Outros aplicativos não podem navegar nos diretórios internos e não têm
permissão para ler nem gravar, a menos que os arquivos sejam explicitamente definidos para permitir tais ações. Portanto,
desde que você use MODE_PRIVATE para seus arquivos no armazenamento interno,
eles nunca ficarão acessíveis para outros aplicativos.
Salvar um arquivo no armazenamento externo
Como o armazenamento externo pode ficar indisponível — como quando o usuário monta o
armazenamento em um PC ou remove o cartão SD que fornece o armazenamento externo — você
deve sempre verificar se o volume está disponível antes de acessá-lo. Consulte o estado de armazenamento
externo chamando getExternalStorageState(). Se o estado
retornado for igual a MEDIA_MOUNTED, os arquivos poderão ser lidos
e gravados. Os métodos a seguir ajudam a determinar a disponibilidade
de armazenamento:
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
Embora o usuário e outros aplicativos possam modificar o armazenamento externo, há duas categorias de arquivos que poderão ser salvas aqui:
- Arquivos públicos
- Arquivos que
precisam estar livremente disponíveis para o usuário e outros aplicativos. Ao desinstalar o aplicativo,
o usuário deve continuar a ter acesso a esses arquivos.
Por exemplo, fotos capturadas pelo aplicativo ou outros arquivos baixados.
- Arquivos privados
- Arquivos que pertencem ao seu aplicativo e devem ser excluídos na
desinstalação. Embora esses arquivos sejam tecnicamente acessíveis pelo usuário e outros aplicativo por estarem
no armazenamento externo, eles são, na verdade, arquivos que não têm valor para o usuário
fora do aplicativo. Ao desinstalar o aplicativo, o sistema exclui
todos os arquivos do diretório privado externo do aplicativo.
Por exemplo, recursos adicionais baixados através do aplicativo ou arquivos de mídia temporários.
Para salvar arquivos públicos no armazenamento externo, use o método
getExternalStoragePublicDirectory() para obter um File que representa
o diretório correto no armazenamento externo. O método usa um argumento que especifica
o tipo de arquivo que você deseja salvar para que eles se organizem de maneira lógica com outros arquivos
públicos, como DIRECTORY_MUSIC ou DIRECTORY_PICTURES. Por exemplo:
public File getAlbumStorageDir(String albumName) {
// Get the directory for the user's public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
Se deseja salvar os arquivos que são privados para o seu aplicativo, você pode adquirir o
diretório adequado chamando getExternalFilesDir() e passando um nome que indique
o tipo de diretório que deseja. Cada diretório criado dessa forma é adicionado ao diretório principal
que contém todos os arquivos do armazenamento externo do aplicativo que o sistema exclui quando o
usuário faz a desinstalação.
Por exemplo, este é um método que pode ser usado para criar um diretório para um álbum de fotos individual:
public File getAlbumStorageDir(Context context, String albumName) {
// Get the directory for the app's private pictures directory.
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
Se nenhum dos nomes predefinidos de subdiretórios atender seus arquivos, você pode, em vez disso, chamar getExternalFilesDir() e passar null. Isso
retorna o diretório raiz para o diretório privado do aplicativo no armazenamento externo.
Lembre-se de que getExternalFilesDir()
cria um diretório dentro de um diretório que é excluído quando o usuário desinstala o aplicativo.
Se os arquivos salvos precisarem estar disponíveis após a desinstalação do
aplicativo — como
quando seu aplicativo é uma câmera e o usuário deseja manter as fotos — use getExternalStoragePublicDirectory().
Independentemente de você usar getExternalStoragePublicDirectory() para arquivos que são compartilhados ou
getExternalFilesDir() para arquivos que são privados do seu aplicativo, é importante que use
nomes de diretório fornecidos por constantes da API, como
DIRECTORY_PICTURES. Esses nomes de diretório garantem
que os arquivos sejam tratados de forma adequada pelo sistema. Por exemplo, arquivos salvos em DIRECTORY_RINGTONES são categorizados pelo scanner de mídia do sistema como toques de telefone
em vez de música.
Consultar espaço livre
Se souber de antemão a quantidade de dados a salvar, você pode chamar
getFreeSpace() ou getTotalSpace() para descobrir se há espaço suficiente disponível sem causar uma IOException. Esses métodos informam o espaço disponível atual e o
espaço total no volume de armazenamento, respectivamente. Essas informações também são úteis para evitar o preenchimento
do volume de armazenamento além de um determinado limite.
No entanto, o sistema não garante que será possível gravar a quantidade de bytes indicada
por getFreeSpace(). Se o número retornado tiver
alguns MB além do tamanho dos dados que deseja salvar ou se o sistema de arquivos
estiver abaixo de 90% cheio, é possível continuar com segurança.
Caso contrário, não grave no armazenamento.
Observação: Não é obrigatório verificar a quantidade de espaço disponível
antes de salvar o arquivo. É possível tentar gravar o arquivo imediatamente e
capturar uma IOException, se ocorrer. Essa ação é recomendada
caso você não saiba exatamente quanto espaço será necessário. Por exemplo, se
você alterar a codificação do arquivo antes de salvá-lo convertendo uma imagem PNG em
JPEG, não saberá o tamanho do arquivo antecipadamente.
Excluir um arquivo
Sempre exclua arquivos que não sejam mais necessários. A forma mais simples de excluir um
arquivo é fazer com que o arquivo de referência aberto chame delete() por conta própria.
myFile.delete();
Se o arquivo está salvo em armazenamento interno, é possível solicitar ao Context para localizar e
excluir o arquivo chamando deleteFile():
myContext.deleteFile(fileName);
Observação: Quando o usuário desinstala o aplicativo, o sistema Android também exclui:
- Todos os arquivos salvos no armazenamento interno
- Todos os arquivos que você salvou no armazenamento externo usando
getExternalFilesDir().
No entanto, recomenda-se excluir manualmente todos os arquivos em cache criados com
getCacheDir() regularmente, bem como
excluir regularmente outros arquivos que não sejam mais necessários.