---
title: "JankStats no Android com Kotlin: meça frames perdidos em Compose"
url: "https://kotlin.dev.br/blog/jankstats-android-kotlin-compose-2026/"
markdown_url: "https://kotlin.dev.br/blog/jankstats-android-kotlin-compose-2026.MD"
description: "Use JankStats no Android com Kotlin para medir frames perdidos, correlacionar jank com telas Compose e validar correções de performance com dados reais."
date: "2026-08-24"
author: "Karina Melo"
---

# JankStats no Android com Kotlin: meça frames perdidos em Compose

Use JankStats no Android com Kotlin para medir frames perdidos, correlacionar jank com telas Compose e validar correções de performance com dados reais.


**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](/blog/android-studio-profiler-cpu-memory-network-kotlin-2026/), o [StrictMode](/blog/strictmode-android-kotlin-performance-2026/) nem [Baseline Profiles e Macrobenchmark](/blog/baseline-profiles-macrobenchmark-android-kotlin-2026/). 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](/guias/guia-kotlin-performance/), 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`:

```kotlin
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`:

```kotlin
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`, `filter` ou `loading`, 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:

```kotlin
@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](/blog/navigation-compose-navhost-navcontroller-kotlin-2026/) e com [layouts adaptativos](/blog/layouts-adaptativos-android-compose-window-size-class-2026/) 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:

1. **Confirme que há jank real** — `isJank == true` em volume relevante, não um frame isolado.
2. **Olhe o estado** — o problema está em `route=feed` + `interaction=scroll`?
3. **Reproduza em aparelho representativo** — intermediário brasileiro, não só o flagship do time.
4. **Abra o Profiler no mesmo cenário** — CPU, memória, System Trace.
5. **Formule uma hipótese estreita** — “decode de imagem na main durante fling”, “recomposição por parâmetro instável”, “consulta Room sem dispatcher”.
6. **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](/blog/configurador-estabilidade-compose-kotlin-2026/);
- decode ou resize de imagem no momento do scroll — [Coil](/blog/coil-jetpack-compose-kotlin-2026/);
- animações longas em aparelhos modestos — [animações Compose](/blog/animacoes-jetpack-compose-kotlin-2026/);
- 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](/blog/paging-3-kotlin-compose-2026/), 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](/blog/baseline-profiles-macrobenchmark-android-kotlin-2026/) 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](/blog/firebase-crashlytics-anr-android-kotlin-2026/). 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

1. **Reproduza** o engasgo com um roteiro curto (abrir feed → scroll 10 s → abrir detalhe).
2. **Ligue JankStats** e confirme jank no estado esperado.
3. **Corra StrictMode** em build de debug para descartar disco/rede na main thread.
4. **Capture System Trace / CPU Profiler** no mesmo intervalo.
5. **Investigue memória** com [LeakCanary](/blog/leakcanary-android-kotlin-memory-leaks-2026/) se o jank cresce ao longo da sessão.
6. **Corrija a causa** (dispatcher, estabilidade Compose, tamanho de imagem, paginação, inicialização — veja também [App Startup](/blog/androidx-startup-kotlin-inicializacao-app-2026/)).
7. **Remeça** com o mesmo estado e, se possível, com Macrobenchmark.
8. **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-performance` no módulo app;
- [ ] `JankStats.createAndTrack(window)` com listener agregado;
- [ ] tracking ligado em `onResume` e desligado em `onPause`;
- [ ] 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:

1. leia o [Android Studio Profiler com Kotlin](/blog/android-studio-profiler-cpu-memory-network-kotlin-2026/);
2. ative [StrictMode](/blog/strictmode-android-kotlin-performance-2026/) nas builds de desenvolvimento;
3. meça startup e cenários com [Baseline Profiles e Macrobenchmark](/blog/baseline-profiles-macrobenchmark-android-kotlin-2026/);
4. investigue retenção com [LeakCanary](/blog/leakcanary-android-kotlin-memory-leaks-2026/);
5. estabilize recomposições com o [configurador de estabilidade Compose](/blog/configurador-estabilidade-compose-kotlin-2026/).

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](/carreira/roadmap-dev-android/) e as [vagas Kotlin](/vagas/).
