Setting Up ANR (Application Not Responding) Monitoring for Android Apps
ANR occurs when the main thread doesn't respond to input for over 5 seconds or a BroadcastReceiver doesn't complete in 10 seconds. The system kills the "Wait/Quit" dialog, the user sees a frozen screen, and Crashlytics logs a record without a stack trace — just ANR and the Activity name. This is the worst scenario: a crash at least shows the trace.
Sources of ANR
Most often — not a single heavy call, but a chain: a coroutine launches on Dispatchers.Main, calls runBlocking, inside of which is Room.database.query() without suspend. On a weak device with a busy disk, this blocks for 5+ seconds.
Second most common: SharedPreferences via getSharedPreferences() on cold start. On Android 7–9 on budget devices, the first SharedPreferences read blocks the main thread for up to 800ms if the file isn't in page cache.
// Antipattern — reading SharedPreferences on main thread at startup
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val token = getSharedPreferences("prefs", MODE_PRIVATE)
.getString("auth_token", null) // blocks main thread
}
}
Correctly: move to DataStore (coroutine-based) or read in lifecycleScope.launch(Dispatchers.IO).
Tools for ANR Monitoring
Firebase Crashlytics with AnrDetector — the simplest path if Crashlytics is already connected. Crashlytics registers ANR via the ApplicationExitInfo API (available from API 30). On Android < 30 — via its own watchdog thread.
// Crashlytics ANR automatically if connected
implementation("com.google.firebase:firebase-crashlytics:18.+")
Sentry detects ANR via watchdog: runs a thread checking every 1000ms if the main thread is alive. If no response > anrTimeoutIntervalMillis, it captures the stack trace.
SentryAndroid.init(this) { options ->
options.dsn = "https://[email protected]/project"
options.isAnrEnabled = true
options.anrTimeoutIntervalMillis = 5000
options.isAnrReportInDebug = false // don't spam in debug
}
Android Vitals in Google Play Console — aggregated production data without SDK. Shows ANR Rate by version and device type. Threshold for "bad behavior" — > 0.47% ANR Rate of daily active users.
Diagnosis via StrictMode
In debug builds, StrictMode helps catch potential ANR before production:
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyFlashScreen()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
}
penaltyFlashScreen() — flashes the screen red on every disk read on the main thread. Annoying, but works: developers see the issue immediately, not a week later in Crashlytics.
ApplicationExitInfo — Precise Trace Source
From API 30, Android preserves the process termination reason in ApplicationExitInfo. This is far more accurate than watchdog threads:
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()
// send trace to your monitoring
Log.e("ANR", trace)
}
}
traceInputStream contains the full thread dump at the moment of ANR — the same data as in /data/anr/traces.txt. You can read it on the next app launch and send it to Sentry/Datadog as an attachment.
Alert Configuration
In Firebase Crashlytics, set up a velocity alert for ANR:
- Threshold: > 1% crash-free sessions affected per hour
- Channel: Slack webhook via Firebase Alert Channels
In Sentry, similarly via Issue Alerts with condition event.type:transaction AND event.tags.mechanism:ANR.
What We Do
- Connect ANR detector via Sentry or Crashlytics (both work, no conflict)
- Configure
ApplicationExitInforeading on API 30+ for precise traces - Enable StrictMode in debug builds for preventive diagnostics
- Configure velocity alerts with Slack notifications
- Analyze Android Vitals baseline to compare with competitors in your category
Timeline
Basic setup: 4 hours – 1 day. With ApplicationExitInfo analysis and dashboard: 2 days. Pricing is calculated individually.







