Programação Por Equipe DevBras

Capa do artigo: Onde o app Android guarda dados: o que some no cache, no backup e na desinstalação

Onde o app Android guarda dados: o que some no cache, no backup e na desinstalação

Escolha entre DataStore, pasta interna, cache, pasta externa do app e MediaStore sabendo quanto cada um dura, e evite que o único exemplar do documento da pessoa suma.

O app de recibos escaneia a nota fiscal, gera um PDF e grava "no celular". Uma semana depois, a pessoa usa um app de limpeza, o sistema libera espaço, e os PDFs do mês somem. Eles estavam em cacheDir. A resposta curta: cada lugar de armazenamento do Android tem um prazo de vida diferente. Cache é para o que se baixa de novo; a pasta interna do app dura até a desinstalação ou a limpeza de dados; o que a pessoa precisa encontrar fora do app vai para o armazenamento compartilhado; e o que não pode se perder precisa de uma cópia no seu servidor.

Este guia percorre essas gavetas com o mesmo app de recibos: um ajuste de tema, um rascunho de recibo em edição, miniaturas das notas, o PDF final e o token de login. Para cada dado, a pergunta é a mesma: quanto tempo ele precisa durar e quem pode apagá-lo.

Por que "salvo" não quer dizer a mesma coisa em todo lugar

O Android isola cada app numa área privada e controla quando ela é limpa. Há três eventos que apagam dados sem o seu código participar: a pessoa toca em "Limpar cache", a pessoa toca em "Limpar armazenamento" (ou "Limpar dados") e a pessoa desinstala o app. Além disso, o próprio sistema pode apagar arquivos de cache quando falta espaço.

Há também um evento que traz dados de volta: o backup automático. A partir do Android 6, o sistema faz backup de boa parte da área privada do app na conta Google da pessoa, com limite de tamanho, e restaura na reinstalação. Isso surpreende nos dois sentidos: dado que você achava que sumiria volta, e dado que não deveria voltar, como um identificador de aparelho, volta também.

As gavetas e quanto cada uma dura

  • DataStore (ou SharedPreferences): chave e valor pequenos, para ajustes. Fica na área privada. Some ao limpar armazenamento e ao desinstalar. Entra no backup automático.
  • filesDir: arquivos privados do app. Sobrevive a "Limpar cache". Some ao limpar armazenamento e ao desinstalar. Entra no backup automático.
  • cacheDir: arquivos descartáveis. Pode sumir a qualquer momento, por ação da pessoa ou do sistema. Fica fora do backup.
  • noBackupFilesDir: como filesDir, mas fora do backup. Bom para o que é ligado a este aparelho.
  • getExternalFilesDir(): pasta do app no armazenamento externo. Muita gente acha que sobrevive à desinstalação, mas também é apagada junto com o app.
  • MediaStore ou seletor de documentos: armazenamento compartilhado, como Downloads e Documentos. A pessoa vê o arquivo no gerenciador e ele fica depois da desinstalação.

Como decidir onde cada dado vai

  1. Pergunte se o dado existe em outro lugar. Se o servidor tem a nota, o aparelho guarda só uma cópia de conveniência.
  2. Pergunte se a pessoa espera achar o arquivo fora do app. Se sim, vai para o armazenamento compartilhado.
  3. Pergunte se dá para recriar. Miniatura, resposta de API, imagem baixada: cache.
  4. Pergunte se pode voltar numa reinstalação. Se não pode, use noBackupFilesDir ou exclua do backup.
  5. Pergunte se é segredo. Token e chave nunca ficam em texto puro; criptografe com uma chave do Android Keystore e prefira prazo curto.

O ajuste de tema no DataStore

import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore

val Context.ajustes by preferencesDataStore(name = "ajustes")
private val TEMA_ESCURO = booleanPreferencesKey("tema_escuro")

suspend fun salvarTema(context: Context, escuro: Boolean) {
    context.ajustes.edit { prefs -> prefs[TEMA_ESCURO] = escuro }
}

Perder esse valor custa um toque para a pessoa. É exatamente o tipo de dado que pode morar numa preferência.

O rascunho do recibo em filesDir

import android.content.Context
import java.io.File
import java.io.IOException

fun salvarRascunho(context: Context, json: String) {
    val pasta = File(context.filesDir, "rascunhos").apply { mkdirs() }
    val temporario = File(pasta, "recibo-atual.json.tmp")
    val destino = File(pasta, "recibo-atual.json")
    temporario.writeText(json)
    if (!temporario.renameTo(destino)) {
        throw IOException("Não foi possível gravar o rascunho")
    }
}

