Tämja Jank i Android-animeringar: Begränsning av Omskisser med Perfetto och Macrobenchmark
av soft24hours team

Smidiga animeringar är kännetecknet för en förstklassig Android-app. Men även med modern maskinvara kämpar utvecklare ofta mot "jank" — de irriterande hacken som uppstår när appen hoppar över ramar (frames).
I den här artikeln ska vi analysera ett verkligt fall av prestandaoptimering. Vi ska se hur du identifierar en ineffektiv animering med hjälp av Perfetto, mäter effekten med Jetpack Macrobenchmark, och löser det genom att använda en teknik som kallas "Redraw Throttling" (begränsning av omskisser).
Problemet: Överflödiga Omskisser
Tänk dig en "shimmer"-laddningsanimering (den ljuseffekten som rör sig över en platshållare). Om den här animeringen begär en omskisser varje millisekund, slösar den bort värdefulla CPU- och GPU-cykler. I komplexa scenarier kan detta leda till att UI-huvudtråden hoppar över ramar, vilket resulterar i en dålig användarupplevelse.
Fas 1: Diagnos med Perfetto
Perfetto är Googles systemövergripande profileringsverktyg. Det låter oss se exakt vad operativsystemet och vår app gör vid varje givet tillfälle.
Genom att spela in ett spår (trace) under den problematiska animeringen märkte vi ett misstänkt högt antal Choreographer#doFrame-händelser och layoutberäkningar. Appen försökte rita om mycket oftare än vad som krävdes för en smidig visuell effekt.
Analys av Tracedata
Genom att använda Perfettos SQL-baserade frågespråk kan vi kvantifiera slöseriet:
SELECT
ts,
dur,
name
FROM slice
WHERE name LIKE '%doFrame%'
AND dur > 16000000 -- Ramar som överstiger 16ms (60fps)
Den här frågan avslöjade att ramtiden blev instabil under shimmer-animeringen, vilket orsakade arbetstoppar som ledde till jank.
Fas 2: Mätning med Macrobenchmark
Innan vi optimerar måste vi fastställa en utgångspunkt (baseline). Biblioteket Jetpack Macrobenchmark är perfekt för detta, eftersom det simulerar verkliga användarscenarier och ger exakta mätvärden som FrameOverrunMs.
Så här satte vi upp testet:
@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()
// Låt animeringen köra i några sekunder
device.waitForIdle()
}
}
Fas 3: Lösningen — Throttling av Omskisser
Lösningen är konceptuellt enkel men kraftfull: rita inte om om inte ett minsta intervall har passerat. Istället för att reagera på varje uppdatering av animeringsstatusen begränsar (throttle) vi anropet till invalidate().
Implementering i en Custom View
private var lastRedrawTime = 0L
private val REDRAW_THRESHOLD_MS = 16L // Mål: 60 FPS
fun onAnimationUpdate() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastRedrawTime >= REDRAW_THRESHOLD_MS) {
invalidate()
lastRedrawTime = currentTime
}
}
Genom att begränsa omskisser till ~60 gånger per sekund frigör vi värdefulla resurser för andra uppgifter i Main Thread utan att offra den visuella smidigheten som användaren uppfattar.
Resultat
Efter att ha tillämpat throttling körde vi Macrobenchmark igen. Resultaten var imponerande:
- Minskning av Frame Jank: ~40 %
- Frame Timing (P99): Minskad från 28ms till 16,5ms.
- Batteriförbrukning: En svag minskning av CPU-belastningen under laddningsprocesser.
Slutsats
Jank i Android-animeringar beror ofta inte på komplex logik, utan på en alltför hög uppdateringsfrekvens. Genom att använda Perfetto för diagnos och Macrobenchmark för validering kan du tillämpa riktade optimeringar som omskisseringsbegränsning för att göra din app silkeslen.
Har du försöpt att profilera dina animeringar med Perfetto? Dela din erfarenhet i kommentarerna!