Domando o Jank em Animações Android: Throttling de Redesenhos com Perfetto e Macrobenchmark
por soft24hours team

Animações fluidas são a marca registrada de um aplicativo Android premium. No entanto, mesmo com hardware moderno, desenvolvedores frequentemente lutam contra o "jank" — aqueles engasgos irritantes que ocorrem quando o aplicativo pula frames.
Neste artigo, analisaremos um caso real de otimização de performance. Veremos como identificar uma animação ineficiente usando o Perfetto, medir seu impacto com o Jetpack Macrobenchmark e resolvê-la aplicando uma técnica chamada "Redraw Throttling" (limitação de redesenhos).
O Problema: Redesenhos Excessivos
Imagine uma animação de carregamento "shimmer" (aquele efeito de brilho que percorre um marcador). Se essa animação solicita um redesenho a cada milissegundo, ela está desperdiçando ciclos valiosos de CPU e GPU. Em cenários complexos, isso pode fazer com que a UI principal pule frames, resultando em uma experiência de usuário ruim.
Fase 1: Diagnóstico com Perfetto
O Perfetto é a ferramenta de perfil de sistema do Google. Ele nos permite ver exatamente o que o sistema operacional e nosso aplicativo estão fazendo a cada momento.
Ao registrar um rastro (trace) durante a animação problemática, notamos um número suspeitosamente alto de eventos Choreographer#doFrame e cálculos de layout. O aplicativo estava tentando redesenhar com muito mais frequência do que o necessário para um efeito visual fluido.
Análise de Dados do Trace
Usando a linguagem de consulta SQL do Perfetto, podemos quantificar o desperdício:
SELECT
ts,
dur,
name
FROM slice
WHERE name LIKE '%doFrame%'
AND dur > 16000000 -- Frames que excedem 16ms (60fps)
Essa consulta revelou que, durante a animação shimmer, o tempo de renderização dos frames tornava-se instável, causando picos de trabalho que levavam ao jank.
Fase 2: Medição com Macrobenchmark
Antes de otimizar, precisamos estabelecer uma "linha de base". A biblioteca Jetpack Macrobenchmark é perfeita para isso, pois simula cenários reais de usuário e fornece métricas precisas como o FrameOverrunMs.
Veja como configuramos o teste:
@RunWith(AndroidJUnit4::class)
class AnimationBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun setupShimmerBenchmark() = benchmarkRule.measureRepeated(
packageName = "com.example.myapp",
metrics = listOf(FrameTimingMetric()),
iterations = 5,
setupBlock = { pressHome() }
) {
startActivityAndWait()
// Deixe a animação rodar por alguns segundos
device.waitForIdle()
}
}
Fase 3: A Solução — Throttling de Redesenhos
A correção é conceitualmente simples, mas poderosa: não redesenhar a menos que um intervalo mínimo tenha passado. Em vez de reagir a cada atualização do estado da animação, limitamos (throttle) a chamada para invalidate().
Implementação em uma Custom View
private var lastRedrawTime = 0L
private val REDRAW_THRESHOLD_MS = 16L // Alvo: 60 FPS
fun onAnimationUpdate() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastRedrawTime >= REDRAW_THRESHOLD_MS) {
invalidate()
lastRedrawTime = currentTime
}
}
Ao limitar os redesenhos a ~60 vezes por segundo, liberamos recursos valiosos para outras tarefas da Main Thread sem sacrificar a fluidez visual percebida pelo usuário.
Resultados
Após aplicar o throttling, executamos o Macrobenchmark novamente. Os resultados foram impressionantes:
- Redução de Frame Jank: ~40%
- Frame Timing (P99): Reduzido de 28ms para 16.5ms.
- Consumo de Bateria: Pequena redução na carga da CPU durante os carregamentos.
Conclusão
O jank em animações Android muitas vezes não vem de uma lógica complexa, mas de uma frequência de atualização excessiva. Usando o Perfetto para diagnóstico e o Macrobenchmark para validação, você pode aplicar otimizações direcionadas, como o throttling de redesenhos, para tornar seu aplicativo suave como seda.
Já tentou perfilar suas animações com o Perfetto? Compartilhe sua experiência nos comentários!