ANR (Application Not Responding) — це системне повідомлення Android, яке з'являється, коли застосунок перестає реагувати на введення користувача довше ніж 5 секунд. За даними Android Developers, основна причина — довгі операції на головному потоці, що блокують обробку дотиків та відтворення інтерфейсу. Розуміння механізмів ANR необхідне кожному Android-розробнику для створення чуйних застосунків.
Головне
ANR (Application Not Responding) — це діалогове вікно операційної системи Android, яке з'являється, коли застосунок перестає відповідати на введення користувача. Система відстежує час обробки подій через InputDispatcher: якщо дотик або натискання кнопки не оброблено за 5 секунд, Android показує діалог із пропозицією закрити або зачекати застосунок.
Механізм ANR захищає користувацький досвід від завислих застосунків. Android не дозволяє одному застосунку блокувати всю систему — на відміну від Desktop-ОС, мобільна платформа примусово обмежує час обробки подій. BroadcastReceiver має ліміт 10 секунд, а служба foreground — 20 секунд.
ANR НЕ є винятком у коді — це системний механізм на рівні Linux-процесів. Android надсилає сигнал SIGQUIT процесу, після чого система зберігає стек викликів усіх потоків у файл traces.txt. Розробник отримує ANR не як catch-виняток, а як звіт після перезапуску застосунку. На Android 11+ з'явився API ApplicationExitInfo, який дозволяє програмно отримати причину завершення процесу, включаючи ANR — це спрощує збір статистики без ручного парсингу traces.txt.
П'ять категорій операцій стабільно призводять до ANR в Android-застосунках. Кожна з них блокує головний потік, не даючи системі обробляти події введення та перемальовування екрана.
Синхронні HTTP-запити, виконані на UI-потоці, — найчастіша причина ANR у початківців. Навіть швидкий запит до сервера може зайняти 1–3 секунди, а при поганому з'єднанні — 30 секунд і більше. Android явно забороняє мережеві операції на головному потоці починаючи з API 11, викидаючи NetworkOnMainThreadException.
Використовуйте Coroutines або RxJava для асинхронних викликів. Корутини з диспетчером Dispatchers.IO виконують запит у фоновому потоці, а результат передають на головний через Dispatchers.Main. Це повністю виключає блокування UI-потоку мережевими операціями.
fun fetchUserData() {
CoroutineScope(Dispatchers.Main).launch {
val result = withContext(Dispatchers.IO) {
api.getUserData() // фонова операція
}
updateUI(result) // результат на головному потоці
}
}
Обробка великих масивів даних, парсинг JSON або XML, робота з bitmap безпосередньо на головному потоці — друга за частотою причина ANR. Навіть 300 мілісекунд безперервної роботи UI-потоку без повернення в цикл подій викликають помітну затримку відтворення, а граничне значення в 5 секунд фіксується як ANR.
WorkManager і фонові служби призначені для винесення важких обчислень із головного потоку. Використовуйте AsyncTask (застарілий), ListenableFuture або Kotlin Flow для передачі даних порціями, не блокуючи UI.
Deadlock виникає, коли два потоки утримують блокування і чекають один одного. Якщо один із потоків — головний, система фіксує ANR рівно через 5 секунд. Thread.join(), CountDownLatch.await() і synchronized-блоки, що викликаються з UI-потоку, несуть ризик блокування.
Уникайте будь-яких блокуючих операцій на головному потоці. Замість synchronized використовуйте ConcurrentHashMap, замість Thread.join() — корутини з async/await. Це правило діє для будь-якої мови в Android: Java, Kotlin або C++ через JNI.
BroadcastReceiver виконується на головному потоці за замовчуванням. Якщо onReceive() зайнятий більше 10 секунд, Android показує ANR. Завантаження даних із БД або мережі всередині onReceive — гарантований шлях до зависання.
Використовуйте goAsync() всередині BroadcastReceiver для перемикання у фоновий потік або registerReceiver із getBackgroundBroadcastReceiver(). Це дозволяє обробляти події без блокування UI.
Важкі запити до ContentProvider або пряма робота з SQLite на UI-потоці — менш очевидна, але часта причина ANR. При міграції бази даних або bulk-вставці тисяч записів час виконання може перевищити 5-секундний ліміт.
Переносьте всі операції з базою даних у фонові потоки через Room із suspend-функціями. Room автоматично перевіряє, що запит не виконується на головному потоці, і викидає виняток при порушенні.
Діагностика ANR відрізняється від налагодження звичайних винятків — ви не можете зловити ANR у try-catch. Основне джерело інформації — файл traces.txt, який Android створює в момент зависання.
traces.txt містить стек викликів усіх потоків застосунку на момент ANR. Щоб прочитати файл із реального пристрою, виконайте команду adb bugreport, яка збирає повний звіт системи, включаючи всі ANR за останній час. Для емулятора файл доступний у /data/anr/traces.txt. Стек викликів показує, який метод виконувався на головному потоці в момент блокування.
adb bugreport bugreport.zip
unzip -p bugreport.zip "*traces*" > traces.txt
Google Play Console надає розділ ANR & Crash із агрегованими звітами та частотами помилок. Для кожного ANR показано стек викликів та статистику за пристроями: модель, версія Android, регіон. Це дозволяє виявити ANR, які залежать від конкретних пристроїв або версій системи.
Android Studio з 2021 року містить ANR Watchdog у профілювальнику. Він автоматично записує дамп потоків, якщо головний потік не відповідає довше за пороговий час. Інструмент показує часову шкалу подій: які операції запускалися, які методи виконувалися і на якому етапі сталося блокування.
Профілактика ANR будується на одному фундаментальному правилі: головний потік повинен тільки обробляти події UI. Будь-яка операція довше 16 мілісекунд (час одного кадру) повинна виконуватися у фоновому потоці.
StrictMode — вбудований інструмент Android для виявлення потенційних ANR на етапі розробки. Увімкніть його в Application.onCreate() із прапорцями на дискові та мережеві операції. При порушенні StrictMode викидає виняток або пише в logcat.
if (BuildConfig.DEBUG) {
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
.let { StrictMode.setThreadPolicy(it) }
}
Kotlin Coroutines — стандартний спосіб асинхронної роботи в сучасних Android-застосунках. Кореневий прийом: операції введення-виведення виконуються на Dispatchers.IO, результат передається на Dispatchers.Main для оновлення UI. Для Flow-подібних сценаріїв використовуйте Dispatchers.Default для CPU-інтенсивних завдань.
RxJava залишається популярним у легасі-проектах. subscribeOn(Schedulers.io()) та observeOn(AndroidSchedulers.mainThread()) — мінімальний набір для запобігання ANR. Головне правило те саме: жоден Observable або Flowable не повинен емітити дані з головного потоку.
Firebase Crashlytics з версії SDK 18.4.0 підтримує моніторинг ANR «з коробки». Для Android 11+ Crashlytics використовує системний API ApplicationExitInfo, який надає точну причину завершення: ANR, Crash або системне вбивство. Увімкніть спеціальні ключі з параметрами екрана та стану для контекстного аналізу.
П'ять інструментів покривають усі етапи роботи з ANR: від налагодження на робочій станції до моніторингу в продакшені. Кожен інструмент вирішує своє завдання та надає дані для різних сценаріїв.
| Інструмент | Призначення | Формат даних |
|---|---|---|
| StrictMode | Виявлення на етапі розробки | Logcat / Exception |
| ANR Watchdog (Android Studio) | Трасування в реальному часі | Thread dump + timeline |
| Google Play Console | Агрегована статистика | ANR rate + stack traces |
| Firebase Crashlytics | Продакшен-моніторинг | ApplicationExitInfo |
| adb bugreport | Повний системний звіт | traces.txt + logcat + dmesg |
Кожен інструмент має свою нішу: StrictMode ловить очевидні порушення на ранніх етапах, Crashlytics показує реальну частоту ANR у користувачів, а adb bugreport дає максимально повну картину для складних випадків. Комбінуйте їх для повного покриття.
Firebase Performance відстежує час відгуку UI-потоку та автоматично створює трейси для підозріло довгих операцій. Якщо головний потік блокується більш ніж на 500 мс, Performance фіксує спеціальний trace з іменем методу-винуватця. Це дозволяє виявляти ANR-сценарії без участі користувача та до того, як вони стануть критичними.
Інтеграція з Firebase Crashlytics дає повну картину: Performance показує уповільнення до ANR, а Crashlytics — сам факт зависання. Налаштуйте оповіщення в Firebase Console на подію ANR rate вище 0.1%, і ви будете отримувати повідомлення про нові проблеми до масових скарг користувачів.
Часті запитання
ANR — це зависання, при якому застосунок не відповідає, але залишається в пам'яті. Crash — повне аварійне завершення з виходом із процесу. ANR можна «пережити», якщо система або користувач дочекаються відповіді, а Crash завжди завершує застосунок.
Ні. ANR — не виняток Java/Kotlin, а сигнал системи на рівні процесів (SIGQUIT). Розробник не може обробити його в коді застосунку. Єдиний спосіб реагувати на ANR — аналізувати звіти після перезапуску.
Продуктивність пристрою, версія Android, завантаженість CPU та кількість фонових процесів впливають на ймовірність ANR. На слабких пристроях та сама операція може виконуватися в 2–3 рази довше, перевищуючи 5-секундний ліміт.
10 секунд для звичайного BroadcastReceiver в onReceive(). Для foreground-служб ліміт становить 20 секунд, а для ContentProvider — немає явного ліміту, але блокування головного потоку довше 5 секунд все одно викликає ANR.
Увімкніть StrictMode у всіх debug-збірках, додайте моніторинг через Firebase Crashlytics та використовуйте adb bugreport, коли ANR трапляється. Нерегулярні ANR часто пов'язані з умовами гонки (race condition) або специфічними станами мережі.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також