Повільний застосунок — головна причина, через яку користувачі видаляють програми. Частки секунди затримки при запуску або прокручуванні списку знижують утримання на десятки відсотків. Продуктивність (performance) — це не лише швидкість, а й стабільність: відсутність ANR, крашів та витоків пам'яті. У цій статті розберемо всі аспекти продуктивності: від управління пам'яттю (GC, ARC) до профілювання інструментами. Детальніше — в офіційному посібнику Android Performance.
Головне
Продуктивність застосунку безпосередньо пов'язана з гальмуванням (jank) — помітною затримкою між дією користувача та реакцією інтерфейсу. Основні причини: блокування Main Thread (важкі операції на UI-потоці), часті перемальовування layout (overdraw), витоки пам'яті (частий GC), неоптимальні алгоритми (O(n²) на великих даних). Frame Rate (FPS) — кількість кадрів на секунду. Для комфортного досвіду потрібні стабільні 60 FPS (Android) або 120 FPS (iPhone Pro, iPad Pro). VSync — синхронізація відтворення з частотою оновлення екрана.
Jank виникає, коли відтворення одного кадру перевищує 16.6 мс (для 60 FPS) або 8.3 мс (для 120 FPS). Профілювання GPU (Profile GPU Rendering на Android, Core Animation на iOS) показує, які етапи рендерингу займають найбільше часу. Основні етапи: Layout (розстановка елементів), Draw (малювання), Display (передача в буфер кадру). Найчастіша проблема — layout inflation в XML, особливо при використанні складних вкладених ConstraintLayout.
Time-to-Interactive (TTI) — час, за який застосунок стає повністю готовим до взаємодії. TTI включає Cold Start, завантаження даних, ініціалізацію бібліотек. Google рекомендує TTI менше 5 секунд, Apple — менше 2 секунд для основних екранів. Lazy Loading — техніка відкладеного завантаження контенту та бібліотек, критична для покращення TTI. В IT Sectr ми застосовуємо ліниву ініціалізацію за замовчуванням на всіх проектах.
ANR та Crash — головні вороги продуктивності мобільного застосунку. ANR (Application Not Responding) — діалогове вікно на Android, що з'являється, якщо головний потік заблоковано більше 5 секунд. Причини: синхронні мережеві запити на UI-потоці, робота з базою даних без корутин, large bitmap decode без downsampling, deadlock на Main Thread. Стек викликів ANR зберігається в /data/anr/traces.txt і дозволяє визначити точне місце блокування.
Crash — несподіване завершення застосунку. На Android — це Exception (Java/Kotlin) або Signal (native code). На iOS — NSException або сигнал (EXC_BAD_ACCESS — звернення до звільненої пам'яті). Інструменти Crash Reporting: Firebase Crashlytics, Sentry, BugSnag. Вони збирають stacktrace, дані про пристрій та кроки відтворення. Stack Overflow — переповнення стеку викликів при нескінченній рекурсії. OutOfMemoryError — коли купа (heap) переповнена.
StrictMode — інструмент Android для виявлення порушень потокової безпеки. Дозволяє задати правила: ThreadPolicy (заборонити диск/мережу на main thread), VmPolicy (виявити витоки Activity, SQLite, CloseGuard). StrictMode рекомендується вмикати тільки в debug-складанні — в релізі він не повинен працювати. На iOS аналог — Main Thread Checker (Xcode), який автоматично виявляє UIKit-виклики не на головному потоці.
Витік пам'яті (Memory Leak) — ситуація, коли об'єкт залишається в пам'яті, хоча застосунок його більше не використовує. Це безпосередньо знижує продуктивність застосунку. На Android GC (Garbage Collection) не може зібрати об'єкт, якщо на нього є сильне посилання. Типові причини: статичні посилання на Activity, нескасовані колбеки/спостерігачі, внутрішні класи з неявним посиланням на зовнішній клас, Handler з неочищеними повідомленнями. LeakCanary — бібліотека для автоматичного виявлення витоків.
ARC (Automatic Reference Counting) — модель управління пам'яттю в iOS. Кожен об'єкт має лічильник посилань (retain count). При обнуленні лічильника пам'ять звільняється. Retain Cycle — коли два об'єкти тримають сильні посилання один на одного (A → B і B → A). ARC ніколи не обнулить лічильники. Рішення: слабкі посилання (weak) або безхазяйні (unowned). Weak автоматично обнуляється (sets to nil) при звільненні об'єкта. Unowned — не обнуляється, але гарантує, що об'єкт живий.
GC (Garbage Collection) працює на Android (Java/Kotlin). GC періодично призупиняє виконання (Stop-the-World pause) для пошуку та звільнення недосяжних об'єктів. GC Trigger: при заповненні купи (heap) на певний відсоток. ARC працює на iOS (Swift/Objective-C) і не має пауз — лічильники оновлюються атомарно при кожному присвоєнні. ARC більш передбачуваний, але може накопичувати надлишкові retain/release при високій частоті присвоєнь.
Weak Reference та Strong Reference — тип посилання визначає, чи може GC/ARC звільнити об'єкт. Strong Reference — об'єкт не буде зібрано, поки існує це посилання. Weak Reference — GC/ARC може зібрати об'єкт; weak-посилання стане nil (у Swift/Java WeakReference). Unowned Reference (Swift) — не обнуляється при звільненні, доступ до нього після смерті об'єкта викликає краш. На Android для слабких посилань використовується java.lang.ref.WeakReference.
Приклад виявлення витоку на Android через LeakCanary:
// Утечка: анонимный класс держит ссылку на Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// Используем `this@MainActivity`, сохраняя ссылку на Activity
Log.d("TAG", "Handler received message")
}
}
handler.sendEmptyMessageDelayed(0, 60000)
}
}
// Исправление: статический Handler + WeakReference
class SafeHandler(activity: MainActivity) : Handler() {
private val weakActivity =
WeakReference(activity)
override fun handleMessage(msg: Message) {
weakActivity.get() ?: return
Log.d("TAG", "Handler received message")
}
}
Профілювання — процес вимірювання продуктивності застосунку: CPU, пам'ять, мережа, енергоспоживання. Без профілювання оптимізація наосліп марна — ви не дізнаєтеся, яка частина коду реально гальмує.
| Інструмент | Платформа | Вимірює | Коли використовувати |
|---|---|---|---|
| Instruments (Time Profiler) | iOS | CPU, виклики функцій, час виконання | Оптимізація алгоритмів, пошук вузьких місць |
| Instruments (Allocations) | iOS | Пам'ять, кількість об'єктів, retain counts | Пошук витоків та надмірного споживання пам'яті |
| Instruments (Leaks) | iOS | Retain cycles, витоки пам'яті | Регулярна перевірка перед релізом |
| Android Profiler (CPU) | Android | CPU usage, thread activity, traces | Пошук блокувань Main Thread |
| Android Profiler (Memory) | Android | Heap dump, allocation tracking | Пошук витоків, аналіз об'єктів |
| Android Profiler (Network) | Android | Трафік, швидкість, таймінги запитів | Оптимізація мережевих викликів |
| LeakCanary | Android | Автоматичне виявлення витоків пам'яті | На всіх етапах розробки |
| StrictMode | Android | Диск/мережа на main thread, витоки | Debug-складання |
| Traceview / Systrace | Android | Метод-трасування, системні події | Глибокий аналіз затримок |
Instruments (Xcode) — найпотужніший інструмент для iOS. Time Profiler показує, які функції споживають найбільше CPU. Allocations відстежує створення та звільнення об'єктів. Leaks автоматично знаходить retain cycles. Кроки профілювання: (1) запустити Instruments; (2) вибрати шаблон (Time Profiler для CPU); (3) виконати проблемний сценарій; (4) проаналізувати стек викликів — найширший стовпець — найгарячіша функція.
Android Profiler — вбудований в Android Studio (View → Tool Windows → Profiler). CPU Profiler показує завантаження кожного потоку. Memory Profiler — heap dump та allocation tracking. Network Profiler — всі HTTP-запити з таймінгами. Energy Profiler — споживання енергії: WakeLock, Location, Network. Для детального трасування використовується Systrace (Android 10+) або Perfetto — системний трейсинг з точністю до мікросекунд.
Запуск застосунку — один з ключових показників продуктивності. Він ділиться на три типи: Cold Start — застосунок запускається з нуля: процес створюється, Application.onCreate (Android) / AppDelegate.applicationDidFinishLaunching (iOS), завантаження класів, ініціалізація бібліотек. Warm Start — процес існує, але Activity/ViewController знищений (наприклад, при повороті екрану або поверненні з пам'яті). Hot Start — Activity/ViewController в пам'яті, застосунок просто показується (перемикання з іншого застосунку).
Cold Start — найважливіша метрика. На Android включає: (1) launch Activity — завантаження XML, ініціалізація View; (2) перший frame — час до першого відтворення. Google рекомендує: launch Activity < 200 мс, перший frame < 500 мс, TTI < 5 секунд. Оптимізація Cold Start: зменшити Application.onCreate (корутини для лінивої ініціалізації), використовувати SplashScreen API (Android 12+), відкласти ініціалізацію бібліотек (WorkManager, DI), видалити зайві ContentProviders.
На iOS Cold Start включає: завантаження Mach-O бінарного файлу, dyld (динамічний лінковщик), ініціалізація Objective-C runtime, Application delegate, перший controller. Chrome Custom Tabs (Android) та Universal Links (iOS) — технології для швидкого відкриття зовнішнього контенту в застосунку без повного Cold Start. Рекомендується тестувати Cold Start на реальних пристроях середнього сегменту.
Розмір застосунку — фактор продуктивності встановлення та оновлень. Він впливає на конверсію: кожні 10 MB знижують конверсію на 1%. Google Play рекомендує розмір APK менше 150 MB; App Store — менше 200 MB (стільникові мережі — 100 MB). Основні методи оптимізації: стиснення зображень (WebP замість PNG дає 25-35% економії), векторизація (VectorDrawable в Android, SF Symbols в iOS), видалення невикористовуваного коду (R8/ProGuard), видалення невикористовуваних ресурсів (lint → unused resources).
App Bundle (Android) — формат публікації, при якому Google Play генерує оптимізований APK під кожен пристрій. App Bundle зменшує розмір завантаження на 20-40%. Dynamic Delivery — модулі, які завантажуються на вимогу (on-demand feature modules). На iOS еквівалент — On-Demand Resources (ODR): ресурси, що завантажуються після першого запуску (рівні гри, відео).
Lazy Loading — техніка, при якій модулі та бібліотеки не завантажуються при запуску, а підвантажуються по необхідності. Split APK (Android) та App Slicing (iOS) — розділення застосунку на архітектурні слоти: arm64-v8a, x86_64. Оптимізація розміру застосунку — постійний процес: аналізуйте склад APK (Analyze APK в Android Studio), видаляйте дублікати іконок, використовуйте SVG замість декількох щільностей PNG. В IT Sectr ми включаємо перевірку розміру збірки в CI/CD для кожного MR.
Часті запитання
ANR (Application Not Responding) — діалог, який з'являється на Android, якщо головний потік заблоковано більше 5 секунд. Щоб уникнути ANR, виносьте всі важкі операції (мережа, база даних, обробка файлів) у фонові потоки. На iOS аналог — frozen UI, коли застосунок перестає реагувати на дотики.
Memory Leak — витік пам'яті, коли об'єкт не може бути звільнений, тому що на нього залишаються посилання. Retain Cycle — ситуація в iOS/Objective-C, коли два об'єкти посилаються один на одного (A → B → A), і ARC не може звільнити жоден. Рішення: weak/unowned посилання та своєчасне очищення колбеків.
Для iOS: Instruments (Time Profiler, Allocations, Leaks). Для Android: Android Profiler (CPU, Memory, Network), LeakCanary (витоки пам'яті), StrictMode (порушення потоків). Рекомендується комбінувати профілювання на етапі розробки та інтеграції.
Cold Start — застосунок запускається з нуля: процес створюється, класи завантажуються, Application.onCreate виконується. Warm Start — процес існує, але Activity/ViewController перестворюється. Hot Start — Activity/ViewController вже в пам'яті, просто показується. Cold Start найдовший (1-5 секунд) і критичний для користувацького досвіду.
Основні методи: видалення невикористовуваних ресурсів та коду (використовуйте R8/ProGuard), векторизація зображень (VectorDrawable, SF Symbols), компресія PNG/WebP (Android), App Bundle замість APK, видалення зайвих бібліотек, Lazy Loading модулів. Оптимізація розміру може скоротити APK на 40-60%.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.