Настройка мониторинга ANR (Application Not Responding) в Android-приложении
ANR происходит, когда main thread не отвечает на input-событие дольше 5 секунд или BroadcastReceiver не завершается за 10 секунд. Система убивает диалог «Ждать/Завершить», пользователь видит зависший экран, а в Crashlytics появляется запись без стека вызовов — только 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 дня. Стоимость рассчитывается индивидуально.







