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: moderate, severe и critical.
Причины 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также