πŸ§ͺ Testing, Debugging, and Performance

ANR Errors Explained and How to Fix Them

An ANR, 'Application Not Responding', is Android telling users your app froze. ANR rate is a core Google Play vital, so frequent ANRs can hurt your app's visibility. This guide explains what triggers ANRs, how to diagnose them from traces, and how to fix the usual culprits.

✍️ Yunok Tech πŸ“… Updated ⏱ 3 min read 🎯 Intermediate
On this page
  1. ANR types and timeouts
  2. Common culprits
  3. Diagnosing from ANR traces
  4. Fixes
  5. Prevent with StrictMode
  6. Real cases
  7. Frequently asked questions
  8. Conclusion

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

TypeTriggered whenTypical timeout
Input dispatchThe app doesn't respond to an input event (tap, key)About 5 seconds
Broadcast receiveronReceive doesn't finish in timeAround 10 s (foreground) to 60 s (background)
ServiceService lifecycle methods take too longSeconds, depending on foreground or background
Foreground service startstartForeground() not called soon enough after startingA few seconds
Content provider / jobProvider or job callbacks block too longVaries

Exact values vary by Android version. The principle is constant: never block the main thread.

Main thread blocked leading to an ANR The main thread's message queue holds a tap event. A long blocking task such as a database query or network call is running on the main thread, so the tap waits in the queue. After about five seconds without handling the input, the system shows the Application Not Responding dialog. Main thread timeline blocking: db.query() / network / lock wait tap event waiting… ~5 s β†’ ANR dialog
The tap itself is cheap. It's stuck behind work that should never have been on 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.
  • runBlocking on the main thread to call suspend code.
  • Heavy work in onReceive or in Application/Activity creation.
  • Binder calls to slow system services or other apps' content providers on main.

Diagnosing from ANR traces

  1. Open Android vitals β†’ ANRs in the Play Console, or Crashlytics if you use it, to see clusters by stack trace.
  2. Look at the main thread stack ("main" thread) in the trace. Its top frames show what it was doing or waiting on.
  3. If main is WAITING or BLOCKED on a lock, find the thread that holds the lock in the same trace.
  4. Reproduce locally with a slow device or emulator, network throttling, and StrictMode enabled.
Excerpt of an ANR main-thread trace (illustrative)Text
"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

Move work off the main threadKotlin
// ❌ 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)
    }
}
BroadcastReceiver: hand off long workKotlin
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 than commit() for SharedPreferences, or better, migrate to DataStore.
  • Replace runBlocking with coroutines launched in a proper scope.
  • Avoid holding locks during I/O. Prefer coroutine Mutex on background dispatchers.
  • Initialise SDKs lazily and off the main thread where supported.

Prevent with StrictMode

Debug-only StrictModeKotlin
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.

See all our apps β†’

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.

Share: LinkedIn X / Twitter WhatsApp