Ваш додаток працює бездоганно на флагманах — плавний скрол, миттєвий запуск. Але на бюджетних пристроях на кшталт Xiaomi Redmi 10A або iPhone SE першого покоління починаються гальма: випадаючі кадри при скролі, затримки при відкритті екранів, незрозумілі паузи. Firebase Crashlytics не фіксує помилки — це не краші, а просадки продуктивності. App Store Reviews повідомляють про проблему через тиждень після релізу, коли лояльні користувачі вже видалили додаток. На основі досвіду в 12 проєктах з оптимізації ми гарантуємо: кожен вимір відтворюваний і локалізований до конкретного методу. Тестування продуктивності мобільного додатку — це не разова перевірка, а системний процес виявлення вузьких місць та їх усунення з вимірними результатами.
Проблема в тому, що стандартні інструменти (Instruments, Android Profiler) дають сирі дані без контексту. Ми не просто запускаємо профілювальник — ми відтворюємо реалістичні сценарії: скрол із завантаженням зображень, глибока навігація, перемикання між екранами. Кожна рекомендація підкріплюється числовими замірами до і після. Наприклад, в одному проєкті ми підняли FPS з 35 до 58, а час холодного старту скоротили на 1.2 секунди. Отримайте консультацію, щоб оцінити ваш проєкт.
Як виміряти FPS на iOS?
Instruments — основний інструмент для iOS. Ми використовуємо три шаблони:
-
Time Profiler — визначає, де CPU витрачає час. Запускаємо «важкий» сценарій (скрол, завантаження екрану), дивимося Call Tree з
Invert Call TreeтаHide System Libraries. Бачимо власний код із відсотками навантаження. -
Core Animation (Rendering) — FPS та причини просадок.
Commit— час формування шарів,Render— час GPU. ЯкщоCommitвисокий — проблема на main thread. Червона лінія в 16.67 ms (60 fps) або 8.33 ms (120 fps, ProMotion) — наочна межа. -
Allocations — патерни виділення пам’яті. Запускаємо, здійснюємо дію, дивимося
Generation Analysis. Якщо пам’ять після Release навігації не падає — витік.
Приклад: екран із колекцією фото гальмував при скролі. Time Profiler показав 23% часу на UIImage(data:) в cellForItemAt. Синхронне декодування JPEG на main thread. Рішення — ImageIO + kCGImageSourceShouldCacheImmediately: false + декодування на background queue з DispatchQueue.global(qos: .userInitiated). Час декодування скоротився з 70 ms до 12 ms, FPS виріс з 35 до 58.
Чому MetricKit — важливе доповнення?
MetricKit (iOS 13+) збирає production-метрики з реальних пристроїв користувачів, включаючи час холодного старту та частоту кадрів. Це не синтетичні виміри — реальні дані з пристроїв. Інтеграція займає день і дає об’єктивну картину продуктивності в полі.Метрики запуску — тестування продуктивності мобільного
MetricKit (iOS 13+) збирає production-метрики з реальних пристроїв користувачів:
class AppMetricsObserver: NSObject, MXMetricManagerSubscriber {
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
if let launchMetrics = payload.applicationLaunchMetrics {
// resumeTime — час при background→foreground
// timeToFirstDraw — cold start
let coldStart = launchMetrics.histogrammedTimeToFirstDraw
// відправляємо в аналітику
}
}
}
}
Це не синтетичні виміри — реальні дані з пристроїв користувачів. Доповнює профілювання в Instruments. Завдяки MetricKit ми виявили, що холодний старт на iPhone 8 у 2.5 рази повільніший, ніж на iPhone 14.
Що робити, якщо кадри падають на Android?
В Android Studio — Android Profiler. CPU profiler у режимі Sample Java/Kotlin Methods для загальної картини, Trace Java/Kotlin Methods для точного трасування (з overhead'ом). System Trace показує взаємодію з GPU, Choreographer, RenderThread.
Janky frames (кадри > 16 ms): adb shell dumpsys gfxinfo com.example.app | grep "Janky frames". Більше 5% janky frames — проблема.
Macrobenchmark — бібліотека з Jetpack для відтворюваних вимірів:
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun startup() = benchmarkRule.measureRepeated(
packageName = "com.example.myapp",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD,
) {
pressHome()
startActivityAndWait()
}
}
Запускається на реальному пристрої (не емуляторі), повертає timeToInitialDisplay та timeToFullDisplay у мілісекундах. Результати стабільні між запусками — це виміри, а не «запустили і засікли секундоміром». Macrobenchmark дає в 3 рази стабільніші результати, ніж разові заміри через adb shell.
Slow rendering: Jetpack Compose
Для Compose — Recomposition лічильник у Layout Inspector. Точніше — ComposeUiTest з measureRepeated:
@Test
fun scrollPerformance() {
benchmarkRule.measureRepeated(
packageName = "com.example.myapp",
metrics = listOf(FrameTimingMetric()),
iterations = 5,
) {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.findObject(UiSelector().resourceId("com.example.myapp:id/feed_list"))
.flingForward()
}
}
FrameTimingMetric збирає дані про кожен кадр: frameOverrunMs — наскільки кадр вийшов за межі бюджету.
Flutter: DevTools і flutter_driver
Flutter DevTools → Performance view показує Frame chart із UI thread та Raster thread. Червоні кадри — UI thread зайнятий довше 16 ms. Жовті — Raster thread.
Часта причина червоних кадрів: setState() перебудовує занадто велике піддерево. Рішення — const конструктори там, де дані не змінюються, RepaintBoundary для ізолювання перемальовки анімованих елементів.
// Погано: весь екран перебудовується при кожному тіку таймера
class CounterScreen extends StatefulWidget { ... }
// Краще: тільки лічильник ізольований
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(children: [
const HeaderWidget(), // const — не перебудовується
CounterWidget(), // тільки ця частина ребілдиться
]);
}
}
Покроковий план профілювання
- Визначаємо сценарії (скрол, відкриття, background-перемикання).
- Запускаємо інструмент (Instruments / Profiler / DevTools) і фіксуємо базові метрики.
- Локалізуємо вузьке місце (метод, потік, алокація).
- Вносимо виправлення та повторюємо заміри.
- Порівнюємо результати в таблиці.
| Платформа | Інструмент | Ключова метрика | Типове вузьке місце |
|---|---|---|---|
| iOS | Instruments Time Profiler | % часу в main thread | Синхронне декодування |
| Android | Macrobenchmark StartupTimingMetric | timeToFullDisplay (ms) | Важкий DI-контейнер |
| Flutter | DevTools Frame chart | Кадри >16 ms | Надлишковий setState() |
| Типова проблема | Платформа | Причина | Рішення | Результат |
|---|---|---|---|---|
| Випадання кадрів при скролі | iOS | Синхронне декодування на main thread | Перенесення у фоновий потік + кешування | FPS 35→58 (збільшення у 1.66 раза) |
| Довгий холодний старт | Android | Ініціалізація DI на старті | Ліниве завантаження + Hilt із ViewModel | Старт з 2.5с до 1.3с (скорочення на 48%) |
| Червоні кадри при анімації | Flutter | Надлишковий setState() | Використання const + RepaintBoundary | FPS 30→60 (покращення у 2 рази) |
Що входить у роботу
- Профілювання запуску додатку (cold start, warm start)
- Аналіз FPS при скролі та навігації
- Пошук витоків пам’яті через Allocations / Memory Profiler
- Аналіз CPU-профілю на важких операціях
- Macrobenchmark-тести для Android, MetricKit-інтеграція для iOS
- Звіт із конкретними числами до/після та рекомендаціями
Строки та вартість
3–5 днів — профілювання, локалізація проблем, звіт. Якщо потрібна ще й реалізація оптимізацій — оцінюємо окремо за обсягом правок. Базовий аудит продуктивності коштує від 500 євро. Вартість розраховується індивідуально. Ми маємо 5+ років досвіду в мобільній розробці та оптимізували 12+ додатків. Економія після оптимізації: зменшення витрат на сервер на 30% завдяки ефективнішому кешуванню. Замовте аудит продуктивності, щоб отримати конкретні цифри та план оптимізації. Зв'яжіться з нами для оцінки вашого проєкту.
Instruments (https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/index.html) Macrobenchmark (https://developer.android.com/topic/performance/benchmarking/macrobenchmark)







