Gerenciar o acesso ao armazenamento externo com escopo

Para que os usuários tenham mais controle sobre os próprios arquivos e para limitar a sobrecarga, os apps segmentados para o Android 10 (API de nível 29) e posterior recebem acesso com escopo a um dispositivo de armazenamento externo, ou armazenamento com escopo, por padrão. Esses apps só podem ver o diretório específico do app, que é acessado usando Context.getExternalFilesDir(), e tipos específicos de mídia. É uma prática recomendada usar o armazenamento com escopo, a menos que o app precise de acesso a um arquivo que não resida no próprio diretório ou no MediaStore.

A tabela a seguir resume como o armazenamento com escopo afeta o acesso a arquivos:

Localização do arquivo Permissões necessárias Método de acesso (*) Arquivos removidos quando o app é desinstalado?
Diretório específico do app Nenhuma getExternalFilesDir() Sim
Coleções de mídia
(fotos, vídeos, áudio)
READ_EXTERNAL_STORAGE
apenas ao
acessar arquivos de outros apps
MediaStore Não
Downloads
(documentos e
e-books)
Nenhuma Estrutura de acesso ao armazenamento
(carrega o seletor de arquivos do sistema)
Não

*Você pode usar a Estrutura de acesso ao armazenamento para acessar cada local mostrado na tabela anterior sem solicitar nenhuma permissão.

Esta página descreve os arquivos que seu app pode acessar ao usar o armazenamento com escopo. Ela também explica como atualizar seu app para que ele continue compartilhando, acessando e modificando outros arquivos salvos em um dispositivo de armazenamento externo.

Permissões necessárias para acesso a arquivos

Um app que usa o armazenamento com escopo sempre tem acesso de leitura/gravação aos arquivos criados, tanto dentro quanto fora do próprio diretório. Consequentemente, se seu app salva e acessa apenas os arquivos que ele cria, você não precisa solicitar as permissões READ_EXTERNAL_STORAGE ou WRITE_EXTERNAL_STORAGE.

No entanto, para acessar arquivos criados por outros apps, as duas condições a seguir precisam ser atendidas:

  1. Seu app recebeu a permissão READ_EXTERNAL_STORAGE.
  2. Os arquivos residem em um dos seguintes conjuntos de mídia bem-definidos:

Para acessar qualquer outro arquivo criado por outro app, inclusive arquivos em um diretório de downloads, seu app precisa usar a Estrutura de acesso ao armazenamento, que permite que o usuário selecione um arquivo específico.

Mesmo com a permissão READ_EXTERNAL_STORAGE, se esse app que acessar a visualização bruta do sistema de arquivos de um dispositivo de armazenamento externo, ele só terá acesso ao diretório específico do app. Se um app tentar abrir arquivos fora desse diretório usando uma visualização bruta de sistema de arquivos, ocorrerá um erro:

Restrições de dados de mídia

O armazenamento com escopo impõe as seguintes restrições relacionadas a dados de mídia:

  • Os metadados EXIF em arquivos de imagem são editados, a menos que o app tenha recebido a permissão ACCESS_MEDIA_LOCATION. Saiba mais na seção sobre como acessar informações de local em fotos.
  • A tabela MediaStore.Files é filtrada, exibindo apenas fotos, vídeos e arquivos de áudio. Por exemplo, ela não exibe arquivos PDF.
  • Todo acesso a arquivos de mídia precisa ser iniciado em código baseado em Java ou em Kotlin usando MediaStore no seu código baseado em Java ou em Kotlin. Veja as orientações sobre como acessar arquivos de mídia em código nativo.

O guia que descreve como trabalhar com arquivos de mídia mostra as práticas recomendadas para acessar documentos individuais e árvores de documentos no MediaStore. Se seu app usa armazenamento com escopo, esses métodos de acesso a mídias são obrigatórios.

Informações de local em fotos

Algumas fotos contêm informações de local nos metadados EXIF, que permitem que os usuários vejam o lugar onde uma foto foi tirada. No entanto, como essas informações de local são confidenciais, o Android 10 as ocultará do seu app por padrão se o armazenamento com escopo for usado.

Caso seu app precise de acesso às informações de local de uma foto, siga as etapas a seguir:

  1. Solicite a permissão ACCESS_MEDIA_LOCATION no manifesto do app.
  2. No objeto MediaStore, chame setRequireOriginal(), passando o URI da foto, conforme mostrado no snippet de código a seguir:

    Kotlin

        // Get location data from the ExifInterface class.
        val photoUri = MediaStore.setRequireOriginal(photoUri)
        contentResolver.openInputStream(photoUri).use { stream ->
            ExifInterface(stream).run {
                // If lat/long is null, fall back to the coordinates (0, 0).
                val latLong = ?: doubleArrayOf(0.0, 0.0)
            }
        }
        

    Java

        Uri photoUri = Uri.withAppendedPath(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                cursor.getString(idColumnIndex));
    
        final double[] latLong;
    
        // Get location data from the ExifInterface class.
        photoUri = MediaStore.setRequireOriginal(photoUri);
        InputStream stream = getContentResolver().openInputStream(photoUri);
        if (stream != null) {
            ExifInterface exifInterface = new ExifInterface(stream);
            double[] returnedLatLong = exifInterface.getLatLong();
    
            // If lat/long is null, fall back to the coordinates (0, 0).
            latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
            // Don't reuse the stream associated with the instance of "ExifInterface".
            stream.close();
        } else {
            // Failed to load the stream, so return the coordinates (0, 0).
            latLong = new double[2];
        }
        

Desativar o armazenamento com escopo

Antes de o app ser totalmente compatível com o armazenamento com escopo, é possível desativá-lo temporariamente com base no nível do SDK de destino do app ou no atributo do manifesto requestLegacyExternalStorage:

  • Segmente o Android 9 (API de nível 28) ou anterior.
  • Se você segmentar o Android 10 ou posterior, defina o valor de requestLegacyExternalStorage como true no arquivo de manifesto do app:

        <manifest ... >
          <!-- This attribute is "false" by default on apps targeting
               Android 10 or higher. -->
          <application android:requestLegacyExternalStorage="true" ... >
            ...
          </application>
        </manifest>
        

Para testar como um app segmentado para o Android 9 ou anterior se comporta ao usar o armazenamento com escopo, ative o comportamento definindo o valor de requestLegacyExternalStorage como false.