Налаштування ANR (Application Not Responding) мониторингу для Android-додатків
ANR відбувається, коли main thread не відповідає на вхідні данні більше ніж 5 секунд або BroadcastReceiver не завершується за 10 секунд. Система убиває діалог «Чекати/Завершити», користувач бачить зависаний екран, а у Crashlytics з'являється запис без stack trace — тільки ANR та ім'я Activity. Це найгірший сценарій: крах принаймні показує трейс.
Джерела ANR
Найчастіше — це не один важкий виклик, а ланцюг: корутина запускається на Dispatchers.Main, викликає runBlocking, всередині якого стоїть Room.database.query() без suspend. На слабому пристрої з зайнятим диском це 5+ секунд блокування.
Друга за частотою причина: SharedPreferences через getSharedPreferences() при холодному старті. На Android 7–9 на бюджетних пристроях перше читання з SharedPreferences блокує main thread до 800ms, якщо файл не у page cache.
// Антипаттерн — читання SharedPreferences на main thread при старті
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val token = getSharedPreferences("prefs", MODE_PRIVATE)
.getString("auth_token", null) // блокує main thread
}
}
Правильно: винести в DataStore (корутинний) або читати в lifecycleScope.launch(Dispatchers.IO).
Інструменти для мониторингу ANR
Firebase Crashlytics з AnrDetector — найпростіший шлях, якщо Crashlytics вже підключений. Crashlytics реєструє ANR через ApplicationExitInfo API (доступен з API 30). На Android < 30 — через власний watchdog-потік.
// Crashlytics ANR автоматично, якщо підключений
implementation("com.google.firebase:firebase-crashlytics:18.+")
Sentry детектує ANR через watchdog: запускає потік, який кожні 1000ms перевіряє, живий ли main thread. Якщо немає відповіді > anrTimeoutIntervalMillis — знімає stack trace.
SentryAndroid.init(this) { options ->
options.dsn = "https://[email protected]/project"
options.isAnrEnabled = true
options.anrTimeoutIntervalMillis = 5000
options.isAnrReportInDebug = false // не спамим у debug
}
Android Vitals у Google Play Console — агреговані дані з продакшену без SDK. Показує ANR Rate за версіями та типом пристрою. Поріг «поганої поведінки» — > 0.47% ANR Rate від щоденних активних користувачів.
Діагностика через StrictMode
У debug-збірці StrictMode допомагає спіймати потенційні ANR до продакшену:
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyFlashScreen()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
}
penaltyFlashScreen() — підсвічує екран червоним при кожному disk read на main thread. Дратує, але працює: розробник бачить проблему одразу, а не через тиждень у Crashlytics.
ApplicationExitInfo — точне джерело трейсів
З API 30 Android зберігає причину завершення процесу в ApplicationExitInfo. Це набагато точніше, ніж watchdog-потоки:
val activityManager = getSystemService(ActivityManager::class.java)
val exitReasons = activityManager.getHistoricalProcessExitReasons(null, 0, 10)
exitReasons.filter { it.reason == ApplicationExitInfo.REASON_ANR }.forEach { info ->
info.traceInputStream?.use { stream ->
val trace = stream.bufferedReader().readText()
// відправляємо trace у ваш мониторинг
Log.e("ANR", trace)
}
}
traceInputStream містить повний thread dump у момент ANR — ті ж самі дані, що в /data/anr/traces.txt. Можна прочитати при наступному запуску додатку та відправити у Sentry/Datadog як attachment.
Налаштування алертів
У Firebase Crashlytics налаштовуємо velocity alert на ANR:
- Поріг: > 1% crash-free sessions affected за 1 годину
- Канал: Slack webhook через Firebase Alert Channels
У Sentry аналогічно через Issue Alerts з умовою event.type:transaction AND event.tags.mechanism:ANR.
Що ми робимо
- Підключаємо ANR-детектор через Sentry або Crashlytics (обидва працюють, не конфліктують)
- Налаштовуємо читання
ApplicationExitInfoна API 30+ для точних трейсів - Включаємо StrictMode у debug-збірці для превентивної діагностики
- Налаштовуємо velocity alerts з сповіщеннями у Slack
- Аналізуємо Android Vitals baseline для порівняння з конкурентами в категорії
Часові оцінки
Базове налаштування: 4 години – 1 день. З аналізом ApplicationExitInfo та дашбордом: 2 дні. Ціна розраховується індивідуально.







