Jank — olyan kifejezés, amely a felületi animáció észrevehető akadozását vagy „dadogását” jelöli, amelyet egyes képkockák kihagyása okoz. Mobilalkalmazásokban a Jank akkor lép fel, amikor egy képkocka renderelési ideje meghaladja a kijelző frissítési gyakorisága által elkülönített költségvetést. A Android Developers, 2025 szerint a Jank a szubjektív „lassulás” érzésének fő oka — az alkalmazás funkcionálisan tökéletes lehet, de a felhasználó lassúnak érzékeli az instabil FPS miatt.
Főbb pontok
Jank — a számítógépes grafika területéről származó kifejezés, amely olyan vizuális hibát jelöl, ahol az animáció a sima csúszás helyett rángatózva mozog. Mobil fejlesztésben a Jank az időegységre jutó kihagyott képkockák számával (skipped frames) mérhető. Ha a rendszer nem tud egy képkockát előkészíteni a VSync pillanatára, a kijelző megismétli az előző képkockát — 16.6 ms-os szünet keletkezik 60 Hz-en. Egy kihagyott képkocka észrevétlen lehet, de 3–5 egymást követő kihagyott képkocka sorozata 50–80 ms-os „lassulás” érzést kelt, amit a felhasználó egyértelműen érzékel.
A Jank különösen kritikus azoknál az animációknál, amelyeknek állandó sebességgel kell működniük: lista görgetése, menü nyitás animációja, parallaxis effektek, képernyők közötti átmenetek. A Google UX-kutatása (2024) szerint az a Jank-mutatóval rendelkező alkalmazás, amely a görgetési munkamenetek több mint 3%-ánál produkál Jank-ot, 22%-kal több egycsillagos értékelést kap, mint az a 0.5% alatti mutatójú alkalmazás. Az Android Vitals eszköz automatikusan nyomon követi a Jank-ot és severity szerint osztályozza: moderate, severe és critical.
A Jank okai több kategóriába sorolhatók. Az első — Layout Jank: gyakori requestLayout() hívások okozzák a View méreteinek változása, LayoutTransition animációk vagy dinamikus tartalom betöltése miatt. Minden requestLayout hívás elindítja a Measure + Layout-ot a teljes View részfára vonatkozóan, ami 5–30 ms-ig tarthat. A második — Draw Jank: overdraw-hoz és nehéz drawable-ok használatához kapcsolódik. A harmadik — Thread Jank: a main thread blokkolása szinkron műveletek miatt — fájlok betöltése, adatbázis-munka a fő szálon, Bitmap dekódolás.
A negyedik kategória — GC Jank: garbage collection az ART/Dalvik vagy Swift ARC rendszerben. Amikor sok objektum halmozódik fel a heap-en, a GC 5–15 ms-os Stop-The-World szünetet indít. Androidon a GC-szünetek leggyakrabban gyakori allokációknál fordulnak elő ciklusokban: objektumok létrehozása onDraw()-ban, allokációk adapterekben, nem használt lambda kifejezések. Az ötödik — IPC Jank: folyamatok közötti kommunikáció (ContentProvider, Binder) a fő szálon. A hatodik — Rendering Jank: lassú GPU-renderelés nem optimális shaderek vagy nagy méretű textúrák miatt.
| Jank típusa | Ok | Jellemző időtartam | Keresőeszköz |
|---|---|---|---|
| Layout | requestLayout, relayout | 5–30 ms | Perfetto, Systrace |
| Draw | Overdraw, nehéz drawable | 3–20 ms | GPU Profiling |
| Thread | Main thread blokkolása | 10–200 ms | Android Studio Profiler |
| GC | Garbage Collection | 5–15 ms | Memory Profiler |
| Rendering | GPU terhelés | 10–50 ms | GPU Tracer, Xcode GPU |
Androidban a Jank diagnosztizálása a Perfetto rendszernyomkövetéssel kezdődik. A Perfetto rögzíti az összes szál, CPU, GPU és ütemező tevékenységét. A Jank egyértelmű mutatója a Choreographer.doFrame és Choreographer.doCallbacks sorok: ha két egymást követő doFrame hívás közötti intervallum meghaladja a 16.6 ms-ot, a képkocka kimaradt. A Perfetto megmutatja a pontos okot — melyik system call, lock vagy GC okozta a késleltetést. Az Android Studio Profilerben hasonló funkció érhető el a CPU Profileren keresztül.
A Jank automatikus észleléséhez éles környezetben a FrameMetricsAggregator használatos — egy API, amely képkockánként gyűjt statisztikákat és munkamenetenként összesíti. Android 12+-ban megjelent a PerformanceHintManager — egy API a rendszer felé történő célzott képkockasebesség-jelzésekhez. Ha az alkalmazás jelzi, hogy 120 FPS-es forgatókönyvben dolgozik, a rendszer növelheti a CPU/GPU frekvenciát a Jank megelőzése érdekében. Az összes kihagyott képkocka egyszerű naplózásához elegendő feliratkozni a Choreographer.FrameCallback-re.
A Kotlin kód feliratkozik a Choreographer.FrameCallback-re és naplóz minden kihagyott képkockát a késleltetés időtartamának megadásával. A callback minden VSync-nél meghívásra kerül.
class JankDetector {
private val frameBudget = 16_666_666L
private var previousFrameTime = 0L
private val callback =
Choreographer.FrameCallback { currentTime ->
if (previousFrameTime != 0L) {
val frameDuration =
currentTime - previousFrameTime
val skippedFrames =
(frameDuration / frameBudget) - 1
if (skippedFrames > 0) {
Log.w("Jank",
"$skippedFrames képkocka kihagyva")
}
}
previousFrameTime = currentTime
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(callback)
}
}
iOS-ben a Jank diagnosztizálása az Instruments segítségével történik a Core Animation sablonnal. Az Instruments valós időben mutatja az FPS-t, az offscreen-renderek számát és a hit-testeket. A Jank fő mutatói iOS-ben: piros oszlopok a Core Animation időskáláján (képkocka költségvetésének túllépése), magas Renderer mutató (offscreen renderinget jelent) és alacsony FPS. Az éles környezet monitorozásához a MetricKit jelentéseket gyűjt az MXAnimatoryMetric metrikával, amely magában foglalja az átlagos FPS-t, a P50 és P95 képkockaidőt.
A Jank natív diagnosztizálása iOS-ben magában foglalja a CADisplayLink-et a timestamp és targetTimestamp ellenőrzésével. Ha az aktuális timestamp jelentősen elmarad a targetTimestamp-től, egy vagy több képkocka kimaradt. Az Apple az egyéni profilozáshoz az os_signpost használatát is ajánlja: helyezzen el signpost-intervallumot a képkocka renderelés elején és végén, és ellenőrizze az Instruments-ben, mely intervallumok haladják meg a 16.6 ms-ot. A SwiftUI-ben a Jank diagnosztizálásához a UIView.invalidateIntrinsicContentSize használatos — ennek a metódusnak a gyakori meghívása instabil Layout-ra utal.
A Swift kód a CADisplayLink segítségével határozza meg a kihagyott képkockákat. Ha a timestamp és a targetTimestamp közötti különbség meghaladja a 16.6 ms-ot — a Jank rögzítésre kerül.
class JankMonitor {
private var displayLink: CADisplayLink?
private var totalJank = 0
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(detectJank)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func detectJank() {
guard let link = displayLink else { return }
let delay = link.targetTimestamp
- link.timestamp
if delay > 0.0167 {
totalJank += 1
}
}
}
A Jank profilozásához mind az operációs rendszer beépített eszközeit, mind külső SDK-kat használnak. Androidban a kulcsfontosságú eszköz a Perfetto (felváltotta a Systrace-t). A Perfetto lehetővé teszi akár 30 másodperces nyomkövetések rögzítését és elemzését a ui.perfetto.dev webes felületen keresztül. Pontos idővonalat mutat a Choreographer, a renderelő szálak (RenderThread) és a GPU tevékenységével. A GPU-problémák részletes elemzéséhez az AGI (Android GPU Inspector) használatos, amely nemcsak a képkockaidőt mutatja, hanem a GPU egyes blokkjainak — shaderek, raszterizáló, textúra blokk — terhelését is.
iOS-ben a megfelelő az Instruments a Core Animation, Metal System Trace és GPU Driver sablonokkal. A Core Animation az FPS-t és a képkockaidőt mutatja, a Metal System Trace a GPU munkáját részletezi minden egyes draw call-ig. Valós eszközökön, terhelés alatt történő profilozáshoz a Firebase Performance (Screen Rendering metrikát gyűjt) és a Sentry (stack trace-t rögzít Jank esetén) használatos. Az új Android 15 Performance Hint API lehetővé teszi a fejlesztő számára, hogy jelezze a rendszernek, mely képkockák fontosak, és figyelmeztetéseket kapjon a rendszertől a Jank közeledtekor.
A Kotlin kód a FrameMetricsAggregator-t használja a képkocka-statisztikák munkamenetenkénti gyűjtésére. Az aggregátor leállítása után megjelenik a kihagyott képkockák száma.
class JankAggregator(private val activity: Activity) {
private val aggregator = FrameMetricsAggregator()
fun startCollection() {
aggregator.add(activity.window)
}
fun stopAndReport() {
aggregator.remove()
val result = aggregator.getMetrics()
val totalFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.size ?: 0
val jankFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.count { it > 16_666_666L} ?: 0
Log.d("JankReport",
"Jank arány: ${jankFrames * 100 / totalFrames}%")
}
}
A Jank megszüntetése a típustól függő módszerek kombinációját igényli. Layout Jank esetén: a mély hierarchiák cseréje ConstraintLayout/Compose/SwiftUI-ra, merge-tagek használata, requestLayout kerülése animációkban. Draw Jank esetén: Debug GPU Overdraw használata a 4x+ overdraw megtalálásához, nehéz drawable-ok cseréje vektorosra (VectorDrawable/PDF), a hardware layers óvatos használata — gyorsítják a renderelést, de több GPU memóriát fogyasztanak. Thread Jank esetén: az összes I/O művelet, adatbázis-munka és Bitmap dekódolás áthelyezése háttérszálakra, Kotlin Coroutines használata megfelelő Dispatcher-rel vagy RxJava Schedulers.io()-val.
GC Jank esetén: allokációk minimalizálása onDraw()-ban és getView()-ben, objektumpoolok (ObjectPool) használata, for-each cseréje indexelt for-ra, immutable data class óvatos használata Kotlinban a copy()-val — a copy új objektumot hoz létre. IPC Jank esetén: ContentProvider lusta inicializálása App Startup segítségével, Binder-hívások áthelyezése háttérszálra. Rendering Jank esetén: textúrák méretének csökkentése a képernyő maximális felbontására, ASTC vagy ETC2 tömörítés használata, felesleges shader compilation kerülése (shaderek előzetes fordítása). Komplex megoldás — rendszeres Perfetto/Instruments profilozás futtatása CI-ban és a Jank-regressziók nyomon követése.
A Kotlin kód aszinkron adatbetöltést mutat be a képernyőre a reportFullyDrawn után, hogy a nehéz munka ne blokkolja az első képkockát. A callback akkor hívódik meg, amikor a felhasználó látja a felületet.
class JankSafeLoader {
suspend fun loadAfterFirstFrame(
activity: Activity
) {
// garantáljuk, hogy az első képkocka már renderelve van
if (Build.VERSION.SDK_INT >= 29) {
activity.reportFullyDrawn()
}
// nehéz betöltés — az első képkocka után
withContext(Dispatchers.IO) {
val data = fetchHeavyData()
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
}
Gyakran ismételt kérdések
Jank — kihagyott renderelési képkockák, amelyek észrevehető akadozásként vagy rángatózásként jelennek meg az animációban. Akkor keletkezik, amikor a képkocka előkészítési ideje meghaladja az időkeretet (16.6 ms 60 FPS esetén).
Layout Jank (gyakori requestLayout), Draw Jank (overdraw), Thread Jank (main thread blokkolása), GC Jank (garbage collection), IPC Jank (Binder-hívások) és Rendering Jank (nehéz shaderek).
Használja a Perfetto-t rendszernyomkövetéshez, a GPU Profiling-et a képkockafázisok elemzéséhez és a FrameMetricsAggregator-t az éles környezet monitorozásához. Android Studio-ban — CPU Profiler Deep Java Trace-szel.
A Instruments segítségével a Core Animation vagy Metal System Trace sablonnal. Éles környezethez — MetricKit MXAnimatoryMetric metrikával. Programozottan — CADisplayLink a timestamp és targetTimestamp különbségének ellenőrzésével.
A Google adatai szerint a 3%-nál magasabb Jank-mutató (100 görgetésből 3 akadozást tartalmaz) a negatív értékelések 22%-os növekedéséhez vezet. Célérték — a görgetési munkamenetek kevesebb mint 0.5%-a.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is