FPS (Frames Per Second) — это метрика, показывающая, сколько отдельных кадров графическая система отрисовывает за одну секунду. В мобильной разработке FPS является стандартным показателем производительности UI: чем выше FPS, тем плавнее анимации и отзывчивее интерфейс. По данным Google Android Performance, 2025, целевое значение FPS для мобильных приложений составляет 60 кадров в секунду — это порог, при котором человеческий глаз воспринимает движение как непрерывное и плавное.
Главное
FPS (Frames Per Second) — это единица измерения частоты кадров, используемая в компьютерной графике, видео и мобильных интерфейсах. Каждый кадр — это статичное изображение, которое отображается на экране в течение короткого промежутка времени. При быстрой смене кадров мозг воспринимает их как непрерывное движение — этот эффект называется персистенцией зрения. Для мобильных приложений FPS — критическая метрика, потому что любой пропущенный кадр (drop) превращает плавную анимацию в заметную запинку. Приложение должно успевать отрисовывать каждый кадр строго в рамках временного бюджета: 16.6 мс для 60 FPS, 11.1 мс для 90 FPS, 8.3 мс для 120 FPS.
FPS измеряется не только для UI, но и для игр, видео и камеры. В играх FPS зависит от сложности сцены, качества текстур и мощности GPU. В видео FPS фиксирован (24, 30, 60 кадров/с) и определяется контентом. В мобильных приложениях FPS зависит от эффективности UI-кода: сложности Layout, количества View, частоты перерисовок и работы GC (Garbage Collection). По данным Apple WWDC 2022, средний FPS в приложении может падать на 10–15% из-за неэффективного обновления коллекций (reloadData вместо insert/delete/dequeueReusableCell). Измерение FPS в реальном времени — стандартная практика для QA-инженеров и разработчиков, работающих над производительностью.
Расчёт FPS в мобильном приложении основан на замере времени между последовательными кадрами. Простейшая формула: FPS = 1000 / deltaTimeMs, где deltaTimeMs — интервал между завершением предыдущего кадра и завершением текущего. Если текущий кадр был отрисован за 20 мс, FPS = 1000 / 20 = 50. Однако на практике FPS редко бывает стабильным даже в течение одной секунды: типичный профиль включает кадры по 12–16 мс, перемежающиеся с пропущенными (jank) или медленными кадрами (40–60 мс). Поэтому FPS измеряют как скользящее среднее за 1–5 секунд или как процентили распределения времени кадров.
В Android FPS вычисляется через Choreographer, который получает колбэк от VSync (импульса синхронизации дисплея). Каждый колбэк соответствует одному кадру. Если колбэк не пришёл — кадр пропущен. Choreographer позволяет измерить точное количество кадров в секунду и количество пропущенных (skipped frames). В iOS CADisplayLink работает аналогично — он вызывается каждый раз, когда дисплей готов к отрисовке нового кадра. Свойство timestamp содержит точное время последнего кадра, а targetTimestamp — ожидаемое время следующего. Разница между ними — это бюджет времени на текущий кадр.
Код на Swift демонстрирует простой мониторинг FPS через CADisplayLink. Счётчик frameCount увеличивается при каждом вызове, и раз в секунду вычисляется фактический FPS.
class FpsCounter {
private var displayLink: CADisplayLink?
private var frameCount = 0
private var lastTime = TimeInterval(0)
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(countFrame)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func countFrame() {
frameCount += 1
let now = Date().timeIntervalSince1970
if now - lastTime >= 1.0 {
print("FPS: \(frameCount)")
frameCount = 0
lastTime = now
}
}
}
Стандарт 60 FPS (или 60 Гц) закрепился в индустрии по нескольким причинам. Первая — физиологическая: человеческий глаз не различает отдельные кадры при частоте выше 50–60 Гц, воспринимая их как плавное движение. Этот порог называется Critical Flicker Fusion (CFF). Вторая — историческая: первые электронно-лучевые трубки (CRT) работали на частоте 60 Гц в США (NTSC) и 50 Гц в Европе (PAL). Современные ЖК-дисплеи унаследовали эту частоту. Третья — инженерная: для UI-анимаций 60 FPS обеспечивает суб-миллисекундную задержку отклика на касание, что критично для ввода текста, скролла и перетаскивания.
Для мобильных разработчиков 60 FPS — это не просто рекомендация, а строгий бюджет в 16.6 мс на кадр. Этот бюджет делится между всеми фазами рендеринга: Input (1–2 мс), Animation (2–3 мс), Layout (3–5 мс), Draw (3–5 мс) и Swap (1–2 мс). Если какая-либо фаза превышает свой под-бюджет, кадр может не уложиться в 16.6 мс. Google Android Performance рекомендует укладываться в 12–14 мс на подготовку кадра, оставляя 2–4 мс запаса под системные прерывания (GC, фоновые потоки). По данным Firebase Performance, приложения со средним FPS ниже 52 и P99 FPS ниже 30 получают на 35% больше жалоб на производительность в Google Play отзывах.
FPS и Frame Time (время кадра) — две стороны одной метрики, и их важно не путать. FPS — скорость, Frame Time — задержка. При 60 FPS каждый кадр занимает 16.6 мс. При 30 FPS — 33.3 мс. Но FPS — нелинейная метрика: падение с 60 до 30 FPS означает, что время кадра выросло в 2 раза, а падение с 30 до 20 — в 1.5 раза. Поэтому профилировщики показывают не FPS, а Frame Time — это позволяет видеть проблемные кадры, а не усреднённую частоту. Например, среднее 55 FPS может скрывать, что 5% кадров имеют Frame Time 50–100 мс — эти кадры вызывают Jank, но не сильно влияют на среднее FPS.
При анализе производительности рекомендуется смотреть не средний FPS, а гистограмму Frame Time. В Android Studio Profiler и iOS Instruments Frame Time отображается в виде шкалы, где зелёная зона — до 16.6 мс (60 FPS), жёлтая — 16.6–33.3 мс (30–60 FPS), красная — более 33.3 мс (менее 30 FPS). Каждый красный столбец — это заметный пользователю лаг. Практическое правило: P95 Frame Time (95% кадров укладываются в X мс) — более надёжная метрика, чем средний FPS. Если P95 Frame Time превышает 32 мс (30 FPS), приложение воспринимается как тормозящее даже при среднем FPS = 50.
Функция на Kotlin для конвертации массива времени кадров в FPS с процентилями. Возвращает не только средний FPS, но и P50, P90 и P99 для детального анализа.
data class FpsReport(
val average: Float,
val p50: Float,
val p90: Float,
val p99: Float
)
fun List<Long>.toFpsReport(): FpsReport {
val fpsValues = this.map { ms ->
if (ms > 0) 1000f / ms else 0f
}.sorted()
return FpsReport(
average = fpsValues.average().toFloat(),
p50 = fpsValues[fpsValues.size / 2],
p90 = fpsValues[(fpsValues.size * 90 / 100)],
p99 = fpsValues[(fpsValues.size * 99 / 100)]
)
}
Современные мобильные устройства с дисплеями 90, 120 и 144 Гц предъявляют новые требования к FPS. Если приложение выдаёт 60 FPS на 120-герцовом дисплее, пользователь видит микро-запинки, потому что каждый второй цикл обновления экрана получает тот же кадр. Для поддержания 120 FPS бюджет на кадр сокращается с 16.6 до 8.3 мс — это требует вдвое более эффективного кода рендеринга. По данным разработчиков Android (Google I/O 2023), для достижения стабильных 120 FPS необходимо: избегать аллокаций в Draw-цикле, минимизировать количество View в иерархии (менее 80), отказаться от тяжелых drawable в пользу VectorDrawable и использовать surfaceView для сложной графики.
В iOS ситуация аналогична: iPhone Pro с ProMotion (120 Гц) требует вдвое больше кадров, но время на каждый кадр вдвое меньше. Apple отмечает, что не все анимации должны работать на 120 FPS — Core Animation автоматически понижает частоту для неподвижных или медленно меняющихся элементов. Однако скролл, анимации жестов и transitions должны выдавать 120 FPS для ощущения "шелковистости". Основные проблемы при переходе с 60 на 120 FPS: увеличение энергопотребления (на 25–40% для GPU), нагрев устройства и троттлинг — когда частота падает из-за перегрева. Рекомендуется реализовать fallback-механизм: если Frame Time стабильно превышает 8.3 мс, снизить целевую частоту до 60 FPS программно, а не ждать системного троттлинга.
Код на Java для Android определяет, может ли устройство поддерживать 120 FPS, и переключает режим рендеринга. Используется Display.getMode для определения поддерживаемых частот.
class FpsModeSwitcher {
static boolean canDo120Fps(Activity activity) {
Display display = activity.getWindowManager()
.getDefaultDisplay();
for (Display.Mode mode : display.getSupportedModes()) {
if (mode.getRefreshRate() >= 120f) {
return true;
}
}
return false;
}
}
Оптимизация FPS требует системного подхода, начиная с профилирования и заканчивая рефакторингом проблемных мест. Первый этап — измерить текущий FPS с помощью профилировщика (. Второй этап — найти кадры, превышающие бюджет. Для Android это можно сделать через GPU Profiling или Perfetto. Для iOS — Instruments с шаблоном Core Animation. Третий этап — устранить причины: сократить overdraw, уменьшить глубину вложенности View, заменить layout-фазу на ConstraintLayout, добавить ViewHolder Recycling, перенести тяжёлые вычисления в фоновый поток.
Специфические для FPS оптимизации включают: Frame Pacing — механизм, который равномерно распределяет время между кадрами, чтобы избежать "пачек" быстрых и медленных кадров. В Android Choreographer.FrameCallback с фиксированным интервалом позволяет реализовать Frame Pacing. В iOS CADisplayLink.preferredFrameRateRange делает то же самое. Второй метод — Triple Buffering: система использует три буфера вместо двух, что позволяет GPU начать рисовать следующий кадр, не дожидаясь освобождения предыдущего. Android автоматически включает Triple Buffering при необходимости, но для iOS разработчик может явно запросить его через CAMetalLayer. Третий — Texture Caching: кэширование растров в GPU-памяти, чтобы не перезагружать их при каждом кадре.
Пример на Kotlin демонстрирует реализацию Frame Pacing с фиксированным интервалом 16.6 мс. Все колбэки поступают с равномерным интервалом, даже если система задерживается.
class PacedFrameRenderer {
private val targetDelta = 16_666_666L // 16.6 ms (60 FPS)
private var lastFrameTime = 0L
private val frameCallback =
Choreographer.FrameCallback { frameTimeNanos ->
val delta = frameTimeNanos - lastFrameTime
if (delta >= targetDelta) {
onFrame(delta)
lastFrameTime = frameTimeNanos
}
Choreographer.getInstance()
.postFrameCallback(this)
}
private fun onFrame(delta: Long) {
// отрисовка кадра
}
}
Часто задаваемые вопросы
60 FPS — комфортный уровень для мобильных приложений. Разница между 60 и 120 FPS заметна только на дисплеях с высокой частотой обновления при быстрых анимациях (скролл, перетаскивание). Ниже 30 FPS — дискомфорт.
FPS = 1000 / FrameTime (ms). Если Frame Time = 16.6 мс, FPS = 60. Если Frame Time = 33.3 мс, FPS = 30. Рекомендуется мониторить Frame Time, а не FPS, так как он показывает проблемные кадры.
При скролле система вызывает Layout и Draw для каждого нового элемента списка. Если View сложные, Layout не кэшируется или используются тяжёлые drawable — Frame Time растёт и FPS падает. Решение — ViewHolder recycling и flat-иерархия.
Используйте Instruments с шаблоном Core Animation (показывает FPS в реальном времени). Для программного замера — CADisplayLink с подсчётом кадров в секунду. Для продакшна — MetricKit с метрикой MXAnimatoryMetric.
Triple Buffering использует три буфера вместо двух, позволяя GPU начать рендеринг следующего кадра до завершения VSync текущего. Это сглаживает пиковые нагрузки и повышает стабильность FPS, но добавляет 1 кадр задержки.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также