Frame Rate — це кількість кадрів, яку графічна система відображає за одну секунду. У мобільних застосунках частота кадрів безпосередньо визначає плавність анімацій, скролінгу та переходів між екранами. За даними Android Developers, 2025, цільовий Frame Rate становить 60 fps для стандартних дисплеїв та 120 fps для пристроїв із високою частотою оновлення. Відхилення від цільового значення призводить до візуальних заїкань та погіршення користувацького досвіду.
Головне
Frame Rate (частота кадрів) — це метрика, що вимірюється в кадрах на секунду (fps) та показує, скільки разів за секунду застосунок оновлює зображення на екрані. Людське око сприймає рух як плавний від 24 fps (кіно), але для інтерактивного інтерфейсу потрібно мінімум 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 у простих сценаріях інтерфейсу, але під важким навантаженням (ігри, складні списки) частота падає до 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, який ізолює рендеринг від основного застосунку. Якщо застосунок блокує головний потік, 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 нижчий, дисплей повторює останній кадр, що сприймається як мікро-заїкання.
Android та iOS підтримують динамічне перемикання частоти оновлення. Android 12+ використовує Smart Refresh Rate: під час прокручування система піднімає частоту до 120 Гц, на статичному контенті знижує до 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 основні проблеми: важкі 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також