Android Animasyon Kasılmalarını (Jank) Dizginleme: Perfetto ve Macrobenchmark ile Yeniden Çizim Sınırlama (Throttling)
yazan: soft24hours team

Akıcı animasyonlar, premium bir Android uygulamasının imzasıdır. Ancak modern donanımlara rağmen geliştiriciler genellikle "jank" — uygulama kareleri (frames) atladığında meydana gelen sinir bozucu takılmalar — ile mücadele ederler.
Bu makalede, gerçek bir performans optimizasyonu vakasını analiz edeceğiz. Verimsiz bir animasyonu Perfetto kullanarak nasıl tanımlayacağımızı, etkisini Jetpack Macrobenchmark ile nasıl ölçeceğimizi ve "Redraw Throttling" (yeniden çizim sınırlama) adı verilen bir teknikle bu sorunu nasıl çözeceğimizi göreceğiz.
Sorun: Gereksiz Yeniden Çizimler
Bir "shimmer" (yer tutucu üzerinde kayan ışık efekti) yükleme animasyonunu düşünün. Eğer bu animasyon her bir milisaniyede bir yeniden çizim talep ediyorsa, değerli CPU ve GPU döngülerini boşa harcıyor demektir. Karmaşık senaryolarda bu, ana UI iş parçacığının kareleri atlamasına ve dolayısıyla kötü bir kullanıcı deneyimine yol açabilir.
1. Aşama: Perfetto ile Teşhis
Perfetto, Google'ın sistem genelindeki profil oluşturma aracıdır. İşletim sisteminin ve uygulamamızın her an ne yaptığını tam olarak görmemizi sağlar.
Sorunlu animasyon sırasında bir iz (trace) kaydettiğimizde, şüpheli derecede yüksek sayıda Choreographer#doFrame olayı ve düzen (layout) hesaplaması fark ettik. Uygulama, akıcı bir görsel efekt için gerekenden çok daha sık yeniden çizim yapmaya çalışıyordu.
İz Verilerinin Analizi
Perfetto'nun SQL tabanlı sorgu dilini kullanarak israfı nicelleştirebiliriz:
SELECT
ts,
dur,
name
FROM slice
WHERE name LIKE '%doFrame%'
AND dur > 16000000 -- 16ms'yi (60fps) aşan kareler
Bu sorgu, shimmer animasyonu sırasında kare sürelerinin kararsız hale geldiğini ve jank'a yol açan iş yükü artışlarına neden olduğunu ortaya koydu.
2. Aşama: Macrobenchmark ile Ölçüm
Optimize etmeden önce bir "temel çizgi" (baseline) belirlememiz gerekir. Jetpack Macrobenchmark kütüphanesi, gerçek kullanıcı senaryolarını simüle ettiği ve FrameOverrunMs gibi kesin metrikler sağladığı için bunun için mükemmeldir.
Testi şu şekilde kurduk:
@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()
// Animasyonun birkaç saniye çalışmasına izin verin
device.waitForIdle()
}
}
3. Aşama: Çözüm — Yeniden Çizim Sınırlama (Throttling)
Düzeltme kavramsal olarak basit ama güçlüdür: Minimum bir süre geçmedikçe yeniden çizim yapma. Animasyon durumundaki her güncellenmeye tepki vermek yerine, invalidate() çağrısını sınırlıyoruz (throttle).
Özel Bir Görünümde (Custom View) Uygulama
private var lastRedrawTime = 0L
private val REDRAW_THRESHOLD_MS = 16L // Hedef: 60 FPS
fun onAnimationUpdate() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastRedrawTime >= REDRAW_THRESHOLD_MS) {
invalidate()
lastRedrawTime = currentTime
}
}
Yeniden çizimleri saniyede yaklaşık 60 kez ile sınırlayarak, kullanıcı tarafından algılanan görsel akıcılıktan ödün vermeden ana iş parçacığındaki (Main Thread) diğer görevler için değerli kaynakları serbest bırakıyoruz.
Sonuçlar
Throttling uyguladıktan sonra Macrobenchmark'ı tekrar çalıştırdık. Sonuçlar etkileyiciydi:
- Animasyon Kasılmalarında (Jank) Azalma: ~%40
- Kare Süresi (P99): 28ms'den 16.5ms'ye düştü.
- Pil Tüketimi: Yükleme işlemleri sırasında CPU yükünde hafif bir azalma.
Sonuç
Android animasyonlarındaki takılmalar genellikle karmaşık bir mantıktan değil, aşırı yüksek güncelleme frekansından kaynaklanır. Teşhis için Perfetto'yu ve doğrulama için Macrobenchmark'ı kullanarak, uygulamanızı ipek gibi pürüzsüz hale getirmek için yeniden çizim sınırlama gibi hedeflenmiş optimizasyonlar uygulayabilirsiniz.
Animasyonlarınızı Perfetto ile profillemeyi hiç denediniz mi? Deneyimlerinizi yorumlarda paylaşın!