Resposta rápida: use JankStats para transformar a sensação de “tela engasgando” em uma métrica objetiva: quantos frames foram perdidos, em qual janela de tempo e sob qual estado da UI. Instale o listener na Window da Activity, associe o estado atual da tela (rota, scroll, filtro, carregamento) e registre agregados em debug, testes ou telemetria. JankStats não substitui o Android Studio Profiler, o StrictMode nem Baseline Profiles e Macrobenchmark. Ele fecha o meio do caminho: medir jank contínuo enquanto o usuário (ou o teste) navega, em vez de depender só de um System Trace pontual.
Quando o feed trava ao carregar a próxima página, o scroll de uma LazyColumn “pula” ou a troca de aba demora um frame a mais do que o esperado, o time costuma discutir opiniões. Alguém diz que é a rede. Outro aponta para recomposição. Outro culpa o celular de teste. Sem uma métrica comum, a discussão se alonga e a correção vira chute.
Jank é o nome curto para frames que não chegaram a tempo. Em 60 Hz, o orçamento aproximado é de ~16,7 ms por frame. Em 90 Hz ou 120 Hz, a margem fica ainda menor. Um atraso pequeno e repetido já é perceptível. O Android expõe APIs de frame timing; o Jetpack JankStats organiza esses sinais em um relatório utilizável por apps Kotlin — inclusive Compose.
Neste guia, você vai configurar JankStats, marcar estado da UI, interpretar relatórios, integrar a métrica a Compose e Macrobenchmark, e montar um fluxo de correção que combina diagnóstico, medição e validação.
O que JankStats mede — e o que não mede
JankStats observa a janela da Activity e classifica frames com base no tempo de produção. Em termos práticos, ele ajuda a responder:
- esta interação gerou frames atrasados?
- o problema aparece só no scroll, só no cold start da tela ou o tempo todo?
- a correção reduziu a taxa de jank no mesmo cenário?
Ele não diz sozinho por que o frame atrasou. Para causa raiz, você ainda precisa de Profiler, System Trace, LeakCanary, StrictMode ou inspeção de código. A divisão útil é:
| Ferramenta | Papel |
|---|---|
| StrictMode | encontra I/O e violações na main thread durante o desenvolvimento |
| LeakCanary | encontra retenção de memória que pode pressionar o GC |
| Android Studio Profiler / System Trace | explica onde o tempo foi gasto |
| Baseline Profiles / Macrobenchmark | mede cenários controlados e startup |
| JankStats | conta e contextualiza jank enquanto a UI roda |
Se você já usa o guia de performance Kotlin, trate JankStats como o instrumento de telemetria de frames perdidos dentro desse mapa.
Dependência e setup mínimo
No Gradle Kotlin DSL do módulo :app:
dependencies {
implementation("androidx.metrics:metrics-performance:1.0.0")
}
Confirme a versão estável atual na documentação oficial do AndroidX Metrics antes de piná-la em produção. O padrão de integração não muda: criar o JankStats a partir da Window, registrar um listener e manter o objeto vivo enquanto a Activity estiver ativa.
Exemplo enxuto em uma ComponentActivity:
class MainActivity : ComponentActivity() {
private var jankStats: JankStats? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
jankStats = JankStats.createAndTrack(window) { frameData ->
if (frameData.isJank) {
Log.d(
"JankStats",
"jank=${frameData.isJank} " +
"totalNanos=${frameData.frameDurationUiNanos} " +
"states=${frameData.states}"
)
}
}
setContent {
AppTheme {
AppNavHost(
onRouteChanged = { route ->
jankStats?.performanceMetricsStateHolder
?.state
?.putState("route", route)
}
)
}
}
}
override fun onResume() {
super.onResume()
jankStats?.isTrackingEnabled = true
}
override fun onPause() {
jankStats?.isTrackingEnabled = false
super.onPause()
}
}
Pontos importantes:
- habilite o tracking só quando fizer sentido — em geral,
onResume/onPause; - não logue cada frame em produção — agregue, faça amostragem e envie resumos;
- associe estado — sem
route,scroll,filterouloading, o relatório vira ruído.
PerformanceMetricsState: o contexto que torna o número útil
Um percentual de jank sem contexto quase não ajuda. “3% de frames ruins” pode ser aceitável no cold start de uma tela pesada e inaceitável no scroll contínuo do feed. O PerformanceMetricsState existe para anexar chaves/valores ao relatório.
Estados úteis em apps brasileiros típicos (fintech, delivery, e-commerce, conteúdo):
route— destino do Navigation Compose;list— home, pedidos, busca, carteira;interaction—scroll,fling,tab_switch,checkout_step;network—idle,refreshing,paging;device_class— compact/medium/expanded, se layouts adaptativos importam.
Em Compose, você pode atualizar o estado a partir de um efeito:
@Composable
fun TrackScreenJank(
route: String,
isRefreshing: Boolean,
metricsStateHolder: PerformanceMetricsState.Holder
) {
DisposableEffect(route, isRefreshing) {
val state = metricsStateHolder.state
state?.putState("route", route)
state?.putState(
"network",
if (isRefreshing) "refreshing" else "idle"
)
onDispose {
state?.removeState("route")
state?.removeState("network")
}
}
}
Combine isso com o Navigation Compose e com layouts adaptativos quando a mesma rota se comporta diferente em tablet e dobrável.
Como interpretar um relatório de jank
O listener entrega FrameData (e variantes com mais detalhe, conforme a versão). O fluxo mental recomendado:
- Confirme que há jank real —
isJank == trueem volume relevante, não um frame isolado. - Olhe o estado — o problema está em
route=feed+interaction=scroll? - Reproduza em aparelho representativo — intermediário brasileiro, não só o flagship do time.
- Abra o Profiler no mesmo cenário — CPU, memória, System Trace.
- Formule uma hipótese estreita — “decode de imagem na main durante fling”, “recomposição por parâmetro instável”, “consulta Room sem dispatcher”.
- Corrija e remeça o mesmo estado.
Hipóteses frequentes em Compose:
- item de lista com chave instável ou conteúdo que recompõe demais — veja o configurador de estabilidade do Compose;
- decode ou resize de imagem no momento do scroll — Coil;
- animações longas em aparelhos modestos — animações Compose;
- trabalho bloqueante que StrictMode já denunciaria em debug.
JankStats + Compose: padrões que funcionam
1. Medir scroll de LazyColumn / LazyGrid
Marque interaction=scroll enquanto LazyListState.isScrollInProgress for verdadeiro. Agregue jank só nessa janela. Assim você não mistura o custo do first composition com o custo do fling.
2. Medir troca de aba e AnimatedContent
Transições são candidatas clássicas a jank. Meça antes e depois de ajustar tween/spring, e não avalie só no emulador com hardware graphics generoso.
3. Medir paging
No Paging 3 com Compose, o pico costuma aparecer quando novos itens chegam e a lista recompõe. Separe estados paging=append e paging=refresh.
4. Não confundir jank com startup
Cold start e first frame são território de Baseline Profiles e Macrobenchmark. JankStats brilha depois que a Activity está viva e o usuário interage.
Integração com Macrobenchmark e CI
Macrobenchmark já mede frame timing em cenários controlados. JankStats complementa de duas formas:
- no app de produção/debug, para telemetria contínua com amostragem;
- em testes instrumentados, para assertar que um fluxo crítico não regrediu além de um limite acordado.
Exemplo de política de time:
- scroll do feed em aparelho de referência: no máximo X% de frames janky em N segundos;
- troca Home → Detalhe → voltar: orçamento separado;
- regressão acima do limite bloqueia merge ou abre bug automático.
Use o módulo :benchmark do guia de Baseline Profiles para o cenário controlado e JankStats para o sinal “no app de verdade”. Os dois se reforçam.
O que enviar para observabilidade
Não envie um evento por frame. Prefira agregados por sessão ou por tela:
- taxa de frames janky;
- contagem absoluta de jank;
- p95/p99 de duração de frame, se disponível na sua versão/API;
- top estados associados (
route,interaction); - versão do app, modelo de aparelho, taxa de atualização da tela.
No backend ou no provedor de crash/performance, correlacione com Crashlytics e ANR. Jank alto persistente em uma rota costuma preceder reclamações de “app travando”, mesmo sem ANR formal.
Cuidados de privacidade: estado de tela não deve carregar PII. Prefira identificadores de rota (feed, checkout/step2) a títulos com nome de cliente ou query de busca livre.
Fluxo de correção recomendado
- Reproduza o engasgo com um roteiro curto (abrir feed → scroll 10 s → abrir detalhe).
- Ligue JankStats e confirme jank no estado esperado.
- Corra StrictMode em build de debug para descartar disco/rede na main thread.
- Capture System Trace / CPU Profiler no mesmo intervalo.
- Investigue memória com LeakCanary se o jank cresce ao longo da sessão.
- Corrija a causa (dispatcher, estabilidade Compose, tamanho de imagem, paginação, inicialização — veja também App Startup).
- Remeça com o mesmo estado e, se possível, com Macrobenchmark.
- Só então considere Baseline Profile se o caminho corrigido for crítico no startup/navegação inicial.
Esse ciclo evita a armadilha de “otimizar no escuro” e gera evidência útil em code review.
Erros comuns
- Ativar JankStats e não anexar estado — você fica com um percentual órfão.
- Logar tudo em produção — custo de I/O e ruído analítico.
- Medir só no emulador top de linha — jank real aparece no intermediário.
- Tratar um frame isolado como regressão — use janelas e taxas.
- Esperar que JankStats explique alocações ou leaks — use a ferramenta certa para cada pergunta.
- Otimizar animação sem medir scroll — muitos apps sofrem mais no fling do que no motion de transição.
- Ignorar taxa de atualização — 120 Hz reduz o orçamento por frame; compare aparelhos equivalentes.
Checklist prático
- dependência
androidx.metrics:metrics-performanceno módulo app; -
JankStats.createAndTrack(window)com listener agregado; - tracking ligado em
onResumee desligado emonPause; - estados
route+interaction(e outros relevantes) preenchidos; - amostragem definida para builds de release;
- cenário de scroll crítico coberto por Macrobenchmark ou teste instrumentado;
- StrictMode ativo em debug;
- hipótese de causa registrada antes do refactor;
- comparação antes/depois no mesmo aparelho e build type;
- sem PII nos estados enviados à telemetria.
Quando JankStats é suficiente — e quando não é
Vale a pena quando o app já tem navegação real, listas, animações ou paging e o time precisa de um sinal contínuo de fluidez. É especialmente útil em produtos com sessão longa (feed, carteira, backlog de pedidos), onde o engasgo aparece depois de alguns minutos.
Não comece por aqui se o app ainda faz I/O na main thread de forma óbvia, se não há testes mínimos ou se o cold start está tão lento que o usuário desiste antes de interagir. Nesse caso, priorize StrictMode, arquitetura de threads, startup e Baseline Profiles.
Perguntas frequentes
JankStats funciona com Jetpack Compose?
Sim. JankStats observa a Window da Activity; Compose renderiza nessa janela. O trabalho extra é anunciar estados (route, scroll, loading) para o relatório fazer sentido.
Qual a diferença entre JankStats e Macrobenchmark?
Macrobenchmark executa cenários controlados e produz métricas reprodutíveis em CI. JankStats pode rodar no app durante uso real ou testes e contextualizar jank com estado da UI. Use os dois.
Preciso de root ou build especial?
Não. É uma biblioteca AndroidX ligada ao app. Ainda assim, interprete resultados com o mesmo cuidado de qualquer métrica de performance: build type, aparelho e taxa de atualização influenciam.
JankStats substitui o GPU Profiler ou Systrace?
Não. Ele quantifica e contextualiza. A explicação fina de threads, binders e pipelines continua no System Trace e no Profiler.
Posso usar isso em app View System (XML)?
Sim. O mecanismo é da Window/Activity. Compose não é pré-requisito; só torna o gerenciamento de estado da UI um pouco mais idiomático via DisposableEffect.
Que taxa de jank é “aceitável”?
Depende do fluxo e do aparelho. Em vez de um número universal, defina orçamentos por cenário crítico e impeça regressões relativas. Um feed comercial costuma ser mais sensível que uma tela de configuração aberta raramente.
Conclusão e próximos passos
JankStats dá ao time Android em Kotlin uma linguagem compartilhada para fluidez: frames perdidos + estado da UI. Com ele, “está engasgando” vira “no scroll do feed, a taxa de jank subiu depois do append do Paging” — e aí o Profiler, o StrictMode e o Compose stability passam a trabalhar sobre uma hipótese clara.
Para fechar o ciclo de performance no app:
- leia o Android Studio Profiler com Kotlin;
- ative StrictMode nas builds de desenvolvimento;
- meça startup e cenários com Baseline Profiles e Macrobenchmark;
- investigue retenção com LeakCanary;
- estabilize recomposições com o configurador de estabilidade Compose.
Se o objetivo também é mercado de trabalho, jank e performance de UI aparecem em entrevistas Android sênior e em vagas que pedem Compose em produção — vale cruzar com o roadmap Android e as vagas Kotlin.