Тестування продуктивності мобільного додатку
Додаток «працює нормально» на iPhone 15 Pro у розробника. На Xiaomi Redmi 10A з Android 11, яким користується третина аудиторії, головний екран завантажується 4 секунди, а список при швидкому скролі заїкається до 20 fps. Firebase Crashlytics не покаже — це не крєш. App Store Reviews покажуть через тиждень після релізу.
Профілювання продуктивності — це не «запустили та подивилися». Це відтворювані вимірювання з конкретними числами, порівняння до та після, локалізація причини до конкретного методу.
iOS: Xcode Instruments
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). FPS виросло з 35 до 58.
Метрики запуску
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.
Android: Android Profiler та Macrobenchmark
У 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 — проблема.
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 у мілісекундах. Стійкий між запусками — це вимірювання, а не натискання секундомира.
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(), // тільки ця частина ребилдиться
]);
}
}
Що включено
- Профілювання запуску додатку (cold start, warm start)
- Аналіз FPS при скролі та навігації
- Пошук утечок пам'яті через Allocations / Memory Profiler
- Аналіз CPU-профіля на важких операціях
- Macrobenchmark-тесты для Android, MetricKit-інтеграція для iOS
- Звіт з конкретними числами до/після та рекомендаціями
Строки
3–5 днів — профілювання, локалізація проблем, звіт. Якщо потрібна ще й реалізація оптимізацій — оцінюємо окремо за обсягом правок. Вартість розраховується індивідуально.







