Jank — термин, обозначаващ забележими заеквания или „запъвания“ в анимацията на интерфейса, причинени от пропускане на отделни кадри. В мобилните приложения Jank възниква, когато времето за рендериране на кадър надвишава бюджета, отделен от честотата на опресняване на дисплея. Според Android Developers, 2025, Jank е основната причина за субективното усещане за „бавене“ — приложението може да е функционално перфектно, но потребителят го възприема като бавно поради нестабилен FPS.
Основни точки
Jank — термин от областта на компютърната графика, обозначаващ визуален дефект, при който анимацията се движи рязко вместо гладко плъзгане. В мобилната разработка Jank се измерва като брой пропуснати кадри (skipped frames) за единица време. Ако системата не успее да подготви кадър до момента на VSync, дисплеят повтаря предишния кадър — възниква пауза от 16.6 ms при 60 Hz. Един пропуснат кадър може да е незабележим, но серия от 3–5 последователни пропуснати кадри създава усещане за „бавене“ с продължителност 50–80 ms, което потребителят ясно регистрира.
Jank е особено критичен за анимации, които трябва да работят с постоянна скорост: скрол на списък, анимация на отваряне на меню, паралакс ефекти, преходи между екрани. Според UX проучване на Google (2024), приложение с Jank показател над 3% от скрол сесиите получава 22% повече еднозвездни отзиви от приложение с показател под 0.5%. Инструментът Android Vitals автоматично проследява Jank и го класифицира по severity: moderate, severe и critical.
Причините за Jank се разделят на няколко категории. Първа — Layout Jank: причинява се от чести requestLayout() поради промяна на размерите на View, анимации LayoutTransition или динамично зареждане на съдържание. Всяко извикване на requestLayout задейства Measure + Layout за цялото поддърво на View, което може да отнеме 5–30 ms. Втора — Draw Jank: свързан с overdraw и използване на тежки drawable. Трета — Thread Jank: блокиране на main thread поради синхронни операции — зареждане на файлове, работа с база данни на главната нишка, декодиране на Bitmap.
Четвърта категория — GC Jank: garbage collection в ART/Dalvik или Swift ARC. Когато в heap-а се натрупат много обекти, GC стартира Stop-The-World пауза с продължителност 5–15 ms. На Android GC паузите най-често възникват при чести алокации в цикли: създаване на обекти в onDraw(), алокации в адаптери, неизползвани ламбда изрази. Пета — IPC Jank: междупроцесна комуникация (ContentProvider, Binder) на главната нишка. Шеста — Rendering Jank: бавно рендериране на GPU поради неоптимални шейдъри или текстури с голям размер.
| Тип Jank | Причина | Типична продължителност | Инструмент за търсене |
|---|---|---|---|
| Layout | requestLayout, relayout | 5–30 ms | Perfetto, Systrace |
| Draw | Overdraw, тежък drawable | 3–20 ms | GPU Profiling |
| Thread | Блокиране на main thread | 10–200 ms | Android Studio Profiler |
| GC | Garbage Collection | 5–15 ms | Memory Profiler |
| Rendering | Натоварване на GPU | 10–50 ms | GPU Tracer, Xcode GPU |
В Android диагностиката на Jank започва със системния трасинг Perfetto. Perfetto записва активността на всички нишки, CPU, GPU и планировчика. Ясен индикатор за Jank са редовете Choreographer.doFrame и Choreographer.doCallbacks: ако интервалът между две последователни извиквания на doFrame надвишава 16.6 ms, кадърът е пропуснат. Perfetto показва точната причина — кой system call, lock или GC е причинил забавянето. В Android Studio Profiler подобна функционалност е достъпна чрез CPU Profiler.
За автоматично откриване на Jank в продукция се използва FrameMetricsAggregator — API, който събира статистика за всеки кадър и я агрегира за сесия. В Android 12+ се появи PerformanceHintManager — API за подсказки към системата за целевата честота на кадрите. Ако приложението посочи, че работи в сценарий от 120 FPS, системата може да увеличи честотата на CPU/GPU за предотвратяване на Jank. За просто логване на всички пропуснати кадри е достатъчно да се абонирате за Choreographer.FrameCallback.
Код на Kotlin се абонира за Choreographer.FrameCallback и логва всеки пропуснат кадър с посочване на продължителността на забавянето. Callback се извиква при всеки 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",
"Пропуснати $skippedFrames кадъра")
}
}
previousFrameTime = currentTime
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(callback)
}
}
В iOS диагностиката на Jank се извършва чрез Instruments с шаблон Core Animation. Instruments показва FPS в реално време, брой offscreen-рендери и hit-тестове. Основни индикатори за Jank в iOS: червени колони в времевата скала на Core Animation (превишаване на бюджета на кадъра), висок показател Renderer (означава offscreen rendering) и нисък FPS. За мониторинг на продукция MetricKit събира отчети с метрика MXAnimatoryMetric, която включва среден FPS, P50 и P95 време на кадър.
Вградената диагностика на Jank в iOS включва CADisplayLink с проверка на timestamp и targetTimestamp. Ако текущият timestamp значително изостава от targetTimestamp, значи един или повече кадри са пропуснати. Apple също препоръчва използването на os_signpost за персонализирано профилиране: поставяне на signpost-interval в началото и края на рендерирането на кадър и проверка в Instruments кои интервали надвишават 16.6 ms. В SwiftUI за диагностика на Jank се използва UIView.invalidateIntrinsicContentSize — честото извикване на този метод показва нестабилен Layout.
Код на Swift определя пропуснати кадри чрез CADisplayLink. Ако разликата между timestamp и targetTimestamp надвишава 16.6 ms — Jank се записва.
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
}
}
}
За профилиране на Jank се използват както вградените инструменти на операционната система, така и SDK на трети страни. В Android ключовият инструмент е Perfetto (замени Systrace). Perfetto позволява запис на трасинги до 30 секунди и анализ чрез уеб интерфейса ui.perfetto.dev. Той показва точна времева линия с работата на Choreographer, нишките за рендериране (RenderThread) и GPU. За подробен анализ на проблеми с GPU се използва AGI (Android GPU Inspector), който показва не само времето на кадъра, но и натоварването на конкретни GPU блокове — шейдъри, растеризатор, текстурен блок.
В iOS аналогът е Instruments с шаблоните Core Animation, Metal System Trace и GPU Driver. Core Animation показва FPS и време на кадър, Metal System Trace — работа на GPU с детайлност до всеки draw call. За профилиране на реални устройства под натоварване се използват Firebase Performance (събира метрика Screen Rendering) и Sentry (улавя stack trace при Jank). Новото API Android 15 Performance Hint позволява на разработчика да посочи на системата кои кадри са важни и да получава предупреждения от системата при приближаване до Jank.
Код на Kotlin използва FrameMetricsAggregator за събиране на статистика за кадри за сесия. След спиране на агрегатора се показва броят на пропуснатите кадри.
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 съотношение: ${jankFrames * 100 / totalFrames}%")
}
}
Отстраняването на Jank изисква комбинация от методи в зависимост от типа му. За Layout Jank: замяна на дълбоки йерархии с ConstraintLayout/Compose/SwiftUI, използване на merge-тагове, избягване на requestLayout в анимации. За Draw Jank: използване на Debug GPU Overdraw за намиране на 4x+ overdraw, замяна на тежки drawable с векторни (VectorDrawable/PDF), внимателно използване на hardware layers — ускоряват рендерирането, но консумират повече GPU памет. За Thread Jank: преместване на всички I/O операции, работа с база данни и декодиране на Bitmap във фонови нишки, използване на Kotlin Coroutines с правилен Dispatcher или RxJava с Schedulers.io().
За GC Jank: минимизиране на алокациите в onDraw() и getView(), използване на обектни пулове (ObjectPool), замяна на for-each с индексиран for, внимателно използване на immutable data class в Kotlin с copy() — copy създава нов обект. За IPC Jank: мързелива инициализация на ContentProvider чрез App Startup, преместване на Binder извиквания във фонова нишка. За Rendering Jank: намаляване на размера на текстурите до максималната резолюция на екрана, използване на ASTC или ETC2 компресия, избягване на ненужна shader compilation (компилиране на шейдъри предварително). Комплексно решение — редовно стартиране на Perfetto/Instruments профилиране в CI и проследяване на Jank регресии.
Kotlin код демонстрира асинхронно зареждане на данни на екрана след reportFullyDrawn, така че тежката работа да не блокира първия кадър. Callback се извиква след като потребителят види интерфейса.
class JankSafeLoader {
suspend fun loadAfterFirstFrame(
activity: Activity
) {
// гарантираме, че първият кадър вече е рендериран
if (Build.VERSION.SDK_INT >= 29) {
activity.reportFullyDrawn()
}
// тежко зареждане — след първия кадър
withContext(Dispatchers.IO) {
val data = fetchHeavyData()
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
}
Често задавани въпроси
Jank — пропуснати кадри на рендериране, които се проявяват като забележими заеквания или рязкости на анимацията. Възниква, когато времето за подготовка на кадър надвиши времевия бюджет (16.6 ms за 60 FPS).
Layout Jank (чести requestLayout), Draw Jank (overdraw), Thread Jank (блокиране на main thread), GC Jank (garbage collection), IPC Jank (Binder извиквания) и Rendering Jank (тежки шейдъри).
Използвайте Perfetto за системен трасинг, GPU Profiling за анализ на фазите на кадъра и FrameMetricsAggregator за мониторинг на продукция. В Android Studio — CPU Profiler с Deep Java Trace.
Чрез Instruments с шаблон Core Animation или Metal System Trace. За продукция — MetricKit с MXAnimatoryMetric. Програмно — CADisplayLink с проверка на разликата между timestamp и targetTimestamp.
Според Google, Jank показател над 3% от скрол сесиите (3 от 100 скрола съдържат заекване) води до увеличение на негативните отзиви с 22%. Целеви показател — под 0.5% от скрол сесиите.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също