Acessar arquivos específicos do app

Em muitos casos, o app cria arquivos que outros apps não precisam ou não podem acessar. O sistema fornece os seguintes locais para armazenar esses arquivos específicos do app:

  • Diretórios de armazenamento interno: esses diretórios incluem um local dedicado para armazenar arquivos persistentes e outro para armazenar dados em cache. O sistema impede que outros apps acessem esses locais. No Android 10 (API de nível 29) e versões mais recentes, esses locais são criptografados. Essas características fazem desses locais bons lugares para armazenar dados sensíveis que somente o próprio app pode acessar.

  • Diretórios de armazenamento externo: esses diretórios incluem um local dedicado para armazenar arquivos persistentes e outro para armazenar dados em cache. Embora seja possível que outro app acesse esses diretórios se ele tiver as permissões adequadas, os arquivos armazenados neles são apenas para uso do seu app. Para criar arquivos que outros apps possam acessar, seu app precisa os armazenar na parte de armazenamento compartilhado do armazenamento externo.

Quando o usuário desinstala o app, os arquivos salvos no armazenamento específico do app são removidos. Devido a esse comportamento, não use esse armazenamento para salvar dados que o usuário espera que persistam de forma independente do seu app. Por exemplo, se o app permite a captura de fotos, o usuário espera poder acessar essas fotos mesmo depois de desinstalar o app. Por isso, use o armazenamento compartilhado para salvar esses tipos de arquivo nas coleções de mídia adequadas.

As seções abaixo descrevem como armazenar e acessar arquivos em diretórios específicos do app.

Acessar do armazenamento interno

Para cada app, o sistema fornece diretórios no armazenamento interno, em que os apps podem organizar os próprios arquivos. Um diretório é criado para os arquivos persistentes do app e o outro contém os arquivos armazenados em cache. O app não precisa de permissões do sistema para ler e gravar arquivos nesses diretórios.

Os outros apps não podem acessar arquivos salvos no armazenamento interno. Isso faz com que o armazenamento interno seja um bom lugar para salvar os dados do app que outros apps não podem acessar.

No entanto, esses diretórios costumam ser pequenos. Antes de gravar arquivos específicos do app no armazenamento interno, o app precisa consultar o espaço livre no dispositivo.

Acessar arquivos persistentes

Os arquivos persistentes comuns do app ficam em um diretório que pode ser acessado usando a propriedade filesDir de um objeto de contexto. O framework fornece vários métodos para ajudar você a acessar e armazenar arquivos nesse diretório.

Acessar e armazenar arquivos

É possível usar a API File para acessar e armazenar arquivos:

A fim de manter o bom desempenho do app, não abra e feche o mesmo arquivo várias vezes.

O snippet de código abaixo demonstra como usar a API File:

Kotlin

val file = File(context.filesDir, filename)

Java

File file = new File(context.getFilesDir(), filename);

Armazenar um arquivo usando um stream

Como alternativa ao uso da API File, você pode chamar openFileOutput() para acessar um FileOutputStream que grava em um arquivo no diretório filesDir.

O snippet de código a seguir mostra como gravar texto em um arquivo:

Kotlin

val filename = "myfile"
val fileContents = "Hello world!"
context.openFileOutput(filename, Context.MODE_PRIVATE).use {
        it.write(fileContents.toByteArray())
}

Java

String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.toByteArray());
}

Para permitir que outros apps acessem arquivos armazenados no diretório no armazenamento interno, use um FileProvider com o atributo FLAG_GRANT_READ_URI_PERMISSION.

Acessar um arquivo usando um stream

Para ler um arquivo como um stream, use openFileInput():

Kotlin

context.openFileInput(filename).bufferedReader().useLines { lines ->
    lines.fold("") { some, text ->
        "$some\n$text"
    }
}

Java

FileInputStream fis = context.openFileInput(filename);
InputStreamReader inputStreamReader =
        new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
    String line = reader.readLine();
    while (line != null) {
        stringBuilder.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Error occurred when opening raw file for reading.
} finally {
    String contents = stringBuilder.toString();
}

Ver lista de arquivos

É possível acessar uma matriz contendo os nomes de todos os arquivos no diretório filesDir ao chamar fileList(), conforme mostrado no snippet de código abaixo:

Kotlin

var files: Array<String> = context.fileList()

Java

Array<String> files = context.fileList();

Criar diretórios aninhados

Também é possível criar diretórios aninhados ou abrir um diretório interno chamando getDir() com um código baseado em Kotlin ou transmitindo o diretório raiz e um novo nome de diretório para um construtor File com um código baseado em Java:

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

File directory = context.getFilesDir();
File file = new File(directory, filename);

Criar arquivos em cache

