Salvar um arquivo no armazenamento externo

O uso de armazenamento externo é ótimo para arquivos que você queira compartilhar com outros apps ou permitir que o usuário acesse em um computador.

O armazenamento externo geralmente está disponível por meio de dispositivos removíveis, como um cartão SD. O Android representa esses dispositivos usando um caminho, como /sdcard.

Depois de solicitar permissões de armazenamento e verificar se há armazenamento disponível, salve os seguintes tipos de arquivos:

  • Arquivos públicos: arquivos que ficarão disponíveis livremente para outros apps e para o usuário. Depois de desinstalar seu app, o usuário continuará tendo acesso a esses arquivos. Por exemplo, fotos tiradas com seu app precisam ser salvas como arquivos públicos.
  • Arquivos privados: arquivos armazenados em um diretório específico do app, acessado usando Context.getExternalFilesDir(). Esses arquivos são apagados quando o usuário desinstala seu app. Embora esses arquivos possam, tecnicamente, ser acessados pelo usuário e por outros apps por estarem no armazenamento externo, eles não têm valor para o usuário fora do seu app. Use esse diretório para arquivos que você não quiser compartilhar com outros apps.

Este guia explica como gerenciar arquivos disponíveis em um dispositivo de armazenamento externo. Para orientações para trabalhar com arquivos no armazenamento interno, veja o guia sobre como gerenciar arquivos no armazenamento interno.

Configurar um dispositivo virtual de armazenamento externo

Para fins de teste, em dispositivos sem armazenamento externo removível, utilize o comando a seguir para habilitar um disco virtual:

    adb shell sm set-virtual-disk true
    

Solicitar permissões de armazenamento externo

O Android inclui as seguintes permissões para acessar arquivos no armazenamento externo:

READ_EXTERNAL_STORAGE
Permite que um app acesse arquivos em um dispositivo de armazenamento externo.
WRITE_EXTERNAL_STORAGE
Permite que um app grave e modifique arquivos em um dispositivo de armazenamento externo. Apps com essa permissão também recebem a permissão READ_EXTERNAL_STORAGE automaticamente.

A partir do Android 4.4 (API de nível 19), a leitura ou gravação de arquivos no seu diretório específico do app não precisa de permissões de armazenamento. Se seu app for compatível com o Android 4.3 (API de nível 18) e versões anteriores e você quiser acessar somente o diretório específico do app, declare que a permissão será solicitada somente em versões anteriores do Android adicionando o atributo maxSdkVersion:

    <manifest ...>
        <!-- If you need to modify files in external storage, request
             WRITE_EXTERNAL_STORAGE instead. -->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
                         android:maxSdkVersion="18" />
    </manifest>
    

Verificar se o armazenamento externo está disponível

Considerando que o armazenamento externo pode ficar indisponível, como quando o usuário ativa o armazenamento em uma outra máquina ou remove o cartão SD que fornece o armazenamento externo, é necessário verificar sempre se o volume está disponível antes de acessá-lo. Consulte o estado de armazenamento externo chamando getExternalStorageState(). Se o estado retornado for MEDIA_MOUNTED, será possível ler e gravar seus arquivos. Se for MEDIA_MOUNTED_READ_ONLY, só será possível ler os arquivos.

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

Kotlin

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

    /* Checks if 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 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;
    }
    

Salvar em um diretório público

Caso queira salvar arquivos em armazenamento externo que outros apps consigam acessar, use uma das seguintes APIs:

  • Se for salvar uma foto, um arquivo de áudio ou um trecho de vídeo, use a API MediaStore.
  • Se for salvar qualquer outro arquivo, como um documento PDF. use o intent ACTION_CREATE_DOCUMENT, que faz parte do framework de acesso ao armazenamento.

Caso queira ocultar seus arquivos do detector de mídia, inclua um arquivo vazio chamado .nomedia no diretório específico do seu app (observe o prefixo de ponto no nome do arquivo). Isso evita que o detector de mídia leia seus arquivos de mídia e os ofereça a outros apps por meio da API MediaStore.

Salvar em um diretório privado

Caso queira salvar arquivos privados do seu app em um armazenamento externo, você pode adquirir o diretório específico do app chamando getExternalFilesDir() e transmitindo a ele um nome que indica o tipo de diretório que você quer. Cada diretório criado dessa forma é adicionado ao diretório pai que contém todos os arquivos do armazenamento externo do app, que o sistema exclui quando o usuário faz a desinstalação.

O snippet de código a seguir mostra como criar um diretório para um álbum de fotos individual:

Kotlin

    fun getPrivateAlbumStorageDir(context: Context, albumName: String): File? {
        // Get the directory for the app's private pictures directory.
        val file = File(context.getExternalFilesDir(
                Environment.DIRECTORY_PICTURES), albumName)
        if (!file?.mkdirs()) {
            Log.e(LOG_TAG, "Directory not created")
        }
        return file
    }
    

Java

    public File getPrivateAlbumStorageDir(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;
    }
    

É importante que você use os nomes de diretório fornecidos por constantes de 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 detector de mídia do sistema como toques de telefone em vez de músicas.

Se nenhum dos nomes predefinidos de subdiretórios for adequado aos seus arquivos, você pode chamar getExternalFilesDir() e transmitir null. Isso retorna o diretório raiz para o diretório privado do app no armazenamento externo.

Escolher entre vários locais de armazenamento

Às vezes, um dispositivo que aloca uma partição da memória interna para uso como armazenamento externo também fornece um slot para cartão SD. Isso significa que o dispositivo possui dois diretórios de armazenamento externo diferentes. Por isso, é necessário selecionar qual deles usar ao gravar arquivos "privados" no armazenamento externo.

A partir do Android 4.4 (API de nível 19), é possível acessar os dois locais chamando getExternalFilesDirs(), que retorna uma matriz File com entradas para cada local de armazenamento. A primeira entrada na matriz é considerada o armazenamento externo principal e o local a ser usado, a menos que esteja cheio ou indisponível.

Se seu app for compatível com o Android 4.3 e versões anteriores, use o método estático da Biblioteca de Suporte, ContextCompat.getExternalFilesDirs(). Isso sempre retorna uma matriz File. No entanto, se o dispositivo estiver executando o Android 4.3 ou versões anteriores, ele terá apenas uma entrada para o armazenamento externo principal. Se houver um segundo local de armazenamento, não será possível acessá-lo no Android 4.3 e versões anteriores.

Nomes de volume únicos

Apps voltados ao Android 10 (API de nível 29) ou versões posteriores podem acessar o nome único que o sistema atribui para cada dispositivo de armazenamento externo. Esse sistema de nomenclatura ajuda você a organizar e indexar o conteúdo de maneira eficiente, além de dar controle sobre onde novos conteúdos serão armazenados.

O principal dispositivo de armazenamento compartilhado é sempre chamado de VOLUME_EXTERNAL_PRIMARY. Você pode encontrar outros volumes chamando MediaStore.getExternalVolumeNames().

Para consultar, inserir, atualizar ou excluir um volume específico, transmita o nome do volume a qualquer um dos métodos getContentUri() disponíveis na API MediaStore, como mostrado no snippet de código a seguir:

    // Assumes that the storage device of interest is the 2nd one
    // that your app recognizes.
    val volumeNames = MediaStore.getExternalVolumeNames(context)
    val selectedVolumeName = volumeNames[1]
    val collection = MediaStore.Audio.Media.getContentUri(selectedVolumeName)
    // ... Use a ContentResolver to add items to the returned media collection.
    

Outros recursos

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

Codelabs