How to Debug ANR Issues Step by Step

Editorial team
Dot
August 6, 2026
How to debug ANR issues step by step – detect, analyze logs, identify root cause and fix Android app freezes

Introduction

An ANR (Application Not Responding) is one of the most frustrating issues an Android developer can face. It doesn’t crash your app, but it makes it feel broken. Unlike a crash, which throws a clear stack trace, an ANR is a silent killer: the UI thread simply stops responding to user input, and Android’s watchdog steps in to ask the user whether to wait or force-close the app.

Since Android Vitals and Play Console now factor ANR rate directly into your app’s visibility and ranking, understanding how to detect, reproduce, and fix ANRs is no longer optional; it’s a core part of shipping a healthy production app.

This blog walks through a complete, reproducible workflow to debug ANR issues step by step, from spotting the symptom to shipping the fix.

 

Problem Statement

Android enforces strict responsiveness rules to protect the user experience:

Trigger Type Timeout Threshold What Happens
Input Dispatching 5 seconds Main thread doesn’t respond to a key press or touch event
Broadcast Receiver 10 seconds (foreground) / 60 seconds (background) onReceive() takes too long
Service 20 seconds (foreground) onStartCommand() /onCreate() blocks the main thread
Content Provider 10 seconds ContentProvider initialization blocks the startup

Important: All four ANR types share one root cause: something is blocking Android’s main (UI) thread. The fix is almost always the same philosophy: move blocking work off the main thread.

The hard part isn’t knowing that it’s knowing where in your 200K-line codebase that blocking call is hiding. That’s where a structured debugging process comes in.

Understanding ANR Trace Files

When an ANR occurs, Android automatically generates a trace dump containing the stack state of every thread at the moment of the freeze. On modern Android versions, this is captured as an am_anr entry in adb logcat, and a full trace snapshot is available via:

  # Pull ANR traces from a connected device (Android 10 and below)
  adb pull /data/anr/traces.txt ./anr-traces.txt
 
  # On Android 11+, ANR traces are per-app and require debuggable builds
  adb shell run-as com.yourapp.package cat /data/anr/anr_*.txt > anr-trace.txt


  # Watch for ANR events live in real time
  adb logcat | grep -E "ANR in|Reason:"

A typical trace snippet looks like this:

----- pid 8452 at 2026-07-06 10:42:13 -----
   Cmd line: com.yourapp.package


   "main" prio=5 tid=1 Blocked
   | group="main" sCount=1 dsCount=0 flags=1 obj=0x72f1a3c0
   | sysTid=8452 nice=0 cgrp=default sched=0/0 handle=0x7b8e4a1c40
   | state=S schedstat=( 128340000 82910000 512 ) utm=10 stm=2 core=3
   at com.yourapp.data.UserRepository.fetchUserSync(UserRepository.java:88)
   at com.yourapp.ui.MainActivity.onCreate(MainActivity.java:45)
   - waiting to lock <0x0d3f2a10> (a java.lang.Object) held by thread 12
   at com.yourapp.network.ApiClient.getSyncResponse(ApiClient.java:120)

Read this bottom-up: the “main” thread is blocked, waiting on a lock held by another thread, while stuck inside a synchronous network call triggered from onCreate(). This single trace already tells us the exact fix needed: move fetchUserSync() off the main thread.

Main thread in the Monitor status, indicating it is blocked waiting on a lock

Common ANR-Causing Patterns

Blocking Network or Disk I/O on the Main Thread

   // ❌ BAD: Synchronous network call directly in onCreate()
   class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)


       // This blocks the UI thread until the network responds
       val user = ApiClient.getSyncResponse("/user/profile")
       bindUserData(user)
   }
   }
   // ✅ GOOD: Move I/O to a background coroutine
   class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)


       lifecycleScope.launch {
           // Dispatchers.IO keeps this off the main thread
           val user = withContext(Dispatchers.IO) {
               ApiClient.getSyncResponse("/user/profile")
           }
           bindUserData(user) // back on Main automatically
       }
   }
   }

Deadlocks from Synchronized Blocks

   // ❌ BAD: Two threads locking objects in reverse order — classic deadlock
   class CacheManager {
   private val lockA = Any()
   private val lockB = Any()


   fun writeFromMainThread() {
       synchronized(lockA) {
           synchronized(lockB) { /* ... */ }
       }
   }


   fun writeFromWorkerThread() {
       synchronized(lockB) {
           synchronized(lockA) { /* ... */ } // reversed order -> deadlock risk
       }
   }
   }

Fix: Always acquire locks in the same global order across every thread, or replace nested locks with a single Mutex / ReentrantLock guarding the whole critical section.