Se você precisar armazenar dados confidenciais apenas temporariamente, use o diretório de cache designado do app no armazenamento interno para salvar os dados. Como acontece com todo o armazenamento específico do app, os arquivos armazenados nesse diretório são removidos quando o usuário desinstala o app, embora eles possam ser removidos antes.

Para criar um arquivo armazenado em cache, chame File.createTempFile():

Kotlin

File.createTempFile(filename, null, context.cacheDir)

Java

File.createTempFile(filename, null, context.getCacheDir());

O app acessa arquivos nesse diretório usando a propriedade cacheDir de um objeto de contexto e a API File:

Kotlin

val cacheFile = File(context.cacheDir, filename)

Java

File cacheFile = new File(context.getCacheDir(), filename);

Remover arquivos em cache

Embora o Android às vezes exclua arquivos armazenados em cache por conta própria, não dependa do sistema para limpar esses arquivos. Sempre mantenha os arquivos do app salvos em cache no armazenamento interno.

Para remover um arquivo do diretório de cache no armazenamento interno, use um destes métodos:

  • O método delete() em um objeto File que representa o arquivo:

    Kotlin

    cacheFile.delete()
    

    Java

    cacheFile.delete();
    
  • O método deleteFile() do contexto do app, transmitindo o nome do arquivo:

    Kotlin

    context.deleteFile(cacheFileName)
    

    Java

    context.deleteFile(cacheFileName);
    

Acessar do armazenamento externo

Se o armazenamento interno não fornecer espaço suficiente para armazenar arquivos específicos do app, considere o uso do armazenamento externo. O sistema fornece diretórios dentro do armazenamento externo, onde um app pode organizar arquivos que têm valor para o usuário apenas dentro do seu app. Um diretório é criado para os arquivos persistentes do seu app e outro contém os arquivos armazenados em cache dele.

No Android 4.4 (API de nível 19) ou mais recentes, o app não precisa solicitar permissões relacionadas ao armazenamento para acessar diretórios específicos do app no armazenamento externo. Os arquivos armazenados nesses diretórios são removidos quando o app é desinstalado.

Em dispositivos com o Android 9 (nível 28 da API) ou versões anteriores, o app pode acessar os arquivos específicos que pertencem a outros apps, desde que ele tenha as permissões de armazenamento adequadas. Para dar mais controle aos usuários sobre os próprios arquivos e limitar a sobrecarga, os apps direcionados ao Android 10 (nível 29 da API) ou mais recentes recebem acesso com escopo ao armazenamento externo, ou ao armazenamento com escopo, por padrão. Quando o armazenamento com escopo está ativado, os apps não podem acessar os diretórios específicos do app que pertencem a outros apps.

Verificar se o armazenamento está disponível

Como o armazenamento externo reside em um volume físico que o usuário pode remover, verifique se o volume pode ser acessado antes de tentar ler ou gravar dados específicos do app no armazenamento externo.

É possível consultar o estado do volume chamando Environment.getExternalStorageState(). Se o estado retornado for MEDIA_MOUNTED, você poderá ler e gravar arquivos específicos do app no armazenamento externo. Se for MEDIA_MOUNTED_READ_ONLY, os arquivos só poderão ser lidos.

Por exemplo, os métodos a seguir são úteis para determinar a disponibilidade do armazenamento:

Kotlin

// Checks if a volume containing external storage is available
// for read and write.
fun isExternalStorageWritable(): Boolean {
    return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}

// Checks if a volume containing external storage is available to at least read.
fun isExternalStorageReadable(): Boolean {
     return Environment.getExternalStorageState() in
        setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
}

Java

// Checks if a volume containing external storage is available
// for read and write.
private boolean isExternalStorageWritable() {
    return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}

// Checks if a volume containing external storage is available to at least read.
private boolean isExternalStorageReadable() {
     return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||
            Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
}

Em dispositivos sem armazenamento externo removível, use o seguinte comando para ativar um volume virtual e testar a lógica de disponibilidade do armazenamento externo:

adb shell sm set-virtual-disk true

Selecionar um local físico de armazenamento

Às vezes, um dispositivo que aloca uma partição da própria memória interna para uso como armazenamento externo também tem um slot para cartão SD. Isso significa que o dispositivo possui vários volumes físicos que podem conter armazenamento externo. Por isso, é necessário selecionar qual deles usar para o armazenamento específico do app.

Para acessar os diferentes locais, chame ContextCompat.getExternalFilesDirs(). Como mostrado no snippet de código, o primeiro elemento na matriz retornada é considerado o volume de armazenamento externo principal. Use esse volume, a menos que ele esteja cheio ou indisponível.

Kotlin

val externalStorageVolumes: Array<out File> =
        ContextCompat.getExternalFilesDirs(applicationContext, null)
val primaryExternalStorage = externalStorageVolumes[0]

Java

File[] externalStorageVolumes =
        ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
