Diagnosing and Fixing Main‑Thread ANRs in Android: A Practical Step‑by‑Step Guide
Diagnose and resolve Android main‑thread ANRs with a step‑by‑step guide: spot UI freezes, identify root causes, run Ordered checks, apply fixes, and know when to ask OEMs for help.
04 Feb 2026, 12:10 UTC

Recognizable ANR Pattern
When a user taps a button, scrolls a list, or navigates between screens, and the app freezes for roughly five seconds before the system pops up the classic “App is not responding” dialog, you’re most likely dealing with a main‑thread ANR. Logcat will contain an am: ANR in <package> entry with a reason such as Input dispatching timed out or Broadcast of Intent timed out. The freeze is usually not a user‑visible lag but a hard block that prevents the UI thread from processing input or drawing.
Cause Taxonomy
| Root Cause | Typical Symptom | Common Culprit |
|---|---|---|
| Blocking I/O on UI Thread | Disk read/write, file copy, or network call executed synchronously | FileInputStream.read(), HttpURLConnection.execute(), Room DAO sync call |
| Lock Contention | UI thread waits for a lock held by a background thread | Shared state updates, synchronized blocks around UI data structures |
| Heavy Initialization in Application or ContentProvider | App start feels slow, first activity launch delayed | Database pre‑population, large static caches, heavy reflection in Application.onCreate() |
| Long‑Running BroadcastReceiver.onReceive() | System intent triggers a freeze (e.g., BOOT_COMPLETED, ACTION_HEADSET_PLUG) | Performing network or disk work directly in onReceive() |
| Excessive View Hierarchy Work | Scrolling or layout passes skip frames, UI stutters | Deep nested layouts, custom views doing heavy calculations in onDraw() |
Ordered Diagnostic Checks
- Enable StrictMode (Debug only)
Add the following in
Application.onCreate()to surface disk/network on the main thread:if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); }Run the user flow again. If a log entry appears like
StrictMode: Disk readbefore the ANR, that’s a strong indicator. - Collect ANR Trace
On the device, execute:
adb shell cat /data/anr/traces.txt > ~/Desktop/traces.txtOpen the file and locate the
Main threadsection. Look for a stack that shows a blocking call (e.g.,java.io.FileInputStream.readorjava.lang.Thread.sleep). If the stack is in awaitingstate, note the method name and the thread that holds the lock if present. - CPU Profiler on Android Studio
Start a recording while reproducing the freeze. In the
Mainthread view, find the top stack that occupies >10% of the time. Compare with the ANR trace to confirm the culprit. - Logcat Frame Skipping
Filter logcat for
Skipped X framesorChoreographermessages:adb logcat -s ChoreographerFrequent skipped frames often precede an ANR and point to heavy layout or draw work.
- Check for BroadcastReceiver Blocks
If the ANR occurs immediately after a system event, inspect
onReceive()for heavy work. Useadb logcatto watch the receiver’s thread. - Validate ContentProvider Timing
If the freeze happens on app launch, run
adb shell dumpsys activity activitiesto see the order ofContentProviderinitializations. A heavy provider can delayActivity.onCreate().
Fixes Tied to Findings
| Finding | Remediation |
|---|---|
| Disk or network on UI thread | Move to Executor or CoroutineDispatcher.IO and post results back to main thread via Handler or runOnUiThread(). |
| Room sync DAO call | Replace with suspend fun or LiveData query; let Room handle background execution. |
| Lock contention | Use fine‑grained locks, AtomicReference, or ReadWriteLock; avoid synchronizing on large objects that the UI thread accesses. |
Heavy Application.onCreate() work | Defer initialization to Handler.postDelayed() or WorkManager if non‑critical; keep startup lean. |
Long BroadcastReceiver.onReceive() | Shorten to a lightweight trigger and delegate heavy work to IntentService or WorkManager. |
| Excessive view hierarchy | Replace deep nested layouts with ConstraintLayout, use RecyclerView with view stubbing, or ViewStub for optional components. |
| Native deadlock (libc) | Re‑examine native code paths; use adb shell am dumpheap to inspect. |
Escalation Criteria
- ANR trace shows a native deadlock in
libcorsystem_server– requires OEM or platform debugging. - Reproducible only on a specific OEM skin or Android version while the same code works elsewhere – file a bug with the device vendor.
- Multiple ANRs with no main thread stack (e.g.,
Force finishing activityentries) – indicates system resource exhaustion; consider reporting to the OS team. - High ANR rate (>5% of sessions) on a particular device model – perform device‑specific profiling and consult the device manufacturer’s support channel.
Concrete Example: Disk Read on Main Thread
public void loadConfig() {
try (InputStream in = new FileInputStream("/data/user/0/com.example.app/files/config.json")) {
String json = new String(in.readAllBytes(), StandardCharsets.UTF_8);
// parse JSON …
} catch (IOException e) { /* … */ }
}
During a button press, loadConfig() runs on the UI thread. StrictMode logs a Disk read violation. The ANR trace shows the main thread waiting on FileInputStream.read. The fix is straightforward:
CoroutineScope(Dispatchers.IO).launch {
val json = withContext(Dispatchers.IO) {
val file = File(filesDir, "config.json")
file.readText()
}
withContext(Dispatchers.Main) {
// update UI
}
}
Verification Checklist
- Re‑run the user flow with StrictMode enabled; confirm no disk/network logs appear before the ANR.
- Re‑capture
traces.txt; the main thread stack should no longer contain the blocking I/O call. - Run the profiler again; the main thread should now spend <10% time on the same operation.
- Check logcat for
Skipped X frames– these should disappear or drop to a negligible count. - Compare crash reports: the ANR percentage should drop below the 5% threshold for the target device/OS cohort.
Practical Tips
- Always test on a device that matches the target OS version; ANR thresholds differ between API levels.
- Use
adb shell dumpsys activityto inspect the activity stack if the freeze occurs during navigation. - Keep
Application.onCreate()to under 200 ms on most devices; defer non‑essential work. - When using
BroadcastReceiver, preferJobIntentServiceorWorkManagerfor background work. - Profile on a representative device under load; a debug build may not reproduce production timing.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.