On this page
An ANR happens when your app's main thread can't respond in time: to an input event, a broadcast, or a service start. The user sees a dialog offering to close the app. Unlike crashes, ANRs often hide in rare conditions: slow storage, lock contention, or a slow network call made synchronously.
ANR types and timeouts
| Type | Triggered when | Typical timeout |
|---|---|---|
| Input dispatch | The app doesn't respond to an input event (tap, key) | About 5 seconds |
| Broadcast receiver | onReceive doesn't finish in time | Around 10 s (foreground) to 60 s (background) |
| Service | Service lifecycle methods take too long | Seconds, depending on foreground or background |
| Foreground service start | startForeground() not called soon enough after starting | A few seconds |
| Content provider / job | Provider or job callbacks block too long | Varies |
Exact values vary by Android version. The principle is constant: never block the main thread.
Common culprits
- Disk I/O on main: synchronous SharedPreferences
commit(), large file reads, database queries. - Network on main, often hidden inside a "quick" SDK call.
- Lock contention: the main thread waits for a lock held by a busy background thread.
runBlockingon the main thread to call suspend code.- Heavy work in
onReceiveor in Application/Activity creation. - Binder calls to slow system services or other apps' content providers on main.
Diagnosing from ANR traces
- Open Android vitals β ANRs in the Play Console, or Crashlytics if you use it, to see clusters by stack trace.
- Look at the main thread stack ("main" thread) in the trace. Its top frames show what it was doing or waiting on.
- If main is
WAITINGorBLOCKEDon a lock, find the thread that holds the lock in the same trace. - Reproduce locally with a slow device or emulator, network throttling, and StrictMode enabled.
"main" prio=5 tid=1 Native
at android.database.sqlite.SQLiteConnection.nativeExecuteForCursorWindow(Native Method)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java)
...
at com.example.orders.OrdersRepository.getAllBlocking(OrdersRepository.kt:42)
at com.example.orders.OrdersActivity.onCreate(OrdersActivity.kt:31)Here the fix is obvious: getAllBlocking runs a database query from onCreate on the main thread.
Fixes
// β Blocking in onCreate
val orders = repository.getAllBlocking()
// β
Suspend + lifecycle-aware collection; Room handles threading
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
repository.observeOrders().collect(adapter::submitList)
}
}class SyncTriggerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Don't sync here; schedule it.
WorkManager.getInstance(context).enqueueUniqueWork(
"sync", ExistingWorkPolicy.KEEP, OneTimeWorkRequestBuilder<SyncWorker>().build(),
)
}
}- Use
apply()rather thancommit()for SharedPreferences, or better, migrate to DataStore. - Replace
runBlockingwith coroutines launched in a proper scope. - Avoid holding locks during I/O. Prefer coroutine
Mutexon background dispatchers. - Initialise SDKs lazily and off the main thread where supported.
Prevent with StrictMode
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().penaltyFlashScreen().build()
)
}Real cases
Startup ANR on old devices
Cause: a large JSON config parsed in Application.onCreate. Fix: parse lazily on a background dispatcher, with defaults until it's ready.
ANR on tapping "Save"
Cause: a synchronous file write and image compression in the click handler. Fix: a ViewModel coroutine with Dispatchers.IO and progress UI.
Receiver ANRs
Cause: a network call in onReceive. Fix: enqueue WorkManager.
Frequently asked questions
What's a "good" ANR rate?
As low as possible. Google Play defines bad-behaviour thresholds for user-perceived ANR rate as a core vital. Check current thresholds in Android vitals documentation.
Do ANRs show up as crashes?
They're reported separately in Android vitals and many crash tools. Monitor both.
Can coroutines still cause ANRs?
Yes, if you run blocking code on Dispatchers.Main or use runBlocking. Suspension prevents ANRs only when the work actually leaves the main thread.
Conclusion
ANRs come from a blocked main thread. Find the cause in the main thread's stack in ANR traces, move I/O and heavy work to background dispatchers or WorkManager, avoid runBlocking and lock contention, and catch regressions early with StrictMode.
Next step: make the first impression fast with Reducing Android App Startup Time.
Official references
π± Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it βΈ
NotiVaultNotification history: recover dismissed notificationsGet it βΈ
Ready to build real Android applications?
Performance work starts with understanding how Android apps are built and how they behave at runtime. Strengthen those fundamentals and the profiling tools start to make sense.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.