FPS в мобильных приложениях: суть, расчёт и оптимизация

Автор: IT Sectr Опубликовано: 2026-04-01 Время чтения: 11 мин

FPS (Frames Per Second) — это метрика, показывающая, сколько отдельных кадров графическая система отрисовывает за одну секунду. В мобильной разработке FPS является стандартным показателем производительности UI: чем выше FPS, тем плавнее анимации и отзывчивее интерфейс. По данным Google Android Performance, 2025, целевое значение FPS для мобильных приложений составляет 60 кадров в секунду — это порог, при котором человеческий глаз воспринимает движение как непрерывное и плавное.

Главное

  • FPS — количество кадров в секунду, основная метрика плавности UI.
  • Целевое значение — 60 FPS, время на кадр — 16.6 мс.
  • Для High Refresh Rate дисплеев требуется 120 FPS (8.3 мс на кадр).
  • Падение FPS ниже 30 заметно невооружённым глазом как запинки и лаги.
  • Мониторинг FPS в продакшне помогает выявлять регрессии производительности.

Что такое FPS

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 в мобильном приложении основан на замере времени между последовательными кадрами. Простейшая формула: 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 — ожидаемое время следующего. Разница между ними — это бюджет времени на текущий кадр.

Мониторинг FPS через CADisplayLink

Код на Swift демонстрирует простой мониторинг FPS через CADisplayLink. Счётчик frameCount увеличивается при каждом вызове, и раз в секунду вычисляется фактический FPS.

swift
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 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 (время кадра) — две стороны одной метрики, и их важно не путать. 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.

Конвертация Frame Time в FPS

Функция на Kotlin для конвертации массива времени кадров в FPS с процентилями. Возвращает не только средний FPS, но и P50, P90 и P99 для детального анализа.

kotlin
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)]
    )
}

Высокий FPS и новые дисплеи

Современные мобильные устройства с дисплеями 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 программно, а не ждать системного троттлинга.

Switcher между 60 и 120 FPS

Код на Java для Android определяет, может ли устройство поддерживать 120 FPS, и переключает режим рендеринга. Используется Display.getMode для определения поддерживаемых частот.

java
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 требует системного подхода, начиная с профилирования и заканчивая рефакторингом проблемных мест. Первый этап — измерить текущий 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-памяти, чтобы не перезагружать их при каждом кадре.

Frame Pacing через Choreographer

Пример на Kotlin демонстрирует реализацию Frame Pacing с фиксированным интервалом 16.6 мс. Все колбэки поступают с равномерным интервалом, даже если система задерживается.

kotlin
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) {
        // отрисовка кадра
    }
}

Часто задаваемые вопросы

Какой FPS считается комфортным для пользователя?

60 FPS — комфортный уровень для мобильных приложений. Разница между 60 и 120 FPS заметна только на дисплеях с высокой частотой обновления при быстрых анимациях (скролл, перетаскивание). Ниже 30 FPS — дискомфорт.

Как FPS связан с временем кадра?

FPS = 1000 / FrameTime (ms). Если Frame Time = 16.6 мс, FPS = 60. Если Frame Time = 33.3 мс, FPS = 30. Рекомендуется мониторить Frame Time, а не FPS, так как он показывает проблемные кадры.

Почему FPS падает при скролле?

При скролле система вызывает Layout и Draw для каждого нового элемента списка. Если View сложные, Layout не кэшируется или используются тяжёлые drawable — Frame Time растёт и FPS падает. Решение — ViewHolder recycling и flat-иерархия.

Как измерить FPS в iOS?

Используйте Instruments с шаблоном Core Animation (показывает FPS в реальном времени). Для программного замера — CADisplayLink с подсчётом кадров в секунду. Для продакшна — MetricKit с метрикой MXAnimatoryMetric.

Что такое Triple Buffering и как он влияет на FPS?

Triple Buffering использует три буфера вместо двух, позволяя GPU начать рендеринг следующего кадра до завершения VSync текущего. Это сглаживает пиковые нагрузки и повышает стабильность FPS, но добавляет 1 кадр задержки.

Итоги

  • FPS — ключевая метрика плавности интерфейса, целевое значение — 60 кадров в секунду.
  • Frame Time (время кадра) — более точный показатель, чем FPS, особенно P95 и P99 процентили.
  • Для 120 Гц дисплеев требуется 120 FPS с бюджетом 8.3 мс на кадр.
  • Основные причины падения FPS — overdraw, глубокая вложенность View, аллокации в Draw-цикле.
  • Frame Pacing и Triple Buffering помогают сгладить неравномерность времени кадров.
  • Профилирование FPS — через GPU Profiling (Android), Instruments Core Animation (iOS), Firebase Performance.
  • Мониторинг P95 Frame Time в продакшне критичен для выявления регрессий до массовых жалоб пользователей.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также