How to Debug ANR (Application Not Responding) in Mobile Apps
How to Debug Anr (Application Not Responding) in Mobile Apps
How to Debug Anr (Application Not Responding) in Mobile Apps
Understanding ANR: What Triggers the Dialog
Android defines an Application Not Responding (ANR) when the UI thread is blocked for too long, preventing the system from processing input events. The threshold differs by component type:
- Activity UI thread – >5 seconds of continuous work.
- BroadcastReceiver – >10 seconds while executing
onReceive(). - Service – >20 seconds in
onCreate(),onStartCommand(), oronBind().
When the limit is exceeded, the WindowManager shows the “App isn’t responding” dialog and writes a traces file to /data/anr/traces.txt. The dialog is a safety net; the real problem is that some code prevented the Looper from dispatching messages.
ANRs are not crashes; they do not generate a stack trace in Logcat unless you look for the specific “ANR in” line. Because the UI thread is still alive but stuck, the app may appear frozen while background threads that thread‑local work continues elsewhere. Understanding the exact blocking point is the first step toward a fix.
Definition and Android's ANR Mechanism
The Android framework monitors the Looper of the main thread via a watchdog. If the Looper does not call loop() for the allotted time, the watchdog posts a Message that triggers the ANR dialog. The watchdog runs in a separate system process, so it is not affected by the app’s own scheduling.
When the ANR occurs, the system dumps the state of all threads to /data/anr/traces.txt. This file contains a snapshot of each thread’s stack trace at the moment the watchdog fired, which is invaluable for pinpointing the blocking call.
Common Triggers (UI Thread Blocking >5s, BroadcastReceiver >10s, Service >20s)
Typical culprits include:
- Synchronous network or disk I/O on the UI thread.
- Heavy computation such as JSON parsing, image decoding, or bitmap manipulation.
- Long‑running database queries or transactions.
- Lock contention where a UI thread waits for a monitor held by a background thread.
- Expensive work in a
BroadcastReceiver(e.g., registering a receiver that does file I/O). - Services that perform startup initialization on the main thread.
Knowing these patterns helps you focus your investigation when you see an ANR.
Reproducing ANR Reliably
An ANR that appears only in production is notoriously hard to debug because it may depend on device state, memory pressure, or specific user actions. A reliable reproduction strategy combines stress testing, deterministic UI scripts, and autonomous exploration tools.
Stress Testing with MonkeyRunner / ADB Monkey
The adb monkey command generates pseudo‑random streams of touch, motion, and key events. By increasing the event count and adding throttling, you can increase the chance of hitting a code path that blocks the UI thread.
# Generate 10 000 events with a 200 ms delay between events
adb shell monkey -p com.example.myapp -v 10000 --throttle 200
When monkey produces an ANR, the system logs a line like:
I/ActivityManager: ANR in com.example.myapp (com.example.myapp/.MainActivity)
You can then pull the traces file for analysis (see the next section).
Using SUSA Autonomous Exploration to Surface ANR
SUSA (susatest.com) explores an app without scripts by simulating a variety of user personas—curious, impatient, novice, power user, and others. Each persona has its own timing profile; for example, the “impatient” persona performs rapid taps and scrolls, which often expose UI‑thread blocking that a real user might encounter when they try to dismiss a loading screen quickly.
To run SUSA against a local APK:
pip install susatest-agent
susatest run --apk path/to/app-debug.apk --personas impatient,elderly --output ./susa-report
The generated report flags any ANR occurrences, includes the traces.txt excerpt, and even suggests the UI flow that led to the block. Because Susa remembers explored screens across runs, subsequent executions become smarter and can surface edge‑case ANRs that only appear after a specific navigation sequence.
Creating Deterministic Repro with Espresso/UIAutomator
When you have a hypothesis about which UI action triggers the ANR, encode it in an instrumented test. Espresso lets you synchronize with the UI thread, ensuring the test waits for idle state before proceeding.
@Test
fun `clicking submit triggers ANR on slow network`() {
// Simulate a delayed network response
IdlingRegistry.getInstance().register(NetworkIdlingResource(3000))
onView(withId(R.id.submit_button)).perform(click())
// Verify that the app does not show the ANR dialog within 6 s
assertFalse(waitForAnrDialog(6000))
}
A custom IdlingResource that counts active network calls can make the test wait until the background work finishes, letting you verify that the UI thread stays responsive.
Capturing Signals: Logs, Traces, and Tombstones
Once you can reproduce the ANR, you need to collect the diagnostic artifacts. Android provides several sources of information, each with its own strengths.
Logcat Filtering for “ANR in”
The simplest signal is a log line that the ActivityManager writes when an ANR is detected.
adb logcat | grep -i "ANR in"
Typical output:
I/ActivityManager: ANR in com.example.myapp (com.example.myapp/.MainActivity)
I/ActivityManager: PID: 12345
I/ActivityManager: Reason: Input dispatching timed out (Waiting because the touched window has not yet finished processing the input event that was delivered to it.)
The line includes the package, activity, and a short reason. Use this to confirm that you are looking at the right incident.
Pulling /data/anr/traces.txt
After an ANR, the system writes a detailed trace file. Pull it with:
adb root # if using a userdebug/build
adb pull /data/anr/traces.txt ./traces.txt
The file contains sections like:
----- pid 12345 at 2025-10-31 14:22:07 -----
Cmd line: com.example.myapp
...
"main" prio=5 tid=1 WaitingForMainLooper
| group="main" sCount=1 dsCount=0 flags=0 obj=0x7f8c0000 self=0x7f8c001000
| sysTid=12346 nice=-10 cgrp=default sched=0/0 handle=0x7f8c001000
| state=S schedstat=( 0 0 0 ) utm=12 stm=3 core=2 HZ=100
| #00 pc 0000000000049a2c /system/lib/libc.so (epoll_wait+12)
| #01 pc 00000000000f1b34 /system/lib/libart.so (_ZN3art11ThreadList13DumpThreadInfoEPKc+116)
| ...
Look for the thread named "main" (or "UI Thread"). Its stack trace shows exactly where the UI thread was blocked when the watchdog fired.
Using Perfetto and Systrace
Perfetto provides a system‑wide trace with configurable buffers and low overhead. To capture a trace around an ANR:
# Start a 5‑second trace, focusing on the scheduler and input pipeline
perfetto -c - <<EOF
buffers: {
size_kb: 6400
fill_policy: RING_BUFFER
}
data_sources: {
config {
name: "linux.syscall"
}
}
duration_ms: 5000
EOF
After the trace finishes, open the resulting .perfetto-trace file in the Perfetto UI (https://ui.perfetto.dev). You can see the main thread’s state over time, identify periods where it stays in S (sleeping) due to a futex wait, and correlate those periods with specific functions from your app.
Using Firebase Crashlytics NDK
Although Crashlytics is marketed for native crashes, it also captures ANRs when you enable firebaseCrashlytics { nativeSymbolUploadEnabled true }. In the Firebase console, ANRs appear under “Issues” with a label “Application Not Responding”. The stack trace is the same as the one in /data/anr/traces.txt, but you get the added benefit of version grouping, device metadata, and email alerts.
Step‑by‑Step Diagnosis Workflow
A methodical approach reduces guesswork. Follow these stages each time you encounter an ANR.
1. Gather Baseline Info
- Retrieve the ANR log line from Logcat.
- Pull
/data/anr/traces.txt. - Note the device model, Android version, and any relevant system properties (e.g.,
ro.build.characteristics).
2. Identify Blocking Thread from Traces
Open the traces file and locate the "main" thread section. The topmost frame (the one closest to the top of the stack) is the method that was executing when the watchdog fired. Common patterns:
java.lang.Object.wait()– indicates a lock or condition variable.java.net.SocketInputStream.read()– points to synchronous network I/O.android.database.sqlite.SQLiteStatement.execute()– suggests a long DB query.android.graphics.BitmapFactory.decodeStream()– reveals heavy image decoding.
If the stack shows a native method (e.g., in libc.so or libart.so), look at the Java frames above it to see what triggered the native call.
3. Correlate with Recent UI Events
Check Logcat for user‑generated events that happened just before the ANR timestamp. Look for:
ViewRootImpl: Dispatching touch eventActivityManager: Displayed activityChoreographer: Skipped frames
If you see a rapid succession of clicks or a scroll gesture right before the block, the offending code is likely invoked by a click listener, scroll listener, or lifecycle method triggered by that interaction.
4. Check for Common Culprits (IO, Locks, Heavy Computation)
Review the code at the offending stack frame. Ask:
- Is this method performing disk or network I/O?
- Does it acquire a lock (
synchronized,ReentrantLock) that could be held by another thread? - Is it doing a costly operation like JSON parsing, regex matching, or bitmap manipulation on the UI thread?
If the answer is yes, you have identified the root cause.
5. Validate Fix with Repeated Runs
After applying a fix (e.g., moving work to a background thread), run the same reproduction steps multiple times:
- Run the monkey script for 50 000 events.
- Execute the Espresso test 20 times on different device configurations (API levels, RAM sizes).
- Run SUSA with all personas and confirm that no ANR appears in the report.
If the ANR disappears consistently, you can be confident the issue is resolved.
Common Causes and Fixes
Understanding the typical patterns helps you spot ANRs quickly during code review or while reading a stack trace. The table below summarizes the most frequent causes, their observable symptoms, and recommended fixes.
| # | Cause | Typical Symptoms (from traces/logcat) | Fix |
|---|---|---|---|
| 1 | Synchronous network or disk I/O on UI thread | Stack shows SocketInputStream.read(), FileInputStream.read(), or OkHttp call inside onClick() or onCreateViewModel initializer. | Move the call to a Coroutine, RxJava, WorkManager, or AsyncTask (deprecated). Use okhttp with enqueue callbacks. |
| 2 | Heavy bitmap decoding or image processing | Stack contains BitmapFactory.decodeStream(), BitmapFactory.decodeFile(), or RenderScript invocation. | Use Glide/Picasso/Coil with proper downsampling; decode off‑main thread via BitmapFactory.decodeFileDescriptor(options, null, BitmapFactory.Options) inside Coroutine or AsyncTask. |
| 3 | Long-running database transaction | Stack shows SQLiteDatabase.beginTransaction(), execSQL(), or Room query inside UI thread. | Perform DB work via Room with @Query returning LiveData/Flow, or use Coroutine with withContext(Dispatchers.IO). |
| 4 | Monitor contention / deadlock | Stack contains Object.wait() or LockSupport.park() while holding a monitor that another thread also tries to acquire. | Refactor to avoid nested locks; prefer Mutex from Kotlin coroutines or ConcurrentLinkedQueue. Ensure locks are always released in finally. |
| 5 | BroadcastReceiver doing heavy work | ANR reason mentions BroadcastReceiver and timeout >10 s; trace shows work in onReceive(). | Move work to a Service or JobIntentService; call goAsync() if you need more time but still finish quickly. |
| 6 | Service performing initialization on main thread | Stack trace originates from Service.onCreate() or onStartCommand() with heavy lifting. | Offload initialization to an IntentService, JobScheduler, or WorkManager. |
| 7 | Third‑party SDK blocking UI thread | Trace enters SDK class (e.g., com.facebook.ads.InterstitialAdManager$1) and stays there. | Check SDK documentation for async initialization; if none, wrap SDK calls in a background thread or consider an alternative library. |
| 8 | Accessibility service interference | ANR occurs only when TalkBack or Switch Access is enabled; trace shows AccessibilityService.onKeyEvent() or similar. | Test with accessibility services disabled to confirm; if the issue is in your view hierarchy, simplify custom views or avoid overriding performClick() with heavy logic. |
Heavy Work on UI Thread (Bitmap Decoding, JSON Parsing)
A common mistake is decoding a large JPEG inside an ImageView setter:
// Bad – runs on UI thread
imageView.setImageBitmap(BitmapFactory.decodeStream(context.assets.open("large_photo.jpg")))
Fix with Coil (Kotlin‑first, coroutine‑based):
imageView.load("large_photo.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
}
If you must decode manually, do it off the main thread:
lifecycleScope.launch {
val bitmap = withContext(Dispatchers.Default) {
BitmapFactory.decodeStream(context.assets.open("large_photo.jpg"))
}
withContext(Dispatchers.Main) {
imageView.setImageBitmap(bitmap)
}
}
Database Transactions on Main Thread
Room automatically throws an exception if you query on the UI thread (unless you use @MainThread). Still, raw SQLiteDatabase usage can slip through:
// Bad
db.execSQL("DELETE FROM logs WHERE timestamp < ?", arrayOf(System.currentTimeMillis() - DAY_MS))
Fix:
// Using Room with Flow
@Query("DELETE FROM logs WHERE timestamp < :cutoff")
suspend fun purgeOldLogs(cutoff: Long)
// Call from a ViewModel
viewModelScope.launch {
repository.purgeOldLogs(System.currentTimeMillis() - DAY_MS)
}
Synchronized Blocks / Deadlocks
Consider a scenario where the UI thread holds lock A and tries to acquire lock B, while a worker thread holds B and tries to acquire A. The traces will show both threads parked on Object.wait().
Solution: lock ordering. Always acquire locks in a globally consistent order (e.g., alphabetical by lock name) or replace synchronized with higher‑level constructs like kotlinx.coroutines.sync.Mutex.
private val mutex = Mutex()
// UI thread
lifecycleScope.launch {
mutex.lock()
try {
// critical section
} finally {
mutex.unlock()
}
}
BroadcastReceiver Doing Long Work
If you need to download a file when a boot completed intent arrives, do not do it in onReceive():
@Override
public void onReceive(Context ctx, Intent intent) {
// Bad – may exceed 10 s
new DownloadService().startDownload(ctx);
}
Correct approach:
@Override
public void onReceive(Context ctx, Intent intent) {
ctx.startService(new Intent(ctx, DownloadService.class));
}
And make DownloadService an IntentService or use WorkManager.
Third‑Party SDKs
Some ad SDKs initialize synchronously in their init() method, which developers sometimes call from Application.onCreate(). If the SDK performs network handshake, you can get an ANR on slow connections. Wrap the call:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
lifecycleScope.launchWhenStarted {
withContext(Dispatchers.IO) {
AdsSdk.initialize(this)
}
}
}
}
Accessibility Service Interference
When an accessibility service is enabled, the system delivers extra events (AccessibilityEvent.TYPE_VIEW_CLICKED) to your views. If a custom view overrides performClick() and does heavy work there, the added event stream can tip the timing over the threshold.
Test by disabling TalkBack and re‑enabling it; if the ANR disappears, review any overridden performClick(), onTouchEvent(), or dispatchPopulateAccessibilityEvent() for costly logic and move it off the UI thread.
Tools Comparison: Manual vs Automated
Choosing the right tool depends on your workflow, the reproducibility of the issue, and the depth of insight you need. The table below contrasts the most common approaches.
| Tool / Technique | Primary Strength | Typical Overhead | Best For | Limitations |
|---|---|---|---|---|
Logcat + traces.txt | Immediate, zero‑setup view of the blocking stack | None (just pull file) | Quick triage, reproducing ANR locally | No context of system state; requires reproduction |
| Systrace / Perfetto | System‑wide, configurable buffers, low‑impact tracing | ~1‑2 % CPU when enabled (adjustable) | Identifying scheduler delays, lock contention, UI thread stalls over time | Requires learning the UI; large traces can be heavy to analyze |
| Android Studio Profiler (CPU) | Visual flame‑graph, method‑level timing, integrates with debugger | Moderate (sampling) | Finding hot methods, verifying that a fix reduces CPU usage | Sampling may miss very short spikes; UI thread blocking may not show as high CPU if waiting on I/O |
| ADB bugreport | Bundles logs, traces, dumpsys, and kernel logs in one zip | None (post‑mortem) | Field‑issue analysis, sending to teammates | Large file; you must extract the relevant sections |
| LeakCanary (memory‑related ANR) | Detects leaked objects that can cause GC pauses leading to ANR | Low (runs in background) | Suspected GC‑induced stalls | Only helps when ANR correlates with GC; does not catch pure I/O blocks |
| SUSA Autonomous QA | Explores app with multiple personas, remembers explored states, auto‑generates regression scripts (Appium/Playwright) | Low‑moderate (depends on exploration depth) | Early discovery of ANRs in CI, regression guarding, persona‑based testing | Requires APK or URL; not a replacement for targeted unit tests |
Android Studio Profiler
Open How to use:
- Connect device, select the app in the Profiler tab.
- Click Record → perform the steps that lead to ANR.
- Stop recording; inspect the CPU timeline.
Look for the main thread’s state: if it stays in Sleeping for >5 s while the UI thread shows no method execution, you likely have a blocking I/O or lock wait.
Systrace / Perfetto
Perfetto is the modern replacement for Systrace. Example command to capture a 10‑second trace focused on scheduling and input:
perfeto --txt -o mytrace.perfetto-trace -b 64000 -t 10s -e sched,sync,input
Open the trace, select the main thread row, and zoom into the region where the thread state is S (sleeping). Hover over the sleep interval to see the futex address; then look up which Java monitor corresponds to that address in the traces.txt.
ADB bugreport
When you cannot reproduce locally but have a user‑submitted bug report:
adb bugreport ./bugreport.zip
unzip bugreport.zip
grep -i "ANR in" bugreport/*.txt
The bugreport also contains dumpstate output, showing CPU usage, memory pressure, and which processes were running at the time—helpful for determining if the ANR was caused by resource starvation rather than a code bug.
LeakCanary
Add the dependency:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'
LeakCanary will automatically trigger a heap dump when it detects a leaked Activity or Fragment. If the heap dump shows a large Bitmap retained by a static field, the ensuing GC can cause the main thread to pause for enough time to trigger an ANR. Fix the leak (e.g., clear the bitmap in onDestroy()), and the ANR may disappear.
SUSA Autonomous QA Platform
SUSA not only finds ANRs but also generates Appium (Android) and Playwright (Web) scripts that reproduce the exact flow. After a run, you receive a folder like:
/susa-run-2025-10-31_14-22/
traces.txt
video.mp4
regression/
appium_test.java
playwright_test.ts
You can commit the regression script to your CI pipeline to ensure the ANR does not reappear. Because SUSA explores with varied timing profiles (impatient, elderly, etc.), it often discovers ANRs that only manifest under specific interaction speeds—something a manual tester might miss.
Preventive Practices
Preventing ANRs is cheaper than fixing them after they reach users. Adopt these habits in your development lifecycle.
StrictMode Policy
Enable StrictMode in debug builds to catch accidental disk or network access on the main thread:
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyDeath()
.build()
)
}
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
When a violation occurs, Logcat prints a stack trace, letting you fix the issue before it becomes an ANR in production.
Use of Kotlin Coroutines / RxJava
Replace AsyncTask or raw Thread with structured concurrency. Example with coroutines:
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
init {
viewModelScope.launch {
try {
val data = repository.fetchData() // suspend function
_uiState.value = UiState.Success(data)
} catch (e: IOException) {
_uiState.value = UiState.Error(e)
}
}
}
}
Because fetchData() is a suspend function, the heavy network work runs on a dispatcher (default is Dispatchers.IO), leaving the UI thread free.
Move Work to WorkManager / IntentService
For tasks that must outlive the UI (e.g., uploading logs, syncing with a server), use WorkManager:
val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
"upload_logs",
ExistingWorkPolicy.KEEP,
uploadWork
)
WorkManager runs on a background thread and handles device‑doze and app‑restart scenarios gracefully.
Load Images with Glide/Picasso/Coil
Image loading libraries automatically downsample, cache, and decode off the main thread. Example with Glide:
Glide.with(this)
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(imageView)
If you need to manipulate the bitmap (e.g., apply a circular crop), use the library’s transformation APIs rather than doing it yourself on the UI thread.
Use of Android Architecture Components (ViewModel, LiveData)
ViewModel survives configuration changes, allowing you to keep data‑loading logic away from UI lifecycle methods. LiveData or StateFlow automatically posts updates on the main thread, so you never need to manually post to a Looper.
class Repository {
fun getData(): Flow<Data> = flow {
emit(Result.Loading)
try {
val result = remoteDataSource.fetch()
emit(Result.Success(result))
} catch (e: Exception) {
emit(Result.Error(e))
}
}
}
Collect the flow in the ViewModel and expose it as a StateFlow.
Code Review Checklist for Threading
Add these items to your PR template:
- [ ] No direct
Thread.start()orAsyncTaskusage outside of legacy code. - [ ] All network calls are wrapped in a suspend function or use enqueue callbacks.
- [ ] Database accesses are annotated with
@WorkerThread(Room) or performed inside awithContext(Dispatchers.IO)block. - [ ]
synchronizedblocks are minimal and never cross UI‑thread boundaries. - [ ] Third‑party SDK init calls are off‑loaded to a background thread or performed inside
WorkManager. - [ ] Accessibility‑related overrides (
performClick,onTouchEvent) contain only lightweight UI updates.
Running static analysis tools like Detekt with the android rule set can catch many of these violations automatically.
Real‑World Examples
Concrete cases illustrate how the abstract patterns manifest in actual codebases.
Example 1: ANR Caused by Synchronous Network Call in Fragment.onCreateView
Situation
A Fragment’s onCreateView() performed a REST call using HttpURLConnection and blocked waiting for the response. On a Nexus 5X running Android 9, the call took ~7 seconds due to poor Wi‑Fi, triggering the ANR dialog.
Traces Snapshot
"main" prio=5 tid=1 WaitingForMainLooper
| group="main" sCount=1 dsCount=0 flags=0 obj=0x765a0000 self=0x765a001000
| sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x765a001000
| state=S schedstat=( 0 0 0 ) utm=45 stm=12 core=3 HZ=100
| #00 pc 0000000000049a2c /system/lib/libc.so (epoll_wait+12)
| #01 pc 00000000000f1b34 /system/lib/libart.so (_ZN3art11ThreadList13DumpThreadInfoEPKc+116)
| #02 pc 00000000000f2a5c /system/lib/libart.so (_ZN3art11ThreadList13Dump+156)
| #03 pc 000000000010e6a7 /system/lib/libart.so (_ZN3art9Thread13DumpStackImplEPKcbPNS_9IndenterP8_Printer+108)
| #04 pc 00000000000ff5c0 /system/lib/libart.so (_ZN3art9Thread13Dump+160)
| #05 pc 0000000000100c8c /system/lib/libart.so (_ZN3art10ThreadList13Dump+200)
| #06 pc 00000000001011a4 /system/lib/libart.so (_ZN3art10ThreadList14ForEach+20)
| #07 pc 00000000000f1c5c /system/lib/libart.so (_ZN3art12DumpCheckpoint2RunEPKc+36)
| #08 pc 00000000007efd28 /system/lib/libandroid_runtime.so (_ZN7android8AndroidRuntime12dumpThreadsEPKc+28)
| #09 pc 00000000007f0f1c /system/lib/libandroid_runtime.so (_ZN7android8AndroidRuntime14startVmThreadEPKcPv+28)
| #10 pc 000000000006f9c0 /system/lib/libc.so (__pthread_start+32)
| #
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free