Heavy Work Inside BroadcastReceiver.onReceive()

   // ❌ BAD: onReceive() has a hard 10-second limit
   class SyncReceiver : BroadcastReceiver() {
   override fun onReceive(context: Context, intent: Intent) {
       // Runs synchronously on the main thread — dangerous!
       val data = DatabaseHelper.getInstance(context).heavyQuery()
       processData(data)
   }
   }
   // ✅ GOOD: Delegate to WorkManager instead
   class SyncReceiver : BroadcastReceiver() {
   override fun onReceive(context: Context, intent: Intent) {
       val request = OneTimeWorkRequestBuilder<SyncWorker>().build()
       WorkManager.getInstance(context).enqueue(request)
   }
   }

Flowchart for debugging a broadcast receiver timeout ANR.

 

Pros and Cons of Common Debugging Tools

Tool Pros Cons
StrictMode Catches violations early in dev builds, zero setup cost Doesn’t run in production; some false positives
Android Studio Profiler Visual thread timeline, CPU/Memory correlation Requires reproducing the ANR live/attached
Perfetto / Systrace Deep system-level trace works on release builds Steeper learning curve, larger trace files
Play Console Android Vitals Real production data across all users No stack-level detail, aggregated only
App Crash & ANR Reporting Real-time alerts, symbolic traces, session context Requires SDK integration

 

Step-by-Step Debugging Workflow

Step 1: Enable StrictMode in Debug Builds

   // Application.kt — catch violations the moment they happen
   class MyApplication : Application() {
   override fun onCreate() {
       super.onCreate()
       if (BuildConfig.DEBUG) {
           StrictMode.setThreadPolicy(
               StrictMode.ThreadPolicy.Builder()
                   .detectDiskReads()
                   .detectDiskWrites()
                   .detectNetwork()
                   .penaltyLog()
                   .build()
           )
       }
   }
   }

Step 2: Reproduce the ANR Locally

   # Simulate an ANR manually for testing (debug builds only)
   adb shell am hang --allow-restart


   # Or trigger via the app's own "Force ANR" debug menu if you've added one

Step 3: Capture a System Trace with Perfetto

   # Record a 10-second system trace while reproducing the freeze
   adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto -t 10s sched freq idle am wm gfx view


   # Pull and open in https://ui.perfetto.dev
   adb pull /data/misc/perfetto-traces/trace.perfetto ./

Traceview timeline showing work executed on a worker thread while the main thread is locked

Step 4: Read the ANR Trace and Isolate the Blocking Call

Pull the trace (see Section 3) and locate the “main” thread state. Trace backward from the topmost frame to find the exact method call that blocked.

Step 5: Fix and Verify

Apply the appropriate fix pattern from Section 4, then re-run Step 2 and Step 3 to confirm the main thread stays in a RUNNABLE/SUSPENDED state without ever blocking past the timeout threshold.

Step 6: Monitor in Production

   # Check ANR rate via Play Console Vitals API (requires Google Play Developer API access)
   curl -X GET \
   "https://playdeveloperreporting.googleapis.com/v1beta1/apps/com.yourapp.package/anrRateMetricSet:query" \
   -H "Authorization: Bearer $ACCESS_TOKEN"

Integrate App’s crash and ANR monitoring SDK to get symbolicated ANR traces with full session context (device, OS version, memory state) delivered to your dashboard in real time; no need to wait for the next Play Console sync cycle.

Play Vitals ANR detection showing lock contention insights and recommended fixes

Comparison: ANR Detection Methods

Method Detection Speed Detail Level Best For
Play Console Android Vitals 24–48 hr delay Aggregated stats Long-term trend tracking
StrictMode Instant (dev only) Method-level Catching issues before release
Perfetto/Systrace Manual capture Thread + system-level Deep root-cause analysis
App Real-Time Monitoring Near-instant Full stack trace + session Production alerting

 

Key Takeaways

  • An ANR means the main thread was blocked past Android’s timeout threshold — always start debugging there.
  • ANR trace files are read bottom-up on the “main” thread to find the exact blocking call.
  • The fix pattern is almost always: move I/O, heavy computation, or locks off the main thread using coroutines, WorkManager, or background threads.
  • Use StrictMode during development to catch violations before they ever reach production.
  • Use Perfetto for deep system-level diagnosis when the cause isn’t obvious from the trace alone.
  • Monitor ANR rate continuously in production: Google Play factors this into app visibility, so real-time alerting closes the feedback loop faster than waiting on the Play Console.

 

Conclusion

ANRs are one of the clearest signals that something in your app’s threading model needs attention. The good news is that the debugging process is highly repeatable: capture the trace, read the main thread state, isolate the blocking call, apply the standard off-main-thread fix, and verify with a system trace before shipping.

Treat your ANR rate the same way you treat your crash-free rate as a first-class production health metric, monitored continuously rather than checked only when Play Console sends a warning email.

 

References

FAQ’s

No items found.

Actionable Insights,
Straight to Your Inbox

Subscribe to our newsletter to get useful tutorials , webinars,use cases, and step-by-step guides from industry experts

Start Pushing Real-Time App Updates Today
Try AppsOnAir for Free
Stay Uptodate