Resposta rápida: ative o StrictMode apenas nas builds de desenvolvimento para encontrar operações de disco ou rede na main thread, recursos que não foram fechados e outros usos incorretos da plataforma Android. Configure uma ThreadPolicy para violações ligadas à thread e uma VmPolicy para problemas do processo, registre os achados com penaltyLog() e corrija a causa em vez de liberar tudo com permitAll(). StrictMode é uma ferramenta de diagnóstico: ele ajuda a revelar trabalho bloqueante e vazamentos, mas não mede sozinho a experiência do usuário, não garante ausência de ANR e não deve derrubar o app de produção por padrão.
Um app pode parecer rápido no aparelho de quem desenvolve e travar em um celular intermediário quando abre um arquivo, consulta o banco ou inicializa uma biblioteca. Parte desses problemas nasce de operações curtas demais para chamar atenção durante o desenvolvimento, mas longas o suficiente para atrasar frames, cliques e transições.
O StrictMode transforma vários desses comportamentos em sinais visíveis no Logcat. Ele funciona como uma cerca de proteção para o ciclo de desenvolvimento: quando uma regra é violada, a plataforma registra, destaca ou pune a operação conforme a política configurada.
Neste guia, você vai aprender a ativar StrictMode com Kotlin, entender a diferença entre ThreadPolicy e VmPolicy, interpretar violações, corrigir I/O bloqueante com coroutines e incorporar a ferramenta ao fluxo do time. Para medir o resultado depois das correções, combine a leitura com Baseline Profiles e Macrobenchmark e com o guia de Crashlytics e ANR no Android.
O que é StrictMode no Android?
StrictMode é uma API de diagnóstico do Android que detecta determinadas operações potencialmente perigosas durante a execução do app. Ela é especialmente útil para encontrar trabalho bloqueante feito na main thread, responsável por processar entrada do usuário, ciclo de vida e renderização da interface.
Se essa thread fica ocupada com leitura de arquivo, consulta lenta, resolução de rede ou inicialização pesada, a interface deixa de responder. Um atraso pequeno pode causar jank. Um bloqueio prolongado pode contribuir para um Application Not Responding (ANR).
StrictMode não impede todas essas situações automaticamente. Você declara quais violações deseja detectar e quais penalidades aplicar. Por exemplo:
- registrar leitura e escrita em disco na main thread;
- detectar acesso de rede na thread de UI;
- avisar sobre objetos fecháveis abandonados;
- encontrar alguns vazamentos relacionados a componentes Android;
- piscar a tela durante certas violações em builds de desenvolvimento;
- encerrar o processo para tornar uma regra impossível de ignorar em testes controlados.
A API trabalha com duas famílias de políticas:
| Política | Escopo | Exemplos de problema |
|---|---|---|
StrictMode.ThreadPolicy | operações executadas por uma thread | disco e rede na main thread |
StrictMode.VmPolicy | comportamento geral da VM e do processo | recursos não fechados e alguns vazamentos de componentes |
A separação é importante. Uma leitura de arquivo no clique de um botão é uma violação de thread. Um Closeable abandonado sem close() é um problema observado pela política da VM.
Como ativar StrictMode com Kotlin
A configuração mais segura fica na classe Application e é condicionada à build de debug. Dessa forma, todos os fluxos de desenvolvimento recebem a política sem introduzir penalidades deliberadas no artefato de produção.
class KotlinBrasilApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
enableStrictMode()
}
}
private fun enableStrictMode() {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
}
}
Registre a Application no manifesto, caso o projeto ainda não tenha uma:
<application
android:name=".KotlinBrasilApplication"
... />
detectAll() é um bom começo para uma build local porque habilita as detecções disponíveis para a versão do Android em execução. Entretanto, a lista concreta pode variar conforme o nível da API. Quando o time precisa de comportamento previsível ou está adotando StrictMode gradualmente em um app legado, declarar detectores específicos costuma ser melhor.
private fun enableStrictMode() {
val threadPolicy = StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
val vmPolicy = StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects()
.detectActivityLeaks()
.penaltyLog()
.build()
StrictMode.setThreadPolicy(threadPolicy)
StrictMode.setVmPolicy(vmPolicy)
}
Use apenas detectores disponíveis no minSdk e no nível de API em que a configuração é executada. O Android Studio indica chamadas que exigem uma versão mais nova; quando necessário, proteja a regra com uma verificação de Build.VERSION.SDK_INT.
O que as penalidades fazem?
Detecção e penalidade são conceitos diferentes. O detector reconhece o comportamento; a penalidade define como o app reage.
penaltyLog()
Registra a violação no Logcat com stack trace. É a opção mais prática para começar porque não altera drasticamente o fluxo do app.
No Android Studio, filtre por StrictMode e procure mensagens como:
StrictMode policy violation: android.os.strictmode.DiskReadViolation
O stack trace mostra o caminho que levou à operação. Leia primeiro os frames pertencentes ao seu pacote. O ponto onde a plataforma efetivamente lê o disco pode estar dentro de SharedPreferences, SQLite, uma biblioteca ou carregamento de arquivo, mas a decisão arquitetural geralmente está alguns frames acima.
penaltyFlashScreen()
Pode destacar visualmente determinadas violações durante o uso manual. É útil em sessões de QA porque revela que uma interação aparentemente simples disparou trabalho bloqueante.
Não dependa apenas dela: o efeito visual não explica a causa e pode não representar todas as violações.
penaltyDeath()
Encerra o processo quando ocorre uma violação compatível. Essa penalidade torna o erro impossível de ignorar, mas pode deixar o desenvolvimento impraticável quando ativada cedo em um projeto antigo.
Uma estratégia melhor é começar com log, eliminar o backlog e reservar penaltyDeath() para testes específicos, builds internas rigorosas ou regras que a equipe já consegue cumprir.
Listener personalizado
Em versões compatíveis do Android, você pode receber violações por callback e encaminhá-las a um logger interno da build de desenvolvimento:
val executor = Executors.newSingleThreadExecutor()
val policy = StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyListener(executor) { violation ->
Log.e("StrictModeReporter", "Violação detectada", violation)
}
.build()
StrictMode.setThreadPolicy(policy)
Não envie dados sensíveis ou stack traces indiscriminadamente a um serviço externo. Para produção, avalie privacidade, volume e custo antes de criar qualquer telemetria semelhante. Na maioria dos projetos, StrictMode permanece local ou restrito a builds internas.
Exemplo: leitura de arquivo na main thread
Considere uma tela que carrega um JSON de assets durante o onCreate:
class CatalogActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val json = assets.open("catalog.json")
.bufferedReader()
.use { it.readText() }
renderCatalog(json)
}
}
A leitura é síncrona e ocorre na main thread. Em um arquivo pequeno e com cache quente, o problema pode passar despercebido. Em armazenamento lento ou durante pressão de recursos, a abertura da tela fica mais cara.
Mova a operação bloqueante para Dispatchers.IO e volte à main thread apenas para atualizar a UI:
class CatalogViewModel(
private val repository: CatalogRepository,
) : ViewModel() {
private val _state = MutableStateFlow<CatalogState>(CatalogState.Loading)
val state: StateFlow<CatalogState> = _state.asStateFlow()
fun load() {
viewModelScope.launch {
_state.value = CatalogState.Loading
_state.value = runCatching {
repository.loadCatalog()
}.fold(
onSuccess = { CatalogState.Success(it) },
onFailure = { CatalogState.Error(it) },
)
}
}
}
class CatalogRepository(
private val assetReader: AssetReader,
) {
suspend fun loadCatalog(): Catalog = withContext(Dispatchers.IO) {
assetReader.readCatalog()
}
}
A correção não consiste apenas em “colocar uma coroutine”. viewModelScope.launch inicia na main thread por padrão. O trabalho bloqueante precisa atravessar explicitamente uma fronteira adequada, como withContext(Dispatchers.IO), ou ser executado por uma API assíncrona.
Também preserve cancelamento e estado de erro. Jogar uma leitura em um GlobalScope remove o alerta da main thread, mas cria um fluxo sem dono e pode atualizar uma tela que já morreu. O guia de structured concurrency em Kotlin aprofunda esse ponto.
SharedPreferences e DataStore: por que leituras pequenas também aparecem?
SharedPreferences parece uma simples tabela em memória, mas seu carregamento e persistência podem tocar o disco. Inicializar preferências em um caminho crítico, especialmente durante startup, pode gerar uma violação ou contribuir para tempo até o primeiro frame.
Não transforme toda chamada em uma exceção local. Primeiro pergunte:
- esse dado precisa ser lido antes da primeira tela?
- ele pode ser carregado de forma assíncrona?
- a UI consegue começar com um estado padrão ou de loading?
- o dado pertence a preferências ou deveria estar em banco/cache estruturado?
Para dados reativos e assíncronos, DataStore com Kotlin costuma oferecer um contrato melhor. Ainda assim, coletar dados de forma errada ou executar transformação pesada na main thread continua sendo um problema. Trocar a biblioteca não elimina a necessidade de medir.
Banco de dados na main thread
Room bloqueia por padrão várias consultas indevidas na main thread justamente porque banco pode envolver disco, locks, migração e materialização de objetos. Não use allowMainThreadQueries() como solução para um alerta de StrictMode.
Prefira APIs suspend, Flow ou paginação:
@Dao
interface ArticleDao {
@Query("SELECT * FROM article ORDER BY publishedAt DESC")
fun observeArticles(): Flow<List<ArticleEntity>>
@Query("SELECT * FROM article WHERE id = :id")
suspend fun findById(id: Long): ArticleEntity?
}
Em Compose, colete o Flow respeitando o ciclo de vida. Para listas grandes, o guia de Paging 3 com Kotlin e Compose mostra como evitar carregar toda a tabela de uma vez. Se a violação aparece na abertura do banco, verifique também migrações, callbacks e inicialização precoce; o artigo sobre migrações do Room cobre os riscos de schema.
Recursos não fechados com VmPolicy
Arquivos, streams, cursores e outros objetos fecháveis podem reter descritores nativos quando não são encerrados. Em Kotlin, a função use é a forma idiomática de garantir fechamento:
fun readText(inputStream: InputStream): String =
inputStream.bufferedReader().use { reader ->
reader.readText()
}
Mesmo quando ocorre uma exceção, use fecha o recurso. Evite guardar um InputStream, Cursor ou ParcelFileDescriptor em singleton ou propriedade de vida longa sem um contrato explícito de propriedade e fechamento.
Uma VmPolicy com detectLeakedClosableObjects() pode ajudar a encontrar esses casos. O aviso pode aparecer depois da coleta de lixo, não exatamente no momento em que o recurso foi abandonado. Por isso, o stack trace de alocação e o caminho de abertura são valiosos.
StrictMode com Jetpack Compose
Compose não muda a regra principal: a composição e os callbacks de UI normalmente executam na main thread. Portanto, não faça leitura bloqueante dentro de um composable, remember, LaunchedEffect ou callback esperando que a API mova o trabalho automaticamente.
Exemplo incorreto:
@Composable
fun ProfileScreen(repository: ProfileRepository) {
val profile = remember {
repository.loadProfileBlocking()
}
Text(profile.name)
}
remember memoriza o resultado entre recomposições compatíveis, mas a primeira execução continua bloqueando a thread atual.
Prefira um estado produzido pelo ViewModel:
@Composable
fun ProfileRoute(viewModel: ProfileViewModel) {
val state by viewModel.state.collectAsStateWithLifecycle()
when (val current = state) {
ProfileState.Loading -> CircularProgressIndicator()
is ProfileState.Content -> Text(current.profile.name)
is ProfileState.Error -> ErrorMessage(current.message)
}
}
Se uma transformação é CPU-intensiva, Dispatchers.IO também não é uma resposta universal. Trabalho de CPU deve ser avaliado para Dispatchers.Default, processamento incremental ou mudança de algoritmo. StrictMode detecta algumas categorias de uso indevido; profiler, traces e benchmarks ajudam a encontrar o restante.
Quando usar permitDiskReads() ou outras liberações locais?
StrictMode permite relaxar temporariamente uma política. Isso pode ser necessário ao integrar código legado ou uma API da plataforma cuja operação é pequena e inevitável naquele ponto.
val previousPolicy = StrictMode.allowThreadDiskReads()
try {
legacySdk.initialize(context)
} finally {
StrictMode.setThreadPolicy(previousPolicy)
}
Use esse padrão como exceção documentada, não como limpeza de Logcat. Antes de liberar:
- confirme qual operação ocorre;
- meça seu custo em cold start e aparelhos mais lentos;
- procure inicialização assíncrona ou lazy na biblioteca;
- limite o escopo ao menor bloco possível;
- restaure a política em
finally; - registre por que a exceção existe e quando poderá ser removida.
Nunca use permitAll() globalmente só para esconder violações. Isso desativa justamente a proteção que deveria revelar regressões futuras.
StrictMode detecta todos os ANRs?
Não. StrictMode encontra categorias conhecidas de comportamento, principalmente violações associadas a políticas de thread e VM. Um app ainda pode ter ANR por:
- deadlock entre threads;
- lock mantido por tempo demais;
- loop ou cálculo pesado na main thread;
- binder call lenta;
- BroadcastReceiver que demora para terminar;
- contenção durante startup;
- excesso de trabalho em callbacks de ciclo de vida;
- renderização ou layout muito custosos.
Por isso, trate StrictMode como uma camada do diagnóstico. Para incidentes reais, analise traces de ANR, vitals da distribuição, logs e reprodução. Para performance repetível, crie cenários com Macrobenchmark. Para startup, revise também AndroidX App Startup e evite inicializar todos os SDKs no Application.onCreate().
Fluxo recomendado para adotar em um projeto existente
Ativar detectAll() com penalidade fatal em um app antigo pode gerar tanto ruído que a equipe desliga a ferramenta. Uma adoção incremental costuma funcionar melhor:
- habilite
penaltyLog()apenas em debug; - percorra startup, login, home, busca e checkout ou outro fluxo crítico;
- agrupe violações por causa raiz, não por quantidade de stack traces;
- corrija primeiro operações de rede e disco em interações críticas;
- adicione testes para a arquitetura que permitiu o problema;
- crie exceções locais somente quando justificadas e medidas;
- repita a auditoria em aparelhos e versões diferentes do Android;
- torne a política mais rígida em builds internas quando o backlog estiver controlado.
Em pull requests, a revisão deve perguntar onde o trabalho executa, quem controla seu ciclo de vida e como erros/cancelamento são tratados. StrictMode encontra o sintoma em runtime; uma boa arquitetura evita que ele nasça.
Erros comuns ao usar StrictMode
Ativar apenas por alguns minutos e nunca mais rodar
Uma tela limpa hoje pode receber amanhã um SDK, uma leitura de preferência ou um fallback síncrono. Mantenha a política na build de debug para que novos fluxos sejam observados continuamente.
Culpar a última linha do stack trace
A operação de disco pode ocorrer dentro de uma biblioteca, mas seu código decidiu inicializá-la no clique ou no startup. Procure o primeiro ponto sob controle do projeto e entenda o contrato da dependência.
Mover tudo para Dispatchers.IO
Isso evita bloquear a main thread, mas não corrige duplicação, falta de cache, chamadas em excesso ou concorrência sem controle. Uma operação ruim em background continua gastando bateria, dados e recursos.
Usar penaltyDeath() em toda build de desenvolvimento desde o primeiro dia
Se o app tem muitas violações, o time passa a contornar a ferramenta. Comece observando, priorize e aumente o rigor gradualmente.
Empacotar diagnóstico agressivo em produção
Um usuário não deve perder uma operação importante porque uma penalidade de desenvolvimento encerrou o processo. Se houver motivo para coletar sinais em produção, desenhe uma estratégia própria, com amostragem, privacidade e impacto avaliados.
Checklist prático
Antes de considerar a configuração pronta, confirme:
- StrictMode é ativado na
Applicationsomente para debug ou build interna; -
ThreadPolicyeVmPolicyestão configuradas separadamente; - o Logcat mostra stack traces úteis;
- rede nunca é executada na main thread;
- leituras e escritas de disco saíram dos caminhos críticos de UI;
- DAOs usam
suspend,Flowou APIs assíncronas adequadas; - recursos fecháveis usam
useou contrato explícito de fechamento; - exceções locais restauram a política em
finally; - nenhuma correção global usa
permitAll()para esconder o problema; - fluxos críticos são medidos com ferramentas além do StrictMode;
- a equipe sabe diferenciar aviso de desenvolvimento, jank e ANR real.
Perguntas frequentes
StrictMode deve ficar ativo em produção?
Normalmente, não com as mesmas políticas e penalidades usadas em desenvolvimento. penaltyLog() pode gerar ruído e penaltyDeath() pode prejudicar usuários. O uso mais comum é em debug e builds internas. Se houver uma necessidade específica de observabilidade em produção, avalie uma solução desenhada para esse ambiente.
StrictMode substitui o Android Profiler?
Não. StrictMode aponta determinadas violações de política e fornece stack traces. O Profiler, traces, Perfetto e Macrobenchmark ajudam a investigar tempo, CPU, memória, frames e execução ao longo do fluxo. As ferramentas se complementam.
Toda DiskReadViolation precisa virar Dispatchers.IO?
A causa precisa ser investigada, mas a solução não é mecânica. Você pode remover a leitura, adiá-la, usar cache, trocar a API, inicializar de forma lazy ou movê-la para uma camada assíncrona. Se a operação for inevitável e comprovadamente segura, uma exceção local documentada pode ser aceitável.
StrictMode encontra memory leaks?
Ele detecta alguns padrões por meio de VmPolicy, como determinados recursos fecháveis e componentes vazados. Para investigar cadeias de retenção de objetos, use também LeakCanary no Android com Kotlin.
Por que a violação aparece mesmo dentro de uma coroutine?
Porque uma coroutine não significa automaticamente background thread. viewModelScope.launch e lifecycleScope.launch usam a main thread por padrão. Para uma chamada bloqueante, a implementação precisa usar uma API assíncrona ou mudar explicitamente para um dispatcher apropriado.
Conclusão
StrictMode é uma das formas mais baratas de tornar visível o trabalho que sabota a responsividade do Android. Uma configuração pequena em debug consegue revelar disco, rede, recursos abandonados e decisões de inicialização que passariam despercebidas em aparelhos rápidos.
O ganho real aparece quando o time trata cada violação como pista arquitetural. Em vez de silenciar a política, descubra por que uma tela conhece I/O bloqueante, por que um SDK precisa iniciar tão cedo ou por que um recurso não tem dono claro. Depois, valide a correção com métricas e cenários reproduzíveis.
Para continuar a trilha, estude performance em Kotlin, ANRs com Firebase Crashlytics, LeakCanary e testes de UI com Espresso. Se você quer aplicar essas práticas em projetos reais, acompanhe também as vagas Kotlin e Android publicadas no Kotlin Brasil.