Frame Rate — это количество кадров, которое графическая система отображает за одну секунду. В мобильных приложениях частота кадров напрямую определяет плавность анимаций, скролла и переходов между экранами. По данным Android Developers, 2025, целевой Frame Rate составляет 60 fps для стандартных дисплеев и 120 fps для устройств с высокой частотой обновления. Отклонение от целевого значения приводит к визуальным запинкам и ухудшению пользовательского опыта.
Главное
Frame Rate (частота кадров) — это метрика, измеряемая в кадрах в секунду (fps), которая показывает, сколько раз за секунду приложение обновляет изображение на экране. Человеческий глаз воспринимает движение как плавное при частоте от 24 fps (кино), но для интерактивного UI требуется минимум 60 fps, чтобы касания и анимации ощущались мгновенными. Каждый кадр — это полный цикл: обработка пользовательского ввода, вычисление Layout, рендеринг View-иерархии и вывод на экран. Если какой-либо из этапов превышает выделенный бюджет времени (16.6 мс при 60 fps), кадр пропускается, и пользователь видит запинку.
Важно различать Frame Rate приложения и частоту обновления дисплея (Refresh Rate). Частота обновления — это характеристика экрана: сколько раз в секунду дисплей физически обновляет изображение (60, 90, 120 или 144 Гц). Frame Rate — это сколько кадров в секунду успевает отрисовать приложение. Если приложение выдаёт 60 fps при дисплее 120 Гц, каждый второй кадр будет дублироваться — изображение останется плавным, но не таким отзывчивым, как могло бы быть. По данным Google I/O 2023, современные флагманы способны поддерживать 120 fps в простых UI-сценариях, но в тяжёлых (игры, сложные списки) частота падает до 40–60 fps.
Рендеринг кадра в мобильном приложении проходит через конвейер из нескольких этапов. В Android конвейер включает: обработка ввода (Input), анимация (Animation), измерение и расположение (Layout), отрисовка (Draw), синхронизация с GPU и вывод на экран (Swap). Каждый этап выполняется на CPU или GPU, и суммарное время всех этапов не должно превышать бюджет кадра. Для 60 fps бюджет — 16.6 мс, для 120 fps — 8.3 мс. Choreographer (Android) и CADisplayLink (iOS) синхронизируют рендеринг с вертикальной развёрткой дисплея (VSync), гарантируя, что кадр выводится только в момент обновления экрана, избегая tearing (разрыва изображения).
В iOS конвейер схож: Run Loop обрабатывает события, Core Animation вычисляет слои, Render Server (отдельный процесс) рендерит и отправляет кадр на GPU. Отличие iOS — выделенный процесс Render Server, который изолирует рендеринг от основного приложения. Если приложение блокирует main thread, Render Server всё равно может отрисовать последний известный кадр, но анимации остановятся. Если же Render Server сам не успевает — GPU простаивает, и Frame Rate падает. По данным Apple WWDC 2022, наиболее частые причины низкого Frame Rate в iOS — избыточная вложенность CALayer, тяжёлые shadowPath и offscreen rendering.
Код на Kotlin подписывается на Choreographer.FrameCallback и логирует фактическое время между кадрами. Если интервал превышает 16.6 мс — фиксируется пропущенный кадр.
class FrameRateMonitor {
private var lastFrameTime = 0L
private val frameCallback =
Choreographer.FrameCallback { frameTimeNanos ->
if (lastFrameTime != 0L) {
val deltaMs = (frameTimeNanos - lastFrameTime) / 1_000_000f
if (deltaMs > 16.6f) {
Log.w("FrameRate",
"Skipped frame: $deltaMs ms")
}
}
lastFrameTime = frameTimeNanos
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(frameCallback)
}
}
Refresh Rate (частота обновления) — аппаратная характеристика дисплея, определяющая, сколько раз в секунду экран физически перерисовывает изображение. Стандартные дисплеи имеют 60 Гц, современные флагманы — 90, 120 или 144 Гц. Frame Rate приложения может быть ниже, равен или выше частоты обновления (в последнем случае лишние кадры отбрасываются). Идеальный сценарий — Frame Rate совпадает с Refresh Rate: каждый аппаратный цикл получает новый кадр от приложения, и движение максимально плавное. Если Frame Rate ниже, дисплей повторяет последний кадр, что воспринимается как микро-запинки (stutter).
Android и iOS поддерживают динамическое переключение частоты обновления. Android 12+ использует Smart Refresh Rate: при прокрутке система поднимает частоту до 120 Гц, при статичном контенте снижает до 60 или 60 Гц для экономии батареи. iOS ProMotion (iPhone 13 Pro и новее) работает аналогично — частота варьируется от 10 до 120 Гц в зависимости от контента. Разработчик должен проверять, поддерживает ли устройство высокую частоту, и адаптировать бюджет времени на кадр. Если приложение не успевает отрисовать кадр за 8.3 мс (для 120 Гц), лучше принудительно работать на 60 Гц — это обеспечит стабильный Frame Rate без пропущенных кадров.
| Тип дисплея | Refresh Rate | Бюджет на кадр | Устройства |
|---|---|---|---|
| Стандартный | 60 Гц | 16.6 мс | Большинство Android/iOS |
| Высокий | 90 Гц | 11.1 мс | OnePlus, Pixel 6+ |
| Флагманский | 120 Гц | 8.3 мс | iPhone Pro, Galaxy S22+ |
| Игровой | 144 Гц | 6.9 мс | ROG Phone, Nubia RedMagic |
Для измерения Frame Rate в мобильных приложениях доступны как встроенные инструменты платформ, так и сторонние профилировщики. В Android основной инструмент — GPU Profiling (Developer Options → Profile GPU Rendering), который показывает временную шкалу каждого кадра с разбивкой по этапам (Draw, Prepare, Process, Execute). Более детальный анализ предоставляет Android Studio Profiler — он записывает полный профиль рендеринга с указанием конкретных View, вызывающих перерисовку. В iOS используется Instruments с шаблоном Core Animation — он показывает FPS, время отрисовки слоёв и количество offscreen-рендеров.
Для продакшн-мониторинга Frame Rate применяются Firebase Performance (Android) — он собирает Frame Rate в фоне и агрегирует по устройствам, версиям ОС и сессиям. В iOS MetricKit предоставляет аналогичные данные через MXAnimatoryMetric. Для игр и Flutter-приложений используются FrameTimingCallback (Flutter) и Unity Profiler. Важно измерять не средний Frame Rate, а процентили: P50, P90 и P99. Приложение может показывать средние 55 fps, но иметь P99 = 30 fps — это означает, что 1% времени пользователи видят сильные запинки, и этого достаточно для негативных отзывов.
Пример на Dart показывает, как подписаться на FrameTimingCallback во Flutter и логировать количество пропущенных кадров. Callback срабатывает после каждого завершённого кадра.
import 'package:flutter/scheduler.dart';
class FrameRateLogger {
int totalFrames = 0;
int missedFrames = 0;
void start() {
SchedulerBinding.instance
.addTimingsCallback(_onReportTimings);
}
void _onReportTimings(List<FrameTiming> timings) {
for (final timing in timings) {
totalFrames++;
if (timing.totalSpan()
> Duration(milliseconds: 16)) {
missedFrames++;
}
}
debugPrint("FPS: \${totalFrames - missedFrames}");
}
}
Оптимизация Frame Rate начинается с выявления узких мест в конвейере рендеринга. На этапе Layout основные проблемы — избыточная вложенность View-иерархии, использование относительных Layout (RelativeLayout с большим количеством правил) и частые вызовы requestLayout. Решение — использовать ConstraintLayout или Flat-иерархию, избегать вложенности более 5–6 уровней. На этапе Draw — перерисовка (overdraw): когда пиксель рисуется несколько раз за кадр. Например, белый фон Activity под полупрозрачным фрагментом, под которым ещё один слой — каждый пиксель рисуется трижды. Инструмент Debug GPU Overdraw показывает проблемные зоны цветовой индикацией. Рекомендуется удерживать overdraw на уровне 2x и ниже.
В iOS основные проблемы: heavy cornerRadius и masksToBounds — они вызывают offscreen rendering, при котором Core Animation создаёт временный буфер, рисует в него, затем копирует результат на экран. Offscreen rendering легко заметить в Instruments Core Animation: если строка Renderer красная — есть проблемы. Решение — использовать UIImageView с заранее обрезанными изображениями вместо cornerRadius, избегать groupOpacity и shouldRasterize без крайней необходимости. Для обеих платформ критично минимизировать количество вызовов invalidate() и setNeedsDisplay() — каждый такой вызов запускает полный цикл перерисовки вью.
Код демонстрирует замену глубокой вложенности RelativeLayout на плоскую структуру ConstraintLayout. Уменьшение уровня вложенности с 4 до 1 сокращает время Layout на 30–50%.
// Пример: плоская структура через ConstraintLayout
class OptimizedView(context: Context) :
ConstraintLayout(context) {
private val binding =
ItemProfileBinding.inflate(
LayoutInflater.from(context)
)
fun bind(user: User) {
binding.avatar.setImageURI(user.avatarUrl)
binding.nameText.text = user.name
// привязываем данные без перерисовки всего контейнера
}
}
Современные мобильные приложения всё чаще используют адаптивный Frame Rate — систему, которая динамически подстраивает целевую частоту под текущий сценарий. При быстрой прокрутке список требует 120 fps для плавности, при статичном экране достаточно 60 fps или даже 30 fps для видео. В Android адаптация реализуется через Choreographer.setFrameInterval (API 33+) и Window.setFrameRate. Разработчик может указать системе предпочтительную частоту: setPreferredRefreshRate в SurfaceView или setFrameRate в Window. iOS автоматически управляет частотой через ProMotion, но разработчик может явно задавать preferredFramesPerSecond для CADisplayLink.
Динамический Frame Rate особенно важен для игр и приложений с анимациями. По данным Google, снижение Frame Rate с 120 до 60 Гц на статичном экране экономит до 30–40% энергии GPU. Для достижения наилучшего баланса между плавностью и энергопотреблением рекомендуется: измерять фактический Frame Rate в разных сценариях, устанавливать целевой fps в зависимости от сцены (игра — 60, меню — 30, видео — 24), и переключать режимы через Lifecycle-aware компоненты, чтобы при свёртывании приложение не тратило ресурсы на рендеринг 120 fps в фоне.
Код на Swift задаёт preferredFramesPerSecond для CADisplayLink в iOS. При прокрутке частота повышается до 120 Гц, при остановке — снижается до 60 Гц.
class AdaptiveFrameRateManager {
private var displayLink: CADisplayLink?
func startWithHighRate() {
displayLink = CADisplayLink(
target: self,
selector: #selector(step)
)
if #available(iOS 15.0, *) {
displayLink?.preferredFrameRateRange =
CAFrameRateRange(
minimum: 60,
maximum: 120,
preferred: 120
)
}
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func step() {
// обновление анимации
}
}
Часто задаваемые вопросы
Для мобильных приложений целевой Frame Rate — 60 fps (16.6 мс на кадр). Для устройств с дисплеями 120 Гц желательно 120 fps. Значения ниже 30 fps заметно ухудшают пользовательский опыт.
Frame Rate — сколько кадров в секунду отрисовывает приложение. Refresh Rate — сколько раз в секунду дисплей физически обновляет изображение. При Frame Rate ниже Refresh Rate дисплей дублирует последний кадр.
Используйте GPU Profiling в Developer Options, Android Studio Profiler или Firebase Performance. Для программного замера — Choreographer.FrameCallback с вычислением интервала между кадрами.
Overdraw — перерисовка одного пикселя несколько раз за кадр. Каждый лишний слой увеличивает время Draw-фазы и снижает Frame Rate. Оптимальный overdraw — 2x, критичный — 4x и выше.
При статичном контенте Dynamic Frame Rate снижает частоту до 30–60 Гц, уменьшая нагрузку на GPU на 30–40%. При прокрутке частота повышается до 90–120 Гц для плавности.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также