Introduction: The ANR Dialog We Love and Hate
Remember the first time you saw an ANR Dialog?
"Application XX is not responding. Do you want to close it?"
As a user, this dialog is frustrating—the app is frozen, nothing works. But as a developer, this dialog is a lifesaver—at least it tells you something is wrong, and it leaves you traces.txt as the "crime scene."
I once encountered a particularly troublesome ANR: when users switched WiFi in the Settings interface, the entire system UI froze. Looking at logcat, I found the main thread waiting for a lock; examining traces.txt, I discovered the lock-holding thread was making a Binder call; further investigation revealed that excessive ContentObserver registrations led to Binder resource exhaustion... It's like a detective game, and the ANR Dialog is the starting point of the case.
This article will take you deep into the internals of the ANR mechanism, from Input event dispatching to timeout detection, from trace information collection to Dialog popup, providing a complete analysis of ANR's full lifecycle. After reading this article, you will be able to:
- Understand the 4 types of ANR and their timeout durations
- Master how InputDispatcher detects Input ANR
- Understand how ActivityManagerService handles ANR
- Learn to analyze traces.txt and find root causes
- Build ANR monitoring and prevention mechanisms
Ready? Let's unveil the mystery of the ANR mechanism!
I. ANR Fundamentals
1.1 What is ANR?
ANR (Application Not Responding) is a protection mechanism in the Android system. When an application fails to respond to user operations or system events within a specified time, the system considers the app "dead" and pops up an ANR dialog, allowing users to choose whether to continue waiting or force close.
To draw an analogy: ANR is like a restaurant's "order timeout reminder." You order a dish, and if the kitchen hasn't served it within 5 minutes, the waiter comes over to ask "Would you like to keep waiting, or order something else?" This prevents you from waiting indefinitely.
Core Functions of ANR:
- Protect User Experience: Prevent applications from being unresponsive for extended periods
- Timely Problem Detection: Record the "crime scene" through traces.txt
- System Self-Protection: Prevent a single application from dragging down the entire system
1.2 The 4 Types of ANR
The Android system sets different timeout durations for different types of events:
| ANR Type | Timeout | Trigger Scenario | Detection Location |
|---|---|---|---|
| Input ANR | 5 seconds | Touch/key events not processed | InputDispatcher |
| Broadcast ANR | 10s foreground / 60s background | BroadcastReceiver's onReceive() not completed | ActivityManagerService |
| Service ANR | 20s foreground / 200s background | Service lifecycle methods not completed | ActivityManagerService |
| ContentProvider ANR | 10 seconds | ContentProvider publish timeout | ActivityManagerService |
Why are the timeout durations different?
This is based on a trade-off between user experience and system responsiveness:
- Input ANR is shortest (5 seconds): User operations must receive quick feedback, otherwise users will think the device is broken
- Broadcast ANR is moderate (10s/60s): Broadcast receivers should execute quickly but are allowed some processing time
- Service ANR is longer (20s/200s): Background services can handle time-consuming operations, given more tolerance
- Background process timeout is even longer: Background operations don't affect foreground experience, can wait longer
1.3 ANR vs Freeze vs Watchdog
These concepts are often confused. Let's compare them:
| Feature | ANR | Freeze | Watchdog |
|---|---|---|---|
| Detection Target | Application process | Application process | System Server |
| Detection Dimension | Specific event response | Overall responsiveness | Core thread health |
| Timeout Duration | 5-200 seconds | Usually less than a second | 30-60 seconds |
| Consequence | ANR Dialog popup | May trigger ANR | System restart |
| Scope | Single application | Single application | Entire system |
Vivid Analogy:
- ANR: Restaurant waiter checking if your ordered dishes are served
- Freeze: Restaurant manager monitoring if waiters are working
- Watchdog: Fire department periodically inspecting if the restaurant's fire safety system is functioning properly
II. Complete Flow of Input ANR
Input ANR is the most common and typical type of ANR. Let's walk through the complete ANR detection flow using a scenario where a user clicks a button.
2.1 Overall Flow Overview
2.2 InputReader: The Source of Events
InputReader runs in an independent thread, responsible for reading input events from the kernel:
// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
// 1. Read raw events from /dev/input/eventX
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
// 2. Process each event
for (size_t i = 0; i < count; i++) {
const RawEvent& rawEvent = mEventBuffer[i];
// 3. Convert to Android events based on device type (touchscreen, keyboard, etc.)
processEventsLocked(rawEvent);
}
// 4. Hand processed events to InputDispatcher
mQueuedListener->flush();
}
Key Points:
- InputReader is the "porter" of events, only responsible for reading and preliminary processing
- It doesn't care whether events are processed by applications, just forwards them
- The actual timeout detection happens in InputDispatcher
2.3 InputDispatcher: The Heart of Timeout Detection
InputDispatcher is the "heart" of ANR detection. Let's see how it detects timeouts:
// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
// Core logic for dispatching events
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
// 1. Dequeue pending events from the queue
if (mPendingEvent == nullptr) {
mPendingEvent = mInboundQueue.dequeueAtHead();
}
// 2. Find the target Window for the event
std::vector<InputTarget> inputTargets;
int32_t injectionResult = findFocusedWindowTargetsLocked(
currentTime, mPendingEvent, inputTargets, nextWakeupTime);
// 3. Dispatch event to target Window
if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
dispatchEventLocked(currentTime, mPendingEvent, inputTargets);
}
// 4. Check for connection timeouts
nsecs_t nextAnrCheck = checkUnresponsiveConnectionsLocked(currentTime);
if (nextAnrCheck < nextWakeupTime) {
nextWakeupTime = nextAnrCheck;
}
}
Key Logic for Timeout Detection:
// Check if connection is responsive
nsecs_t InputDispatcher::checkUnresponsiveConnectionsLocked(nsecs_t currentTime) {
nsecs_t nextAnrCheck = LONG_LONG_MAX;
for (const auto& [token, connection] : mConnectionsByToken) {
// Check this connection's wait queue
if (connection->waitQueue.empty()) {
continue;
}
// Get head event
DispatchEntry* head = connection->waitQueue.head;
nsecs_t eventTime = head->eventEntry->eventTime;
// Calculate wait duration
nsecs_t waitDuration = currentTime - eventTime;
// If exceeds 5 seconds (5000ms), trigger ANR
if (waitDuration > 5000ms) {
onAnrLocked(connection);
} else {
// Record next check time
nsecs_t timeoutTime = eventTime + 5000ms;
if (timeoutTime < nextAnrCheck) {
nextAnrCheck = timeoutTime;
}
}
}
return nextAnrCheck;
}
Elegant Design:
-
Record timestamp when event enters queue: Each event dispatch records
eventTime - Periodically check head event: InputDispatcher periodically checks the head of waitQueue
-
Calculate wait duration:
waitDuration = currentTime - eventTime -
Timeout triggers ANR: If wait exceeds 5 seconds, call
onAnrLocked()
Why check the head instead of the tail?
Because the head is the earliest sent event. If even the head hasn't been processed, it means the app is truly stuck. It's like queuing for tickets: if the person at the front is still being served, those behind naturally can't proceed.
2.4 The Secret of Wait Queue (waitQueue)
Understanding waitQueue is key to understanding Input ANR:
Lifecycle of waitQueue:
1. Before event is sent
waitQueue: []
2. Event sent to application
dispatchEntry enqueued
waitQueue: [Event1]
Record Event1.eventTime = T0
3. Application processes event (normal case)
Application calls finish()
waitQueue: [] ← Event1 removed
4. Application stuck (abnormal case)
After T0 + 5 seconds
waitQueue: [Event1] ← Event1 still there!
InputDispatcher detects timeout
Triggers ANR
Code Implementation:
// Called after application finishes processing event
void InputDispatcher::finishDispatchCycleLocked(
nsecs_t currentTime, sp<Connection> connection) {
// Remove processed events from waitQueue
while (!connection->waitQueue.empty()) {
DispatchEntry* dispatchEntry = connection->waitQueue.head;
connection->waitQueue.dequeue(dispatchEntry);
// Release resources
releaseDispatchEntry(dispatchEntry);
// If this is the last event, reset timeout check
if (connection->waitQueue.empty()) {
connection->lastEventTime = LLONG_MIN;
}
}
}
2.5 ANR Analysis Example
Let's look at a simplified analysis of a real Input ANR scenario:
Scenario: User quickly swipes in a list interface, suddenly the app becomes unresponsive
logcat Log:
12-25 10:30:15.123 1234 1250 E InputDispatcher: channel 'e8d3a72 com.example.app/com.example.app.MainActivity (server)' ~ Channel is unresponsive! Waited 5008ms
12-25 10:30:15.234 1234 1250 I InputDispatcher: Delivering ANR to com.example.app
traces.txt Fragment:
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x74e04dd8 self=0x7a4e014c00
| sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7b5a9f49a8
| state=S schedstat=( 15678923456 8765432109 1234 ) utm=1500 stm=67 core=2 HZ=100
at com.example.app.RecyclerAdapter.onBindViewHolder(RecyclerAdapter.java:150)
- waiting to lock <0x0a1b2c3d> (a java.lang.Object) held by thread 15
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3500)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:4064)
at android.view.View.layout(View.java:19839)
...
Root Cause Analysis:
-
Phenomenon: Main thread is blocked, state is
Blocked -
Location: Stuck at
RecyclerAdapter.onBindViewHolder() -
Cause: Main thread waiting for a lock
<0x0a1b2c3d>, held by thread 15 - Solution: Find what thread 15 is doing, resolve the deadlock
Key Lessons:
- Never wait for locks on the main thread
- Don't perform time-consuming operations in RecyclerView's Adapter
- Complex data processing should be done in background threads
III. Broadcast ANR Detection Mechanism
Compared to Input ANR's "passive detection" (waiting for event processing to complete), Broadcast ANR uses "active detection" (actively setting timeout).
3.1 Broadcast Distribution Flow
3.2 Timeout Timer Implementation
// frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
private void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) {
// 1. Set timeout duration
final long timeout = r.isForeground() ?
BROADCAST_FG_TIMEOUT : BROADCAST_BG_TIMEOUT;
// 2. Send delayed message
mHandler.sendMessageDelayed(
mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, r),
timeout);
// 3. Call receiver's onReceive()
app.thread.scheduleReceiver(...);
}
// Handler processes timeout message
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BROADCAST_TIMEOUT_MSG:
BroadcastRecord r = (BroadcastRecord) msg.obj;
// Trigger ANR
broadcastTimeoutLocked(r, true);
break;
}
}
// Cancel timeout after broadcast execution completes
private void performReceiveLocked(ProcessRecord app, ...) {
// 1. Execution complete
r.receiver = null;
// 2. Cancel timeout message
mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, r);
// 3. Continue processing next broadcast
processCurBroadcastLocked(r, app);
}
Key Points:
- Use Handler's delayed messages to implement timeout detection
- If completed before timeout,
removeMessages()cancels the timer - If timeout occurs, Handler receives the message and triggers ANR
3.3 Foreground vs Background Broadcast Differences
// Timeout duration definitions
static final int BROADCAST_FG_TIMEOUT = 10 * 1000; // 10 seconds
static final int BROADCAST_BG_TIMEOUT = 60 * 1000; // 60 seconds
// How to determine foreground or background?
boolean isForeground() {
// 1. Broadcast sent with FLAG_RECEIVER_FOREGROUND specified
if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0) {
return true;
}
// 2. Receiving process is a foreground process
if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
return true;
}
// 3. Current receiver of ordered broadcast is foreground
if (ordered && curReceiver.priority > 0) {
return true;
}
return false;
}
Practical Case:
// Case: A time-consuming BroadcastReceiver
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// ❌ Wrong: Doing time-consuming work on main thread
for (int i = 0; i < 1000000; i++) {
// Heavy computation
}
// If exceeds 10 seconds, triggers ANR
}
}
// ✅ Correct approach: Use goAsync()
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 1. Get PendingResult
final PendingResult result = goAsync();
// 2. Process in background thread
new Thread(() -> {
try {
// Time-consuming operation
doHeavyWork();
} finally {
// 3. Notify system when complete
result.finish();
}
}).start();
}
}
What goAsync() does:
- Tells the system "I need async processing, don't rush the timeout"
- System extends timeout duration (but still has limits)
- Must call
result.finish()when complete
IV. Service ANR Detection Mechanism
Service ANR detection logic is similar to Broadcast but more complex, as Services have multiple lifecycle methods that need monitoring.
4.1 Timeout Monitoring of Service Lifecycle
// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
// Service start timeout detection
private final void bringUpServiceLocked(ServiceRecord r, ...) {
// 1. Set timeout duration
final long timeout = r.isForeground() ?
SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT;
// 2. Send timeout message
mAm.mHandler.sendMessageDelayed(
mAm.mHandler.obtainMessage(SERVICE_TIMEOUT_MSG, r),
timeout);
// 3. Start Service
app.thread.scheduleCreateService(r, ...);
}
// Service lifecycle method execution complete
private void serviceDoneExecutingLocked(ServiceRecord r, ...) {
// 1. Cancel timeout message
mAm.mHandler.removeMessages(SERVICE_TIMEOUT_MSG, r);
// 2. Update Service state
r.executeNesting--;
if (r.executeNesting <= 0) {
r.executing = false;
}
}
Monitored Lifecycle Methods:
-
onCreate(): Service creation -
onStartCommand(): Service start -
onBind(): Service binding -
onUnbind(): Service unbinding -
onDestroy(): Service destruction
4.2 Special Handling of Foreground Services
// Foreground Service timeout is shorter
static final int SERVICE_TIMEOUT = 20 * 1000; // 20 seconds
static final int SERVICE_BACKGROUND_TIMEOUT = 200 * 1000; // 200 seconds
// Correct way to start foreground Service
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
// Call startForeground() as soon as possible in onCreate()
// Otherwise may trigger ANR
Notification notification = createNotification();
startForeground(NOTIFICATION_ID, notification);
// Then do other initialization
initializeResources();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// If there are time-consuming operations, put them in background thread
new Thread(() -> {
doHeavyWork();
}).start();
return START_STICKY;
}
}
Why is foreground Service timeout shorter?
Because foreground Services are usually related to user's current operations (like music playback, navigation), they must start quickly, otherwise user experience is affected.
4.3 IntentService Timeout Issues
IntentService is a special Service that comes with its own worker thread:
// IntentService internal implementation
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
@Override
public void onCreate() {
super.onCreate();
// Create worker thread
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Send task to worker thread
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
return START_REDELIVER_INTENT;
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Call onHandleIntent() on worker thread
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
// Subclasses implement this method
protected abstract void onHandleIntent(Intent intent);
}
Important Details:
-
onStartCommand()is on main thread but returns quickly -
onHandleIntent()is on worker thread, can be time-consuming - Won't trigger Service ANR because main thread methods are fast
But watch out for onCreate()!
// ❌ Wrong: Initialize time-consuming resources in onCreate()
public class MyIntentService extends IntentService {
@Override
public void onCreate() {
super.onCreate();
// This is still on main thread!
loadLargeDatabase(); // May trigger ANR
}
@Override
protected void onHandleIntent(Intent intent) {
// This is on worker thread, can be time-consuming
processData();
}
}
// ✅ Correct: Put time-consuming initialization in worker thread too
public class MyIntentService extends IntentService {
@Override
public void onCreate() {
super.onCreate();
// Only do lightweight initialization
}
@Override
protected void onHandleIntent(Intent intent) {
// Initialize on first call
if (!initialized) {
loadLargeDatabase();
initialized = true;
}
processData();
}
}
V. ANR Information Collection and Reporting
When InputDispatcher or AMS detects an ANR, it triggers a series of information collection operations. This information is crucial for analyzing ANR.
5.1 ANR Trigger and Information Collection Flow
5.2 Implementation of appNotResponding()
// frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java
public void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, String annotation) {
// 1. Prevent duplicate ANR
synchronized (mService) {
if (mNotResponding) {
Slog.w(TAG, "App already notresponding: " + this);
return;
}
mNotResponding = true;
// 2. Record ANR information
mNotRespondingReport = generateErrorReport(
annotation, null, null, null, null, null);
}
// 3. Collect CPU usage
updateCpuStatsNow();
// 4. Output to logcat
Slog.e(TAG, "ANR in " + processName + " (" + activityShortComponentName + ")");
Slog.e(TAG, "PID: " + pid);
Slog.e(TAG, "Reason: " + annotation);
Slog.e(TAG, "Load: " + loadAverage);
Slog.e(TAG, "CPU usage from " + cpuUsageStart + "ms to " + cpuUsageEnd + "ms later:");
Slog.e(TAG, cpuUsageString);
// 5. Dump all related thread stacks
ArrayList<Integer> firstPids = new ArrayList<>(5);
firstPids.add(pid); // ANR process
firstPids.add(Process.myPid()); // System Server
File tracesFile = ActivityManagerService.dumpStackTraces(
firstPids, processCpuTracker, ...);
// 6. Write to DropBox
mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
parentShortComponentName, parentProcess, annotation,
cpuUsageString, tracesFile, null);
// 7. Pop up ANR Dialog
Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);
mService.mUiHandler.sendMessage(msg);
}
5.3 Generation of traces.txt
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public static File dumpStackTraces(ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, ...) {
// 1. Create traces file
File tracesFile = new File("/data/anr/traces.txt");
// 2. Dump first batch of processes (ANR process and System Server)
for (int pid : firstPids) {
dumpJavaTracesTombstoned(pid, tracesFile, timeout);
}
// 3. Dump Native processes (if ANR process has native threads)
if (DEBUG_ANR) {
dumpNativeBacktraceToFile(pid, tracesFile);
}
// 4. Dump other important processes
ArrayList<Integer> extraPids = new ArrayList<>();
// 4.1 Last 3 recently used applications
if (lastPids != null) {
for (int i = 0; i < lastPids.size() && i < 3; i++) {
extraPids.add(lastPids.get(i));
}
}
// 4.2 All persistent processes
synchronized (mPidsSelfLocked) {
for (int i = 0; i < mPidsSelfLocked.size(); i++) {
ProcessRecord r = mPidsSelfLocked.valueAt(i);
if (r.persistent) {
extraPids.add(r.pid);
}
}
}
// 4.3 Dump these processes
for (int pid : extraPids) {
dumpJavaTracesTombstoned(pid, tracesFile, timeout);
}
return tracesFile;
}
Structure of traces.txt:
----- pid 12345 at 2024-12-25 10:30:15 -----
Cmd line: com.example.app
Build fingerprint: 'google/sdk_gphone_x86_64/generic_x86_64:11/RSR1.201013.001/6903271:user/release-keys'
ABI: 'x86_64'
...
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x74e04dd8
| sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7b5a9f49a8
| state=S schedstat=( 15678923456 8765432109 1234 ) utm=1500 stm=67
at com.example.app.MainActivity.onClick(MainActivity.java:150)
- waiting to lock <0x0a1b2c3d> (a java.lang.Object) held by thread 15
at android.view.View.performClick(View.java:7125)
...
"Thread-15" prio=5 tid=15 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x13579bdf
| sysTid=12360 nice=0 cgrp=default sched=0/0 handle=0x7a4e028800
at android.os.BinderProxy.transactNative(Native Method)
- locked <0x0a1b2c3d> (a java.lang.Object)
at android.os.BinderProxy.transact(Binder.java:1129)
...
"Binder:12345_2" prio=5 tid=12 Native
...
----- end 12345 -----
Key Information Interpretation:
- Cmd line: Process name
- tid: Thread ID
- State: Blocked/Native/Waiting/Runnable
- Stack: Java method call chain
-
Lock information:
waiting to lock <address>orlocked <address>
5.4 DropBox: ANR's "Black Box"
DropBox is Android's system logging service, specifically for storing system exception events:
// Write to DropBox
void addErrorToDropBox(String eventType, ProcessRecord process,
String processName, String activityShortComponentName, ...) {
// 1. Build DropBox entry
StringBuilder sb = new StringBuilder(1024);
sb.append("Process: ").append(processName).append("\n");
sb.append("PID: ").append(process.pid).append("\n");
sb.append("Reason: ").append(annotation).append("\n");
sb.append("Load: ").append(loadAverage).append("\n");
sb.append(cpuUsageString);
// 2. Add traces content
if (tracesFile != null) {
sb.append("\n\n*** TRACES ***\n");
sb.append(FileUtils.readTextFile(tracesFile, DROPBOX_MAX_SIZE, "..."));
}
// 3. Write to DropBox
DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
dbox.addText(eventType, sb.toString());
}
View DropBox Content:
# List all ANR records
adb shell dumpsys dropbox --print anr
# Export recent ANR
adb shell dumpsys dropbox --print anr > anr_dropbox.txt
# Clear DropBox
adb shell dumpsys dropbox --wipe
DropBox vs traces.txt:
| Feature | DropBox | traces.txt |
|---------|---------|------------|
| Storage Location | /data/system/dropbox | /data/anr/ |
| Storage Format | Structured entries | Plain text |
| Capacity Limit | Limited (rolling deletion) | Single file |
| Information Included | ANR + CPU + Memory | Thread stacks only |
| View Method | dumpsys dropbox | adb pull |
VI. ANR Dialog Display
After ANR detection and information collection complete, a dialog is finally popped up for users to decide how to handle it.
6.1 Types of ANR Dialog
Android has two types of ANR Dialog:
1. Application ANR Dialog (regular applications):
─────────────────────────────
│ Application Not Responding │
│ │
│ "XX" is not responding. │
│ │
│ Do you want to close it? │
│ │
│ [Wait] [Close App] │
─────────────────────────────
2. System ANR Dialog (system applications or System Server):
─────────────────────────────
│ System Not Responding │
│ │
│ "System UI" is not │
│ responding. │
│ │
│ [Wait] [OK] │
─────────────────────────────
6.2 Dialog Implementation
// frameworks/base/services/core/java/com/android/server/am/AppNotRespondingDialog.java
final class AppNotRespondingDialog extends BaseErrorDialog {
private final ActivityManagerService mService;
private final ProcessRecord mProc;
public AppNotRespondingDialog(ActivityManagerService service,
Context context, Data data) {
super(context);
mService = service;
mProc = data.proc;
// 1. Set Dialog title
setTitle(context.getText(com.android.internal.R.string.anr_title));
// 2. Set message content
StringBuilder msg = new StringBuilder();
msg.append(context.getString(com.android.internal.R.string.anr_activity_application,
data.aInfo.loadLabel(context.getPackageManager()).toString(),
data.proc.info.processName));
setMessage(msg);
// 3. Set buttons
setCancelable(false);
// "Wait" button
setButton(DialogInterface.BUTTON_POSITIVE,
context.getText(com.android.internal.R.string.wait),
mHandler.obtainMessage(WAIT));
// "Close App" button
setButton(DialogInterface.BUTTON_NEGATIVE,
context.getText(com.android.internal.R.string.force_close),
mHandler.obtainMessage(FORCE_CLOSE));
// "Report" button (optional)
if (data.proc.errorReportReceiver != null) {
setButton(DialogInterface.BUTTON_NEUTRAL,
context.getText(com.android.internal.R.string.report),
mHandler.obtainMessage(APP_INFO));
}
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case WAIT:
// User chooses to wait, close Dialog
mService.mUiHandler.sendEmptyMessage(DISMISS_DIALOG);
break;
case FORCE_CLOSE:
// User chooses to close, kill application
mService.killAppAtUsersRequest(mProc, AppNotRespondingDialog.this);
break;
case APP_INFO:
// User chooses to report, open app info page
mService.mUiHandler.obtainMessage(SHOW_APP_ERROR_REPORT, mProc)
.sendToTarget();
break;
}
}
};
}
6.3 Follow-up Handling of User Actions
User Chooses "Wait":
// Do nothing, just close Dialog
// ANR process continues running
// If still unresponsive, may trigger ANR again
User Chooses "Close App":
void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
synchronized (this) {
// 1. Mark as user-initiated close
app.crashing = false;
app.crashingReport = null;
app.notResponding = false;
app.notRespondingReport = null;
// 2. Kill process
Process.killProcess(app.pid);
// 3. Clean up process record
handleAppDiedLocked(app, false, true);
}
}
User Chooses "Report" (some systems):
// Open Feedback or BugReport application
Intent intent = new Intent("android.intent.action.APP_ERROR");
intent.putExtra("error_type", "anr");
intent.putExtra("package_name", app.info.packageName);
intent.putExtra("process_name", app.processName);
intent.putExtra("pid", app.pid);
context.startActivity(intent);
VII. ANR Analysis in Practice
We've covered a lot of theory. Let's see how to analyze ANR in practice through several real cases.
7.1 Case 1: ANR Caused by Main Thread Waiting for Lock
Phenomenon: After user clicks button, app freezes for 5 seconds, ANR pops up
logcat Log:
12-25 10:30:15.123 1234 1250 E InputDispatcher: Application is not responding: AppWindowToken{e8d3a72 token=Token{b6c7d3e ActivityRecord{a9f8e1d u0 com.example/.MainActivity}}}
12-25 10:30:15.234 1234 1250 I ActivityManager: ANR in com.example.app (com.example.app/.MainActivity)
12-25 10:30:15.234 1234 1250 I ActivityManager: PID: 12345
12-25 10:30:15.234 1234 1250 I ActivityManager: Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 1.)
traces.txt Analysis:
----- pid 12345 at 2024-12-25 10:30:15 -----
Cmd line: com.example.app
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x74e04dd8
| sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7b5a9f49a8
| state=S schedstat=( 15678923456 8765432109 1234 ) utm=1500 stm=67
at com.example.app.DataManager.getData(DataManager.java:45)
- waiting to lock <0x0a1b2c3d> (a java.lang.Object) held by thread 15
at com.example.app.MainActivity.onClick(MainActivity.java:100)
at android.view.View.performClick(View.java:7125)
at android.view.View.performClickInternal(View.java:7102)
at android.view.View.access$3500(View.java:801)
at android.view.View$PerformClick.run(View.java:27851)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
...
"Thread-15" prio=5 tid=15 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x13579bdf
| sysTid=12360 nice=0 cgrp=default sched=0/0 handle=0x7a4e028800
| state=S schedstat=( 9876543210 5432109876 123 ) utm=900 stm=87
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:1129)
- locked <0x0a1b2c3d> (a java.lang.Object)
at android.content.IContentProvider$Stub$Proxy.query(IContentProvider.java:xxx)
at android.content.ContentResolver.query(ContentResolver.java:xxx)
at com.example.app.DataManager.loadFromDatabase(DataManager.java:80)
at com.example.app.DataManager$1.run(DataManager.java:60)
...
Analysis Steps:
-
Check main thread state:
Blocked- main thread is blocked -
Check blocking location:
DataManager.getData()line 45, waiting for a lock<0x0a1b2c3d> -
Find lock-holding thread: Thread-15 holds this lock (
locked <0x0a1b2c3d>) -
See what lock-holding thread is doing: Making Binder call
transactNative(), querying ContentProvider - Root cause: Thread 15 holds lock while doing time-consuming operation, main thread can't get lock
Solution:
// ❌ Original code
public class DataManager {
private final Object mLock = new Object();
public void onClick() {
// Called from main thread
synchronized (mLock) {
Data data = getData();
updateUI(data);
}
}
private Data getData() {
// Still holding lock!
return loadFromDatabase(); // Binder call, very slow
}
}
// ✅ After fix
public class DataManager {
private final Object mLock = new Object();
public void onClick() {
// Async load
new Thread(() -> {
Data data;
synchronized (mLock) {
data = getData();
}
// Update UI on main thread
runOnUiThread(() -> updateUI(data));
}).start();
}
}
7.2 Case 2: ANR Caused by Binder Call Timeout
Phenomenon: App in background, suddenly receives Broadcast ANR
logcat:
12-25 11:00:00.123 1234 1250 W BroadcastQueue: Timeout of broadcast BroadcastRecord{a1b2c3d u0 android.intent.action.BATTERY_CHANGED} - receiver=android.content.IIntentReceiver$Stub$Proxy@e4f5g6h
12-25 11:00:00.234 1234 1250 I ActivityManager: ANR in com.example.app (com.example.app/.MyReceiver)
12-25 11:00:00.234 1234 1250 I ActivityManager: Reason: Broadcast of Intent { act=android.intent.action.BATTERY_CHANGED }
traces.txt:
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x74e04dd8
| sysTid=23456 nice=0 cgrp=default sched=0/0 handle=0x7b5a9f49a8
| state=S schedstat=( 2345678901 1234567890 234 ) utm=200 stm=34
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:1129)
at android.app.IActivityManager$Stub$Proxy.registerContentObserver(IActivityManager.java:xxxx)
at android.content.ContentResolver.registerContentObserver(ContentResolver.java:xxxx)
at com.example.app.MyReceiver.onReceive(MyReceiver.java:30)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:xxxx)
...
Analysis:
- ANR Type: Broadcast ANR
-
Main thread state:
Native- executing Native method -
Stuck where:
BinderProxy.transactNative()- Binder call - Doing what: Registering ContentObserver
- Why slow: Possibly System Server busy or Binder resources exhausted
Root Cause: Synchronous Binder call in BroadcastReceiver, while System Server responds slowly
Solution:
// ❌ Original code
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Synchronously register ContentObserver, may be slow
context.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS),
false,
new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
// ...
}
});
}
}
// ✅ Fix Option 1: Use goAsync()
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final PendingResult result = goAsync();
new Thread(() -> {
try {
context.getContentResolver().registerContentObserver(...);
} finally {
result.finish();
}
}).start();
}
}
// ✅ Fix Option 2: Delayed registration
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Start Service for delayed registration
Intent serviceIntent = new Intent(context, RegistrationService.class);
context.startService(serviceIntent);
}
}
7.3 Case 3: Database Operations on Main Thread
traces.txt:
"main" prio=5 tid=1 Native
| sysTid=34567 nice=-10
at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:609)
at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:820)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:144)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:134)
at com.example.app.DatabaseHelper.queryAllRecords(DatabaseHelper.java:150)
at com.example.app.MainActivity.onCreate(MainActivity.java:50)
...
Analysis:
- Main thread executing SQLite query
-
getCount()loads all data into Cursor - Very slow with large datasets
Solution:
// ❌ Original code
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Query database on main thread
Cursor cursor = mDbHelper.queryAllRecords();
int count = cursor.getCount(); // Triggers actual query
processData(cursor);
}
// ✅ Using AsyncTask (deprecated, example only)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new QueryTask().execute();
}
private class QueryTask extends AsyncTask<Void, Void, Cursor> {
@Override
protected Cursor doInBackground(Void... voids) {
return mDbHelper.queryAllRecords();
}
@Override
protected void onPostExecute(Cursor cursor) {
processData(cursor);
}
}
// ✅ Using Kotlin Coroutines (recommended)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
val cursor = withContext(Dispatchers.IO) {
dbHelper.queryAllRecords()
}
processData(cursor)
}
}
// ✅ Using Room + LiveData (best practice)
@Dao
interface RecordDao {
@Query("SELECT * FROM records")
fun getAllRecords(): LiveData<List<Record>>
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.records.observe(this) { records ->
processData(records)
}
}
VIII. ANR Prevention and Monitoring
Prevention is better than cure. Let's see how to avoid ANR during development and how to build an effective monitoring system.
8.1 Prevention Measures During Development
1. Use StrictMode to Detect Main Thread Violations
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads() // Detect disk reads
.detectDiskWrites() // Detect disk writes
.detectNetwork() // Detect network operations
.detectCustomSlowCalls() // Detect custom slow calls
.penaltyLog() // Output to logcat
.penaltyDeath() // Direct crash (development phase)
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() // Detect database leaks
.detectLeakedClosableObjects() // Detect unclosed objects
.penaltyLog()
.build());
}
}
}
2. Use BlockCanary to Detect Lag
// Initialize BlockCanary
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
BlockCanary.install(this, new AppBlockCanaryContext()).start();
}
}
// Custom configuration
public class AppBlockCanaryContext extends BlockCanaryContext {
@Override
public int provideBlockThreshold() {
return 500; // Over 500ms considered lag
}
@Override
public boolean displayNotification() {
return BuildConfig.DEBUG; // Show notification only in debug environment
}
}
3. Use TraceView for Performance Analysis
// Add trace before and after suspicious code
Debug.startMethodTracing("my_trace");
// Suspicious code
suspiciousMethod();
Debug.stopMethodTracing();
// Export trace file
adb pull /sdcard/Android/data/<package>/files/my_trace.trace .
// Analyze using Android Studio's Profiler
8.2 Online Monitoring System
1. ANR Capture and Reporting
public class ANRWatchDog extends Thread {
private final int mTimeout;
private final Handler mHandler = new Handler(Looper.getMainLooper());
public ANRWatchDog(int timeout) {
super("ANR-WatchDog");
this.mTimeout = timeout;
}
@Override
public void run() {
while (!isInterrupted()) {
// 1. Record current time
final long startTime = System.currentTimeMillis();
final AtomicLong executionTime = new AtomicLong();
// 2. Send task to main thread
mHandler.post(() -> {
executionTime.set(System.currentTimeMillis());
});
// 3. Wait for a period
try {
Thread.sleep(mTimeout);
} catch (InterruptedException e) {
return;
}
// 4. Check if task executed
long execTime = executionTime.get();
if (execTime == 0 || System.currentTimeMillis() - execTime > mTimeout) {
// ANR detected!
onAnrDetected();
}
}
}
private void onAnrDetected() {
// 1. Collect stack trace information
Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
// 2. Report to server
ANRReporter.report(stackTraces);
}
}
// Start monitoring
new ANRWatchDog(5000).start();
2. Use Third-Party Platforms like Bugly
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Initialize Bugly
CrashReport.initCrashReport(getApplicationContext(), "YOUR_APP_ID", BuildConfig.DEBUG);
// Configure ANR monitoring
CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(getApplicationContext());
strategy.setAppReportDelay(10000); // Delayed reporting
CrashReport.initCrashReport(getApplicationContext(), "YOUR_APP_ID", BuildConfig.DEBUG, strategy);
}
}
3. Monitor Key Metrics
public class PerformanceMonitor {
// Monitor main thread message processing time
public void monitorMainLooper() {
Looper.getMainLooper().setMessageLogging(new Printer() {
private static final String START = ">>>>> Dispatching";
private static final String END = "<<<<< Finished";
@Override
public void println(String msg) {
if (msg.startsWith(START)) {
// Message processing started
mStartTime = System.currentTimeMillis();
} else if (msg.startsWith(END)) {
// Message processing finished
long duration = System.currentTimeMillis() - mStartTime;
if (duration > 100) { // Over 100ms
// Log slow message
logSlowMessage(msg, duration);
}
}
}
});
}
// Monitor method execution time
@Around("execution(* com.example.app..*(..))")
public Object measureMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
if (duration > 50) { // Over 50ms
String method = joinPoint.getSignature().toShortString();
Log.w("Performance", method + " took " + duration + "ms");
}
return result;
}
}
8.3 Best Practices for ANR Optimization
1. Golden Rules for Main Thread
Main thread only does three things:
1. Update UI
2. Dispatch events
3. Launch async tasks
Absolutely must not do:
1. Disk I/O (read/write files, database)
2. Network I/O (HTTP requests, Socket communication)
3. Complex computation (big data processing, encryption/decryption)
4. Synchronous Binder calls (ContentProvider, System Service)
5. Wait for locks (synchronized, Lock)
2. Use Async Mechanisms
| Scenario | Recommended Solution | Example |
|---|---|---|
| Simple async tasks |
HandlerThread + Handler
|
Background data processing |
| Lifecycle-aware |
ViewModel + LiveData
|
UI data loading |
| Complex coroutines | Kotlin Coroutines
|
Network request + database |
| Scheduled tasks | WorkManager |
Periodic sync |
| File I/O | Executors.newSingleThreadExecutor() |
Log writing |
| Database |
Room + LiveData
|
Local data access |
3. Optimize Binder Calls
// ❌ Synchronous ContentProvider call on main thread
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
// ✅ Use ContentProviderOperation for batch operations
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
ops.add(ContentProviderOperation.newInsert(uri).withValues(values1).build());
ops.add(ContentProviderOperation.newInsert(uri).withValues(values2).build());
getContentResolver().applyBatch(AUTHORITY, ops);
// ✅ Use AsyncQueryHandler for async queries
new AsyncQueryHandler(getContentResolver()) {
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
processData(cursor);
}
}.startQuery(0, null, uri, null, null, null, null);
4. Reduce Lock Contention
// ❌ Coarse-grained lock
public class DataCache {
private final Map<String, Data> mCache = new HashMap<>();
public synchronized Data get(String key) {
Data data = mCache.get(key);
if (data == null) {
data = loadFromDisk(key); // Time-consuming operation while holding lock!
mCache.put(key, data);
}
return data;
}
}
// ✅ Fine-grained lock + Double-Check
public class DataCache {
private final ConcurrentHashMap<String, Data> mCache = new ConcurrentHashMap<>();
private final Object mLock = new Object();
public Data get(String key) {
Data data = mCache.get(key);
if (data == null) {
synchronized (mLock) {
data = mCache.get(key);
if (data == null) {
data = loadFromDisk(key); // Only hold lock when necessary
mCache.put(key, data);
}
}
}
return data;
}
}
// ✅ Use Lock + tryLock to avoid dead wait
public class DataCache {
private final ReentrantLock mLock = new ReentrantLock();
public Data get(String key) {
if (mLock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
return loadData(key);
} finally {
mLock.unlock();
}
} else {
// Failed to acquire lock, return default value or async load
return getDefaultValue();
}
}
}
IX. Summary and Outlook
Let's review the full picture of the ANR mechanism:
9.1 Core Points Review
The 4 Types of ANR:
- Input ANR: 5 seconds no response, detected by InputDispatcher
- Broadcast ANR: 10s/60s, detected by ActivityManagerService
- Service ANR: 20s/200s, detected by ActivityManagerService
- ContentProvider ANR: 10 seconds, detected by ActivityManagerService
ANR Detection Mechanism:
- Input: Record timestamp when event enters queue, periodically check waitQueue
- Broadcast/Service: Send delayed message, timeout triggers Handler callback
ANR Processing Flow:
Detect timeout → Call appNotResponding() → Collect CPU/memory info
→ Dump thread stacks → Generate traces.txt → Write to DropBox → Pop Dialog
Key Information in traces.txt:
- Thread state: Blocked/Waiting/Native/Runnable
- Stack information: Method call chain
- Lock information: waiting to lock / locked
9.2 ANR Optimization Pyramid
9.3 Practical Recommendations
-
Development Phase:
- Enable StrictMode
- Use BlockCanary
- Code Review focus on main thread operations
-
Testing Phase:
- Stress testing
- Monkey testing
- Performance testing (TraceView, Systrace)
-
Production Phase:
- Integrate monitoring platforms like Bugly
- Regularly analyze ANR Top charts
- Establish ANR rapid response mechanism
9.4 Preview of Upcoming Articles
ANR mechanism is just one aspect of stability. In subsequent articles, we will continue to dive deeper:
| Article | Topic | Core Content |
|---|---|---|
| Article 3 | ANR Troubleshooting Practice | Deep interpretation of traces.txt, Systrace analysis, common ANR cases |
| Article 4 | Exception Logging Mechanism | Crash handling, Tombstone analysis, signal mechanism |
| Article 5 | Deep Analysis of Native Crash | debuggerd, symbolization, coredump |
| Article 6 | Java Exception and JE Analysis Practice | Analysis practice of Java exceptions |
| Article 7 | Watchdog Mechanism | System watchdog, semi-deadlock detection, System Server protection |
References
- Android Official Documentation - ANR
- AOSP Source Code - InputDispatcher.cpp
- AOSP Source Code - ActivityManagerService.java
- AOSP Source Code - BroadcastQueue.java
- Android Performance Patterns
Previous Article: Article 1 - Android Stability Basics: System Architecture and Key Mechanisms
Next Article: Article 3 - ANR Troubleshooting Practice: Log Analysis and Tool Practice
Back to Series Index: Android Stability & Performance Deep Understanding Series Introduction
Author Bio: Years of Android system development experience, specializing in system stability and performance optimization. Welcome to follow this series and explore the wonderful world of Android systems together!




Top comments (0)