Gravar num temporário e renomear evita um rascunho pela metade se o app for encerrado no meio da escrita. O rascunho sobrevive a "Limpar cache", mas não sobrevive a "Limpar armazenamento". Se o rascunho tem horas de trabalho, envie para o servidor de tempos em tempos.

As miniaturas em cacheDir

fun arquivoDaMiniatura(context: Context, notaId: Long): File =
    File(File(context.cacheDir, "miniaturas").apply { mkdirs() }, "$notaId.jpg")

Antes de usar, confira se o arquivo existe; se não existir, gere de novo a partir da nota. Código que assume que o cache está lá quebra no primeiro aparelho com pouco espaço.

O PDF final em Downloads, pelo MediaStore

import android.content.ContentValues
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.annotation.RequiresApi

@RequiresApi(Build.VERSION_CODES.Q)
fun salvarPdfEmDownloads(context: Context, nome: String, bytes: ByteArray): Uri? {
    val valores = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, nome)
        put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/Recibos")
    }
    val resolver = context.contentResolver
    val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, valores) ?: return null
    resolver.openOutputStream(uri)?.use { it.write(bytes) }
    return uri
}

Esse código vale a partir do Android 10. Para versões anteriores, ou quando a pessoa deve escolher a pasta, use o seletor de documentos do sistema (ACTION_CREATE_DOCUMENT), que não exige permissão de armazenamento. Lembre que, depois de uma reinstalação, o app não é mais considerado dono desses arquivos; para abri-los de novo, a pessoa escolhe pelo seletor.

Backup automático: controle o que volta

O backup automático é ligado por padrão com android:allowBackup no manifesto. A partir do Android 12, as regras de inclusão e exclusão ficam num arquivo XML apontado por android:dataExtractionRules; antes disso, por android:fullBackupContent. Use essas regras para tirar do backup o que é ligado ao aparelho, como o token de notificação push ou a chave criptografada pelo Keystore, que não funciona em outro celular. A documentação oficial de backup do Android tem o formato exato dos arquivos.

Token de login: o que não guardar

Token em preferência aberta é legível em aparelho com root e em qualquer backup extraído. Criptografe com uma chave gerada no Android Keystore, que não sai do hardware, guarde em noBackupFilesDir ou exclua do backup, e use token de vida curta com renovação. Se a pessoa reinstalar, ela entra de novo. Isso é o comportamento correto.

Erros comuns

  • Gravar documento da pessoa em cacheDir. O sistema apaga quando falta espaço e o único exemplar some.
  • Achar que getExternalFilesDir() sobrevive à desinstalação. Ela é apagada junto com o app.
  • Token em SharedPreferences sem criptografia. Vaza em backup e em aparelho comprometido.
  • Ignorar o backup automático. Um identificador de aparelho volta em outro celular e dois dispositivos se passam pelo mesmo.
  • Usar o caminho de arquivo de Downloads em vez do Uri. Em versões novas do Android, o acesso direto por caminho falha ou depende de permissão que o app não tem.
  • Não avisar o que se perde ao desinstalar. A pessoa troca de celular e descobre tarde que os rascunhos ficaram no aparelho velho.

Checklist

  • Cada dado do app tem um lugar escolhido pelo prazo de vida, não por hábito.
  • Nada em cacheDir é o único exemplar de alguma coisa.
  • Arquivos que a pessoa precisa achar fora do app vão para MediaStore ou seletor de documentos.
  • Token e chaves estão criptografados com Keystore e fora do backup.
  • As regras de backup foram revisadas para o que é ligado ao aparelho.
  • O app funciona depois de "Limpar cache" e depois de "Limpar armazenamento".
  • Dados importantes têm cópia no servidor.

Como testar

Com o celular ligado pelo cabo e o app em modo de depuração (veja celular Android para depurar), simule a limpeza total e reabra o app:

adb shell pm clear com.exemplo.recibos

Depois limpe só o cache pelas configurações do sistema e abra de novo. Anote o que sumiu em cada caso. Se o app depende de algo rodando em segundo plano para sincronizar antes disso, leia também app Android em segundo plano.

Desinstale o seu app num aparelho de teste, instale de novo e abra. Qual dado voltou pelo backup, qual sumiu, e cada um desses resultados foi uma decisão sua?

Avalie este artigo

Referência: https://zarelli.wordpress.com/2012/08/03/android-opcoes-de-armazenamento/