Participe do evento ⁠#Android11: apresentação de lançamento da versão Beta no dia 3 de junho.

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 confidenciais 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 esse ele tiver as permissões adequadas, os arquivos armazenados nesses diretórios são apenas para uso do seu app. Para criar arquivos que outros apps possam acessar, seu app precisa armazenar esses arquivos 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 independentemente 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 a seguir descrevem como armazenar e acessar arquivos em diretórios específicos do app.

Acessar a partir do armazenamento interno

Para cada app, o sistema fornece diretórios no armazenamento interno, onde 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 dele. 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, observe que 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:

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 ter um FileOutputStream que grava em um arquivo dentro do 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 nesse diretório dentro do 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 receber uma matriz contendo os nomes de todos os arquivos no diretório filesDir chamando fileList(), conforme mostrado no snippet de código a seguir:

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 de 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 dos seguintes 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 a partir 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 (API de nível 28) ou anterior, qualquer app pode acessar arquivos específicos do app dentro do armazenamento externo, contanto que o outro app tenha as permissões de armazenamento adequadas. Para dar aos usuários mais controle sobre os próprios arquivos e para limitar a sobrecarga, os apps voltados ao Android 10 (API de nível 29) e versões mais recentes recebem acesso com escopo ao armazenamento externo, ou 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ê pode ler e gravar arquivos específicos do app no armazenamento externo. Se for MEDIA_MOUNTED_READ_ONLY, os arquivos só podem 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() == Environment.MEDIA_MOUNTED;
    }

    // Checks if a volume containing external storage is available to at least read.
    private boolean isExternalStorageReadable() {
         return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED ||
                Environment.getExternalStorageState() == 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(), como mostrado no snippet de código a seguir:

Kotlin

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

Java

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

Criar arquivos em cache

Para adicionar um arquivo específico do app ao cache no armazenamento externo, tenha 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 funciona com arquivos de mídia que são ú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, será 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() para reivindicar o espaço. Caso contrário, o app poderá invocar uma intent que inclui a ação ACTION_MANAGE_STORAGE. Essa intent exibe um aviso para o usuário, pedindo que ele escolha arquivos no dispositivo a serem removidos para que o app possa ter o espaço necessário. Se desejado, essa solicitação pode mostrar a quantidade de espaço livre disponível no dispositivo. Para mostrar essas informações úteis, use o resultado do seguinte cálculo:

    StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()
    

O snippet de código a seguir 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 {
            action = ACTION_MANAGE_STORAGE
        }
        // Display prompt to user, requesting that they choose files to remove.
    }
    

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 {
        Intent storageIntent = new Intent();
        storageIntent.setAction(ACTION_MANAGE_STORAGE);
        // Display prompt to user, requesting that they choose files to remove.
    }
    

Outros recursos

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

Vídeos