File primaryExternalStorage = externalStorageVolumes[0];

Acessar arquivos persistentes

Para acessar arquivos específicos do app no armazenamento externo, chame getExternalFilesDir().

A fim de manter o bom desempenho do app, não abra e feche o mesmo arquivo várias vezes.

O snippet de código abaixo demonstra como chamar getExternalFilesDir():

Kotlin

val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)

Java

File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);

Criar arquivos em cache

Para adicionar um arquivo específico do app ao cache no armazenamento externo, acesse uma referência ao externalCacheDir:

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

File externalCacheFile = new File(context.getExternalCacheDir(), filename);

Remover arquivos em cache

Para remover um arquivo do diretório de cache externo, use o método delete() em um objeto File que representa o arquivo:

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

Conteúdo de mídia

Se o app funcionar com arquivos de mídia que sejam úteis para o usuário apenas dentro dele, é recomendável armazená-los em diretórios específicos do app no armazenamento externo, conforme demonstrado no seguinte snippet de código:

Kotlin

fun getAppSpecificAlbumStorageDir(context: Context, albumName: String): File? {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    val file = File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName)
    if (!file?.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created")
    }
    return file
}

Java

@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (file == null || !file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

É importante usar nomes de diretórios fornecidos por constantes da API, como DIRECTORY_PICTURES. Esses nomes de diretório garantem que os arquivos sejam tratados de forma adequada pelo sistema. Se nenhum dos nomes de subdiretórios predefinidos for adequado para seus arquivos, transmita null para getExternalFilesDir(). Isso retorna o diretório raiz específico do app no armazenamento externo.

Consultar espaço livre

Boa parte dos usuários não tem muito espaço de armazenamento disponível nos dispositivos. Por isso, o app precisa consumir espaço de modo cuidadoso.

Se você souber com antecedência a quantidade de dados que está armazenando, é possível descobrir quanto espaço o dispositivo pode fornecer ao app chamando getAllocatableBytes(). O valor de retorno de getAllocatableBytes() pode ser maior do que a quantidade atual de espaço livre no dispositivo. Isso ocorre porque o sistema identificou arquivos que podem ser removidos dos diretórios de cache de outros apps.

Se houver espaço suficiente para salvar os dados do app, chame allocateBytes(). Caso contrário, o app pode solicitar que o usuário remova alguns arquivos do dispositivo ou remova todos os arquivos de cache do dispositivo.

O snippet de código abaixo mostra um exemplo de como o app pode consultar o espaço livre no dispositivo:

Kotlin

// App needs 10 MB within internal storage.
const val NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

val storageManager = applicationContext.getSystemService<StorageManager>()!!
val appSpecificInternalDirUuid: UUID = storageManager.getUuidForPath(filesDir)
val availableBytes: Long =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid)
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
        appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP)
} else {
    val storageIntent = Intent().apply {
        // To request that the user remove all app cache files instead, set
        // "action" to ACTION_CLEAR_APP_CACHE.
        action = ACTION_MANAGE_STORAGE
    }
}

Java

// App needs 10 MB within internal storage.
private static final long NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

StorageManager storageManager =
        getApplicationContext().getSystemService(StorageManager.class);
UUID appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
long availableBytes =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
            appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP);
} else {
    // To request that the user remove all app cache files instead, set
    // "action" to ACTION_CLEAR_APP_CACHE.
    Intent storageIntent = new Intent();
    storageIntent.setAction(ACTION_MANAGE_STORAGE);
}

Criar uma atividade de gerenciamento de armazenamento

Um app pode declarar e criar uma atividade personalizada que, quando iniciada, permite que o usuário gerencie os dados que o app armazenou no dispositivo. Para declarar uma atividade personalizada "gerenciar espaço", os apps usam o atributo android:manageSpaceActivity no arquivo de manifesto. Os apps gerenciadores de arquivos podem invocar essa atividade mesmo quando o app não a exporta, ou seja, quando a atividade define android:exported como false.

Pedir que o usuário remova alguns arquivos do dispositivo

Para solicitar que o usuário selecione alguns arquivos no dispositivo que vão ser removidos, invoque uma intent que inclua a ação ACTION_MANAGE_STORAGE. Essa intent exibe uma solicitação ao usuário. Essa solicitação pode exibir a quantidade de espaço livre disponível no dispositivo, se você quiser. Para mostrar essas informações úteis, use o resultado do cálculo abaixo:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

Pedir que o usuário remova todos os arquivos em cache

Também é possível solicitar que o usuário limpe os arquivos de cache de todos os apps no dispositivo. Para fazer isso, invoque uma intent que contenha a ação de intent ACTION_CLEAR_APP_CACHE.

Outros recursos

Para mais informações sobre como salvar arquivos no armazenamento do dispositivo, consulte os seguintes recursos.

Vídeos