Meny
Tøyle Android-animasjon Jank: Throttling av Omopptegnelser med Perfetto og Macrobenchmark

Tøyle Android-animasjon Jank: Throttling av Omopptegnelser med Perfetto og Macrobenchmark

av soft24hours team

Optimalisering av Shimmer i Android med Perfetto

Smidige animasjoner er kjennetegnet på en førsteklasses Android-app. Men selv med moderne maskinvare kjemper utviklere ofte mot "jank" — de irriterende hakkene som oppstår når appen hopper over rammer (frames).

I denne artikkelen skal vi analysere et reelt tilfelle av ytelsesoptimalisering. Vi skal se hvordan du identifiserer en ineffektiv animasjon ved hjelp av Perfetto, måler effekten med Jetpack Macrobenchmark, og løser det ved å bruke en teknikk kalt "Redraw Throttling" (begrensning av omopptegnelser).

Problemet: Overflødige Omopptegnelser

Tenk deg en "shimmer"-lasteanimasjon (den lyseffekten som beveger seg over en plassholder). Hvis denne animasjonen ber om en omopptegning hvert eneste millisekund, sløser den bort verdifulle CPU- og GPU-sykluser. I komplekse scenarier kan dette føre til at UI-hovedtråden hopper over rammer, noe som resulterer i en dårlig brukeropplevelse.

Fase 1: Diagnose med Perfetto

Perfetto er Googles systemomspennende profileringsverktøy. Det lar oss se nøyaktig hva operativsystemet og appen vår gjør til enhver tid.

Ved å ta opp et spor (trace) under den problematiske animasjonen, la vi merke til et mistenkelig høyt antall Choreographer#doFrame-hendelser og layoutberegninger. Appen forsøkte å tegne om mye oftere enn nødvendig for en smidig visuell effekt.

Analyse av Tracedata

Ved å bruke Perfettos SQL-baserte spørrespråk, kan vi kvantifisere sløsingen:

SELECT
    ts,
    dur,
    name
FROM slice
WHERE name LIKE '%doFrame%'
AND dur > 16000000 -- Rammer som overstiger 16ms (60fps)

Denne spørringen avslørte at rammetiden ble ustabil under shimmer-animasjonen, noe som forårsaket arbeidsstopper som førte til jank.

Fase 2: Måling med Macrobenchmark

Før vi optimaliserer, må vi etablere et utgangspunkt (baseline). Biblioteket Jetpack Macrobenchmark er perfekt for dette, da det simulerer reelle brukerscenarier og gir nøyaktige beregninger som FrameOverrunMs.

Slik satte vi opp testen:

@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()
        // La animasjonen kjøre i noen sekunder
        device.waitForIdle()
    }
}

Fase 3: Løsningen — Throttling av Omopptegnelser

Løsningen er konseptuelt enkel, men kraftfull: ikke tegn om med mindre et minimumsintervall har passert. I stedet for å reagere på hver eneste oppdatering av animasjonsstatusen, begrenser (throttle) vi kallet til 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
    }
}

Ved å begrense omopptegnelser til ~60 ganger i sekundet, frigjør vi verdifulle ressurser for andre oppgaver i Main Thread uten å ofre den visuelle smidigheten som brukeren oppfatter.

Resultater

Etter å ha brukt throttling, kjørte vi Macrobenchmark på nytt. Resultatene var imponerende:

  • Reduksjon i Frame Jank: ~40 %
  • Frame Timing (P99): Redusert fra 28ms til 16,5ms.
  • Batteriforbruk: En svak reduksjon i CPU-belastning under lasteprosesser.

Konklusjon

Jank i Android-animasjoner skyldes ofte ikke kompleks logikk, men en altfor høy oppdateringsfrekvens. Ved å bruke Perfetto for diagnose og Macrobenchmark for validering, kan du bruke målrettede optimaliseringer som redraw throttling for å gjøre appen din silkemyk.

Har du prøvd å profilere animasjonene dine med Perfetto? Del din erfaring i kommentarene!