Jank — це термін, що позначає помітні запинки або «заїкання» в анімації інтерфейсу, спричинені пропуском окремих кадрів. У мобільних додатках Jank виникає, коли час рендерингу кадру перевищує бюджет, виділений частотою оновлення дисплея. За даними Android Developers, 2025, Jank є основною причиною суб'єктивного відчуття «гальмувань» — додаток може бути функціонально ідеальним, але користувач сприймає його як повільний через нестабільний FPS.
Головне
Jank — це термін з області комп'ютерної графіки, що позначає візуальний дефект, при якому анімація рухається ривками замість плавного ковзання. У мобільній розробці Jank вимірюється як кількість пропущених кадрів (skipped frames) в одиницю часу. Якщо система не встигає підготувати кадр до моменту VSync, дисплей повторює попередній кадр — виникає пауза тривалістю 16.6 мс при 60 Гц. Один пропущений кадр може бути непомітним, але серія з 3–5 пропущених кадрів поспіль створює відчуття «гальма» тривалістю 50–80 мс, яке користувач чітко фіксує.
Jank особливо критичний для анімацій, які повинні працювати з постійною швидкістю: скрол стрічки, анімація відкриття меню, паралакс-ефекти, переходи між екранами. За даними UX-дослідження Google (2024), додаток з Jank-показником більше 3% скрол-сесій отримує на 22% більше однозіркових відгуків, ніж додаток з показником менше 0.5%. Інструмент Android Vitals автоматично відстежує Jank і класифікує його за severity: помірний, серйозний і критичний.
Причини Jank поділяються на кілька категорій. Перша — Layout Jank: викликається частими requestLayout() через зміну розмірів View, анімацій LayoutTransition або динамічного завантаження контенту. Кожен виклик requestLayout запускає Measure + Layout для всього піддерева View, що може займати 5–30 мс. Друга — Draw Jank: пов'язаний з перемальовуванням (overdraw) і використанням важких drawable. Третя — Thread Jank: блокування main thread через синхронні операції — завантаження файлів, робота з БД на головному потоці, декодування Bitmap.
Четверта категорія — GC Jank: збирання сміття (Garbage Collection) в ART/Dalvik або Swift ARC. Коли в купі накопичується багато об'єктів, GC запускає Stop-The-World паузу тривалістю 5–15 мс. На Android GC-паузи найчастіше виникають при частих алокаціях в циклах: створення об'єктів в onDraw(), allocation в адаптерах, невикористовувані lambda-вирази. П'ята — IPC Jank: міжпроцесна взаємодія (ContentProvider, Binder) на головному потоці. Шоста — Rendering Jank: повільний рендеринг GPU через неоптимальні шейдери або текстури великого розміру.
| Тип Jank | Причина | Типова тривалість | Інструмент пошуку |
|---|---|---|---|
| Layout | requestLayout, relayout | 5–30 мс | Perfetto, Systrace |
| Draw | Overdraw, важкі drawable | 3–20 мс | GPU Profiling |
| Thread | Блокування main thread | 10–200 мс | Android Studio Profiler |
| GC | Garbage Collection | 5–15 мс | Memory Profiler |
| Rendering | Завантаження GPU | 10–50 мс | GPU Tracer, Xcode GPU |
В Android діагностика Jank починається з системного трейсу Perfetto. Perfetto записує активність всіх потоків, CPU, GPU і планувальника. Наочний індикатор Jank — рядки Choreographer.doFrame і Choreographer.doCallbacks: якщо між двома послідовними викликами doFrame інтервал перевищує 16.6 мс, кадр пропущено. 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 і логує кожен пропущений кадр із зазначенням тривалості затримки. Колбек викликається на кожен 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",
"Skipped $skippedFrames frames")
}
}
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 мс. В SwiftUI для діагностики Jank використовується UIView.invalidateIntrinsicContentSize — частий виклик цього методу говорить про нестабільний Layout.
Код на Swift визначає пропущені кадри через CADisplayLink. Якщо різниця між timestamp і targetTimestamp перевищує 16.6 мс — фіксується 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 metric) і 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 ratio: \${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-регресій.
Котлін-код демонструє асинхронне підвантаження даних на екран після reportFullyDrawn, щоб важка робота не блокувала перший кадр. Колбек викликається після того, як користувач бачить інтерфейс.
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 мс для 60 FPS).
Layout Jank (часті requestLayout), Draw Jank (overdraw), Thread Jank (блокування main thread), GC Jank (збирання сміття), 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також