Jank — este un termen care desemnează întârzierile vizibile sau „bâlbâiala” animației interfeței, cauzate de omiterea cadrelor individuale. În aplicațiile mobile, Jank apare atunci când timpul de randare a unui cadru depășește bugetul alocat de rata de reîmprospătare a afișajului. Conform Android Developers, 2025, Jank este cauza principală a senzației subiective de „încetinire” — aplicația poate fi funcțional perfectă, dar utilizatorul o percepe ca fiind lentă din cauza unui FPS instabil.
Principalele puncte
Jank — este un termen din domeniul graficii computerizate care desemnează un defect vizual în care animația se mișcă sacadat în loc de o alunecare lină. În dezvoltarea mobilă, Jank se măsoară ca numărul de cadre omise (skipped frames) pe unitatea de timp. Dacă sistemul nu reușește să pregătească un cadru până la momentul VSync, afișajul repetă cadrul anterior — apare o pauză de 16.6 ms la 60 Hz. Un cadru omis poate fi imperceptibil, dar o serie de 3–5 cadre omise consecutiv creează o senzație de „încetinire” de 50–80 ms pe care utilizatorul o resimte clar.
Jank este deosebit de critic pentru animațiile care trebuie să ruleze cu viteză constantă: derularea listelor, animația de deschidere a meniului, efectele de paralaxă, tranzițiile între ecrane. Conform unui studiu UX Google (2024), o aplicație cu un indicator Jank de peste 3% din sesiunile de derulare primește cu 22% mai multe recenzii de o stea decât o aplicație cu un indicator sub 0.5%. Instrumentul Android Vitals urmărește automat Jank și îl clasifică după severitate: moderate, severe și critical.
Cauzele Jank se împart în mai multe categorii. Prima — Layout Jank: cauzat de requestLayout() frecvente din cauza modificării dimensiunilor View, animațiilor LayoutTransition sau încărcării dinamice a conținutului. Fiecare apel requestLayout declanșează Measure + Layout pentru întregul subarbore View, ceea ce poate dura 5–30 ms. A doua — Draw Jank: asociat cu suprasolicitarea (overdraw) și utilizarea drawable-urilor grele. A treia — Thread Jank: blocarea main thread din cauza operațiunilor sincrone — încărcarea fișierelor, lucrul cu baza de date pe firul principal, decodificarea Bitmap.
A patra categorie — GC Jank: garbage collection în ART/Dalvik sau Swift ARC. Când în heap se acumulează multe obiecte, GC declanșează o pauză Stop-The-World de 5–15 ms. Pe Android, pauzele GC apar cel mai frecvent la alocări frecvente în bucle: crearea de obiecte în onDraw(), alocări în adaptoare, expresii lambda neutilizate. A cincea — IPC Jank: comunicarea interproces (ContentProvider, Binder) pe firul principal. A șasea — Rendering Jank: randarea lentă a GPU din cauza shader-elor neoptimale sau a texturilor de dimensiuni mari.
| Tip Jank | Cauză | Durată tipică | Instrument de căutare |
|---|---|---|---|
| Layout | requestLayout, relayout | 5–30 ms | Perfetto, Systrace |
| Draw | Overdraw, drawable grele | 3–20 ms | GPU Profiling |
| Thread | Blocarea main thread | 10–200 ms | Android Studio Profiler |
| GC | Garbage Collection | 5–15 ms | Memory Profiler |
| Rendering | Încărcarea GPU | 10–50 ms | GPU Tracer, Xcode GPU |
În Android, diagnosticarea Jank începe cu trasarea de sistem Perfetto. Perfetto înregistrează activitatea tuturor firelor, CPU, GPU și a planificatorului. Un indicator clar al Jank sunt liniile Choreographer.doFrame și Choreographer.doCallbacks: dacă intervalul dintre două apeluri consecutive doFrame depășește 16.6 ms, cadrul este omis. Perfetto arată cauza exactă — care system call, lock sau GC a cauzat întârzierea. În Android Studio Profiler, o funcționalitate similară este disponibilă prin CPU Profiler.
Pentru detectarea automată a Jank în producție se utilizează FrameMetricsAggregator — API care colectează statistici pentru fiecare cadru și le agregă pe sesiune. În Android 12+ a apărut PerformanceHintManager — API pentru sugestii către sistem privind rata de cadre țintă. Dacă aplicația indică faptul că rulează într-un scenariu de 120 FPS, sistemul poate crește frecvența CPU/GPU pentru a preveni Jank. Pentru logarea simplă a tuturor cadrelor omise, este suficient să vă abonați la Choreographer.FrameCallback.
Codul în Kotlin se abonează la Choreographer.FrameCallback și loghează fiecare cadru omis cu indicarea duratei întârzierii. Callback-ul este apelat la fiecare VSync.
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",
"S-au omis $skippedFrames cadre")
}
}
previousFrameTime = currentTime
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(callback)
}
}
În iOS, diagnosticarea Jank se realizează prin Instruments cu șablonul Core Animation. Instruments arată FPS în timp real, numărul de randări offscreen și hit-test-uri. Principalii indicatori ai Jank în iOS: coloane roșii în scala de timp Core Animation (depășirea bugetului cadrului), indicatorul ridicat Renderer (înseamnă offscreen rendering) și FPS scăzut. Pentru monitorizarea producției, MetricKit colectează rapoarte cu metrica MXAnimatoryMetric, care include FPS mediu, P50 și P95 timpul cadrului.
Diagnosticarea nativă a Jank în iOS include CADisplayLink cu verificarea timestamp și targetTimestamp. Dacă timestamp-ul curent rămâne semnificativ în urma targetTimestamp, înseamnă că unul sau mai multe cadre au fost omise. Apple recomandă, de asemenea, utilizarea os_signpost pentru profilarea personalizată: plasarea signpost-interval la începutul și sfârșitul randării cadrului și verificarea în Instruments care intervale depășesc 16.6 ms. În SwiftUI, pentru diagnosticarea Jank se utilizează UIView.invalidateIntrinsicContentSize — apelarea frecventă a acestei metode indică un Layout instabil.
Codul în Swift determină cadrele omise prin CADisplayLink. Dacă diferența dintre timestamp și targetTimestamp depășește 16.6 ms — Jank este înregistrat.
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
}
}
}
Pentru profilarea Jank se utilizează atât instrumentele integrate ale sistemului de operare, cât și SDK-uri terțe. În Android, instrumentul cheie este Perfetto (a înlocuit Systrace). Perfetto permite înregistrarea de trace-uri de până la 30 de secunde și analizarea lor prin interfața web ui.perfetto.dev. Acesta arată axa temporală exactă cu activitatea Choreographer, firele de randare (RenderThread) și GPU. Pentru analiza detaliată a problemelor GPU se utilizează AGI (Android GPU Inspector), care arată nu doar timpul cadrului, ci și încărcarea blocurilor specifice GPU — shader-e, rasterizator, blocul de texturi.
În iOS, echivalentul este Instruments cu șabloanele Core Animation, Metal System Trace și GPU Driver. Core Animation arată FPS și timpul cadrului, Metal System Trace — activitatea GPU cu detaliere până la fiecare draw call. Pentru profilarea pe dispozitive reale sub sarcină se utilizează Firebase Performance (colectează metrica Screen Rendering) și Sentry (capturează stack trace la Jank). Noul API Android 15 Performance Hint permite dezvoltatorului să indice sistemului care cadre sunt importante și să primească avertismente de la sistem la apropierea de Jank.
Codul în Kotlin utilizează FrameMetricsAggregator pentru colectarea statisticilor cadrelor pe sesiune. După oprirea agregatorului, se afișează numărul de cadre omise.
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",
"Raport Jank: ${jankFrames * 100 / totalFrames}%")
}
}
Eliminarea Jank necesită o combinație de metode în funcție de tipul acestuia. Pentru Layout Jank: înlocuirea ierarhiilor adânci cu ConstraintLayout/Compose/SwiftUI, utilizarea merge-tag-urilor, evitarea requestLayout în animații. Pentru Draw Jank: utilizarea Debug GPU Overdraw pentru găsirea overdraw-ului 4x+, înlocuirea drawable-urilor grele cu vectoriale (VectorDrawable/PDF), utilizarea cu prudență a hardware layers — accelerează randarea, dar consumă mai multă memorie GPU. Pentru Thread Jank: mutarea tuturor operațiunilor I/O, lucrului cu baza de date și decodificării Bitmap pe fire de fundal, utilizarea Kotlin Coroutines cu Dispatcher corespunzător sau RxJava cu Schedulers.io().
Pentru GC Jank: minimizarea alocărilor în onDraw() și getView(), utilizarea pool-urilor de obiecte (ObjectPool), înlocuirea for-each cu for indexat, utilizarea cu grijă a immutable data class în Kotlin cu copy() — copy creează un obiect nou. Pentru IPC Jank: inițializarea lentă a ContentProvider prin App Startup, mutarea apelurilor Binder pe un fir de fundal. Pentru Rendering Jank: reducerea dimensiunii texturilor la rezoluția maximă a ecranului, utilizarea compresiei ASTC sau ETC2, evitarea shader compilation inutile (compilarea shader-elor în avans). Soluția complexă — rularea regulată a profilării Perfetto/Instruments în CI și urmărirea regresiilor Jank.
Codul Kotlin demonstrează încărcarea asincronă a datelor pe ecran după reportFullyDrawn, pentru ca munca grea să nu blocheze primul cadru. Callback-ul este apelat după ce utilizatorul vede interfața.
class JankSafeLoader {
suspend fun loadAfterFirstFrame(
activity: Activity
) {
// garantăm că primul cadru este deja randat
if (Build.VERSION.SDK_INT >= 29) {
activity.reportFullyDrawn()
}
// încărcare grea — după primul cadru
withContext(Dispatchers.IO) {
val data = fetchHeavyData()
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
}
Întrebări frecvente
Jank — sunt cadre de randare omise care se manifestă ca întârzieri vizibile sau sacadări ale animației. Apare atunci când timpul de pregătire a cadrului depășește bugetul de timp (16.6 ms pentru 60 FPS).
Layout Jank (requestLayout frecvente), Draw Jank (overdraw), Thread Jank (blocarea main thread), GC Jank (garbage collection), IPC Jank (apeluri Binder) și Rendering Jank (shader-e grele).
Utilizați Perfetto pentru trace de sistem, GPU Profiling pentru analiza fazelor cadrului și FrameMetricsAggregator pentru monitorizarea producției. În Android Studio — CPU Profiler cu Deep Java Trace.
Prin Instruments cu șablonul Core Animation sau Metal System Trace. Pentru producție — MetricKit cu MXAnimatoryMetric. Programatic — CADisplayLink cu verificarea diferenței dintre timestamp și targetTimestamp.
Conform Google, un indicator Jank de peste 3% din sesiunile de derulare (3 din 100 de derulări conțin o întârziere) duce la o creștere a recenziilor negative cu 22%. Indicatorul țintă — sub 0.5% din sesiunile de derulare.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și