引言:那个让人又爱又恨的ANR Dialog
还记得第一次看到ANR Dialog吗?
"应用XX无响应,是否关闭?"
作为用户,这个对话框让人抓狂------应用卡住了,什么都做不了。但作为开发者,这个对话框却是救命稻草------至少它告诉你出问题了,还给你留下了traces.txt这个"作案现场"。
我曾经遇到过一个让人头疼的ANR:用户在设置界面切换WiFi时,整个系统界面就卡死了。看logcat,发现主线程在等一个锁;再看traces.txt,发现持锁的线程正在进行Binder调用;继续追踪,原来是ContentObserver注册过多导致的Binder资源耗尽...这就像一场侦探游戏,而ANR Dialog是案件的起点。
本文将带你深入ANR机制的内部,从Input事件分发到超时检测,从traces信息收集到Dialog弹出,完整剖析ANR的全生命周期。读完本文,你将能够:
- 理解ANR的4种类型及其超时时间
- 掌握InputDispatcher如何检测Input ANR
- 了解ActivityManagerService如何处理ANR
- 学会分析traces.txt和找到根因
- 构建ANR监控和预防机制
准备好了吗?让我们揭开ANR机制的神秘面纱!
一、ANR基础概念
1.1 什么是ANR?
ANR (Application Not Responding) 是Android系统的一种保护机制。当应用在规定时间内没有响应用户操作或系统事件时,系统会认为应用"死了",弹出ANR对话框让用户选择是继续等待还是强制关闭。
打个比方:ANR就像餐厅的"上菜超时提醒"。你点了一道菜,如果厨房5分钟还没上菜,服务员就会过来询问"要不要继续等,还是换一道菜"。这避免了你无限期地等下去。
ANR的核心作用:
- 保护用户体验: 避免应用长时间无响应
- 及时发现问题: 通过traces.txt记录"作案现场"
- 系统自我保护: 防止单个应用拖垮整个系统
1.2 ANR的4种类型
Android系统对不同类型的事件设置了不同的超时时间:
| ANR类型 | 超时时间 | 触发场景 | 检测位置 |
|---|---|---|---|
| Input ANR | 5秒 | 用户触摸/按键事件未被处理 | InputDispatcher |
| Broadcast ANR | 前台10秒 / 后台60秒 | BroadcastReceiver的onReceive()未执行完 | ActivityManagerService |
| Service ANR | 前台20秒 / 后台200秒 | Service的生命周期方法未执行完 | ActivityManagerService |
| ContentProvider ANR | 10秒 | ContentProvider发布超时 | ActivityManagerService |
为什么超时时间不一样?
这是基于用户体验和系统响应性的权衡:
- Input ANR最短(5秒): 用户操作必须得到快速反馈,否则会认为设备坏了
- Broadcast ANR中等(10秒/60秒): 广播接收器本应该快速执行,但允许一定的处理时间
- Service ANR较长(20秒/200秒): 后台服务可以处理耗时操作,给予更多宽容
- 后台进程超时更长: 后台操作不影响前台体验,可以等待更久
1.3 ANR vs Freeze vs Watchdog
经常有人混淆这几个概念,让我们对比一下:
| 特性 | ANR | Freeze | Watchdog |
|---|---|---|---|
| 检测对象 | 应用进程 | 应用进程 | System Server |
| 检测维度 | 特定事件响应 | 整体响应性 | 核心线程健康 |
| 超时时间 | 5-200秒 | 通常小于秒 | 30-60秒 |
| 后果 | 弹出ANR Dialog | 可能触发ANR | 系统重启 |
| 作用域 | 单个应用 | 单个应用 | 整个系统 |
形象的比喻:
- ANR: 餐厅服务员检查你点的菜是否上齐了
- Freeze: 餐厅经理监控服务员是否在工作
- Watchdog: 消防队定时巡查餐厅的消防系统是否正常
二、Input ANR的完整流程
Input ANR是最常见也是最典型的ANR类型。让我们通过一个用户点击按钮的场景,完整走一遍ANR的检测流程。
2.1 整体流程概览

2.2 InputReader:事件的源头
InputReader运行在一个独立的线程中,负责从内核读取输入事件:
cpp
// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
// 1. 从/dev/input/eventX读取原始事件
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
// 2. 处理每个事件
for (size_t i = 0; i < count; i++) {
const RawEvent& rawEvent = mEventBuffer[i];
// 3. 根据设备类型(触摸屏、键盘等)转换为Android事件
processEventsLocked(rawEvent);
}
// 4. 将处理好的事件交给InputDispatcher
mQueuedListener->flush();
}
关键点:
- InputReader是事件的"搬运工",只负责读取和初步处理
- 它不关心事件是否被应用处理,只管往前送
- 真正的超时检测发生在InputDispatcher
2.3 InputDispatcher:超时检测的核心
InputDispatcher是ANR检测的"心脏"。让我们看看它如何检测超时:
cpp
// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
// 分发事件的核心逻辑
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
// 1. 从队列中取出待分发的事件
if (mPendingEvent == nullptr) {
mPendingEvent = mInboundQueue.dequeueAtHead();
}
// 2. 找到事件的目标Window
std::vector<InputTarget> inputTargets;
int32_t injectionResult = findFocusedWindowTargetsLocked(
currentTime, mPendingEvent, inputTargets, nextWakeupTime);
// 3. 分发事件到目标Window
if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
dispatchEventLocked(currentTime, mPendingEvent, inputTargets);
}
// 4. 检查是否有连接超时
nsecs_t nextAnrCheck = checkUnresponsiveConnectionsLocked(currentTime);
if (nextAnrCheck < nextWakeupTime) {
nextWakeupTime = nextAnrCheck;
}
}
超时检测的关键逻辑:
cpp
// 检查连接是否响应
nsecs_t InputDispatcher::checkUnresponsiveConnectionsLocked(nsecs_t currentTime) {
nsecs_t nextAnrCheck = LONG_LONG_MAX;
for (const auto& [token, connection] : mConnectionsByToken) {
// 检查这个连接的等待队列
if (connection->waitQueue.empty()) {
continue;
}
// 获取队头事件
DispatchEntry* head = connection->waitQueue.head;
nsecs_t eventTime = head->eventEntry->eventTime;
// 计算等待时间
nsecs_t waitDuration = currentTime - eventTime;
// 如果超过5秒(5000ms),触发ANR
if (waitDuration > 5000ms) {
onAnrLocked(connection);
} else {
// 记录下次检查时间
nsecs_t timeoutTime = eventTime + 5000ms;
if (timeoutTime < nextAnrCheck) {
nextAnrCheck = timeoutTime;
}
}
}
return nextAnrCheck;
}
精妙的设计:
- 事件入队列时记录时间戳 : 每个事件分发时,都会记录
eventTime - 定时检查队头事件: InputDispatcher会定期检查waitQueue的队头
- 计算等待时长 :
waitDuration = currentTime - eventTime - 超时触发ANR : 如果等待超过5秒,调用
onAnrLocked()
为什么检查队头而不是队尾?
因为队头是最早发送的事件,如果队头都还没处理完,说明应用真的卡住了。这就像排队买票:如果队伍最前面的人还在办理,后面的人自然也走不了。
2.4 等待队列(waitQueue)的秘密
理解waitQueue是理解Input ANR的关键:
ini
waitQueue的生命周期:
1. 事件发送前
waitQueue: []
2. 事件发送给应用
dispatchEntry入队
waitQueue: [Event1]
记录Event1.eventTime = T0
3. 应用处理事件(正常情况)
应用调用finish()
waitQueue: [] ← Event1被移除
4. 应用卡住(异常情况)
T0 + 5秒后
waitQueue: [Event1] ← Event1还在!
InputDispatcher检测到超时
触发ANR
代码实现:
cpp
// 应用处理完事件后调用
void InputDispatcher::finishDispatchCycleLocked(
nsecs_t currentTime, sp<Connection> connection) {
// 从waitQueue移除已处理的事件
while (!connection->waitQueue.empty()) {
DispatchEntry* dispatchEntry = connection->waitQueue.head;
connection->waitQueue.dequeue(dispatchEntry);
// 释放资源
releaseDispatchEntry(dispatchEntry);
// 如果这是最后一个事件,重置超时检查
if (connection->waitQueue.empty()) {
connection->lastEventTime = LLONG_MIN;
}
}
}
2.5 ANR分析示例
让我们看一个真实的Input ANR场景的分析简化:
场景: 用户在列表界面快速滑动,突然应用无响应
logcat日志:
csharp
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片段:
ini
"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)
...
根因分析:
- 现象 : 主线程被阻塞,状态是
Blocked - 位置 : 卡在
RecyclerAdapter.onBindViewHolder() - 原因 : 主线程在等待一个锁
<0x0a1b2c3d>,这个锁被线程15持有 - 解决: 找到线程15在干什么,解开死锁
关键教训:
- 永远不要在主线程中等待锁
- RecyclerView的Adapter中不要做耗时操作
- 复杂的数据处理应该在后台线程完成
三、Broadcast ANR的检测机制
相比Input ANR的"被动检测"(等事件处理完),Broadcast ANR采用"主动检测"(主动设置超时)。
3.1 广播分发流程

3.2 超时定时器的实现
java
// frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
private void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) {
// 1. 设置超时时间
final long timeout = r.isForeground() ?
BROADCAST_FG_TIMEOUT : BROADCAST_BG_TIMEOUT;
// 2. 发送延时消息
mHandler.sendMessageDelayed(
mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, r),
timeout);
// 3. 调用receiver的onReceive()
app.thread.scheduleReceiver(...);
}
// Handler处理超时消息
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BROADCAST_TIMEOUT_MSG:
BroadcastRecord r = (BroadcastRecord) msg.obj;
// 触发ANR
broadcastTimeoutLocked(r, true);
break;
}
}
// 广播执行完毕后取消超时
private void performReceiveLocked(ProcessRecord app, ...) {
// 1. 执行完毕
r.receiver = null;
// 2. 取消超时消息
mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, r);
// 3. 继续处理下一个广播
processCurBroadcastLocked(r, app);
}
关键点:
- 使用Handler的延时消息实现超时检测
- 如果在超时前完成,
removeMessages()取消定时器 - 如果超时,Handler收到消息,触发ANR
3.3 前台 vs 后台广播的区别
java
// 超时时间定义
static final int BROADCAST_FG_TIMEOUT = 10 * 1000; // 10秒
static final int BROADCAST_BG_TIMEOUT = 60 * 1000; // 60秒
// 如何判断前台还是后台?
boolean isForeground() {
// 1. 广播发送时指定了FLAG_RECEIVER_FOREGROUND
if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0) {
return true;
}
// 2. 接收进程是前台进程
if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
return true;
}
// 3. 有序广播的当前接收者是前台
if (ordered && curReceiver.priority > 0) {
return true;
}
return false;
}
实战案例:
java
// 案例:一个耗时的BroadcastReceiver
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// ❌ 错误:在主线程做耗时操作
for (int i = 0; i < 1000000; i++) {
// 大量计算
}
// 如果超过10秒,触发ANR
}
}
// ✅ 正确做法:使用goAsync()
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 1. 获取PendingResult
final PendingResult result = goAsync();
// 2. 在子线程处理
new Thread(() -> {
try {
// 耗时操作
doHeavyWork();
} finally {
// 3. 完成后通知系统
result.finish();
}
}).start();
}
}
goAsync()的作用:
- 告诉系统"我需要异步处理,别急着超时"
- 系统会延长超时时间(但仍有上限)
- 必须在完成后调用
result.finish()
四、Service ANR的检测机制
Service ANR的检测逻辑与Broadcast类似,但更复杂,因为Service有多个生命周期方法需要监控。
4.1 Service生命周期的超时监控
java
// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
// Service启动超时检测
private final void bringUpServiceLocked(ServiceRecord r, ...) {
// 1. 设置超时时间
final long timeout = r.isForeground() ?
SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT;
// 2. 发送超时消息
mAm.mHandler.sendMessageDelayed(
mAm.mHandler.obtainMessage(SERVICE_TIMEOUT_MSG, r),
timeout);
// 3. 启动Service
app.thread.scheduleCreateService(r, ...);
}
// Service生命周期方法执行完毕
private void serviceDoneExecutingLocked(ServiceRecord r, ...) {
// 1. 取消超时消息
mAm.mHandler.removeMessages(SERVICE_TIMEOUT_MSG, r);
// 2. 更新Service状态
r.executeNesting--;
if (r.executeNesting <= 0) {
r.executing = false;
}
}
监控的生命周期方法:
onCreate(): Service创建onStartCommand(): Service启动onBind(): Service绑定onUnbind(): Service解绑onDestroy(): Service销毁
4.2 前台Service的特殊处理
java
// 前台Service的超时时间更短
static final int SERVICE_TIMEOUT = 20 * 1000; // 20秒
static final int SERVICE_BACKGROUND_TIMEOUT = 200 * 1000; // 200秒
// 启动前台Service的正确姿势
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
// 在onCreate()中尽快调用startForeground()
// 否则可能触发ANR
Notification notification = createNotification();
startForeground(NOTIFICATION_ID, notification);
// 然后再做其他初始化
initializeResources();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 如果有耗时操作,放到子线程
new Thread(() -> {
doHeavyWork();
}).start();
return START_STICKY;
}
}
为什么前台Service超时时间短?
因为前台Service通常与用户当前的操作相关(如音乐播放、导航),必须快速启动,否则影响用户体验。
4.3 IntentService的超时问题
IntentService是一个特殊的Service,它自带工作线程:
java
// IntentService的内部实现
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
@Override
public void onCreate() {
super.onCreate();
// 创建工作线程
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) {
// 将任务发送到工作线程
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) {
// 在工作线程调用onHandleIntent()
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
// 子类实现这个方法
protected abstract void onHandleIntent(Intent intent);
}
重要细节:
onStartCommand()在主线程,但很快返回onHandleIntent()在工作线程,可以耗时- 不会触发Service ANR,因为主线程方法都很快
但是要注意onCreate()!
java
// ❌ 错误:在onCreate()中初始化耗时资源
public class MyIntentService extends IntentService {
@Override
public void onCreate() {
super.onCreate();
// 这里仍然在主线程!
loadLargeDatabase(); // 可能触发ANR
}
@Override
protected void onHandleIntent(Intent intent) {
// 这里在工作线程,可以耗时
processData();
}
}
// ✅ 正确:耗时初始化也放到工作线程
public class MyIntentService extends IntentService {
@Override
public void onCreate() {
super.onCreate();
// 只做轻量级初始化
}
@Override
protected void onHandleIntent(Intent intent) {
// 第一次调用时再初始化
if (!initialized) {
loadLargeDatabase();
initialized = true;
}
processData();
}
}
五、ANR信息的收集与上报
当InputDispatcher或AMS检测到ANR后,会触发一系列的信息收集操作。这些信息对于分析ANR至关重要。
5.1 ANR触发和信息搜集流程

5.2 appNotResponding()的实现
java
// 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. 防止重复ANR
synchronized (mService) {
if (mNotResponding) {
Slog.w(TAG, "App already notresponding: " + this);
return;
}
mNotResponding = true;
// 2. 记录ANR信息
mNotRespondingReport = generateErrorReport(
annotation, null, null, null, null, null);
}
// 3. 收集CPU使用情况
updateCpuStatsNow();
// 4. 输出到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所有相关线程堆栈
ArrayList<Integer> firstPids = new ArrayList<>(5);
firstPids.add(pid); // ANR进程
firstPids.add(Process.myPid()); // System Server
File tracesFile = ActivityManagerService.dumpStackTraces(
firstPids, processCpuTracker, ...);
// 6. 写入DropBox
mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
parentShortComponentName, parentProcess, annotation,
cpuUsageString, tracesFile, null);
// 7. 弹出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 traces.txt的生成
java
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public static File dumpStackTraces(ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, ...) {
// 1. 创建traces文件
File tracesFile = new File("/data/anr/traces.txt");
// 2. dump第一批进程(ANR进程和System Server)
for (int pid : firstPids) {
dumpJavaTracesTombstoned(pid, tracesFile, timeout);
}
// 3. dump Native进程(如果ANR进程有native线程)
if (DEBUG_ANR) {
dumpNativeBacktraceToFile(pid, tracesFile);
}
// 4. dump其他重要进程
ArrayList<Integer> extraPids = new ArrayList<>();
// 4.1 最近使用的3个应用
if (lastPids != null) {
for (int i = 0; i < lastPids.size() && i < 3; i++) {
extraPids.add(lastPids.get(i));
}
}
// 4.2 所有persistent进程
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这些进程
for (int pid : extraPids) {
dumpJavaTracesTombstoned(pid, tracesFile, timeout);
}
return tracesFile;
}
traces.txt的结构:
ini
----- 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 -----
关键信息解读:
- Cmd line: 进程名称
- tid: 线程ID
- 状态: Blocked/Native/Waiting/Runnable
- 堆栈: Java方法调用链
- 锁信息 :
waiting to lock <地址>或locked <地址>
5.4 DropBox:ANR的"黑匣子"
DropBox是Android的系统日志服务,专门用于存储系统异常事件:
java
// 写入DropBox
void addErrorToDropBox(String eventType, ProcessRecord process,
String processName, String activityShortComponentName, ...) {
// 1. 构建DropBox条目
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. 添加traces内容
if (tracesFile != null) {
sb.append("\n\n*** TRACES ***\n");
sb.append(FileUtils.readTextFile(tracesFile, DROPBOX_MAX_SIZE, "..."));
}
// 3. 写入DropBox
DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
dbox.addText(eventType, sb.toString());
}
查看DropBox内容:
bash
# 列出所有ANR记录
adb shell dumpsys dropbox --print anr
# 导出最近的ANR
adb shell dumpsys dropbox --print anr > anr_dropbox.txt
# 清空DropBox
adb shell dumpsys dropbox --wipe
DropBox vs traces.txt:
| 特性 | DropBox | traces.txt |
|---|---|---|
| 存储位置 | /data/system/dropbox |
/data/anr/ |
| 存储格式 | 结构化条目 | 纯文本 |
| 容量限制 | 有限(会滚动删除) | 单个文件 |
| 包含信息 | ANR+CPU+内存 | 仅线程堆栈 |
| 查看方式 | dumpsys dropbox | adb pull |
六、ANR Dialog的展示
ANR检测和信息收集完成后,最终会弹出一个对话框让用户决定如何处理。
6.1 ANR Dialog的类型
Android有两种ANR Dialog:
1. 应用ANR Dialog(普通应用):
css
─────────────────────────────
│ 应用无响应 │
│ │
│ "XX"未响应。 │
│ │
│ 您要关闭它吗? │
│ │
│ [等待] [关闭应用] │
─────────────────────────────
2. 系统ANR Dialog(系统应用或System Server):
css
─────────────────────────────
│ 系统未响应 │
│ │
│ "System UI"未响应。 │
│ │
│ [等待] [确定] │
─────────────────────────────
6.2 Dialog的实现
java
// 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. 设置Dialog标题
setTitle(context.getText(com.android.internal.R.string.anr_title));
// 2. 设置消息内容
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. 设置按钮
setCancelable(false);
// "等待"按钮
setButton(DialogInterface.BUTTON_POSITIVE,
context.getText(com.android.internal.R.string.wait),
mHandler.obtainMessage(WAIT));
// "关闭应用"按钮
setButton(DialogInterface.BUTTON_NEGATIVE,
context.getText(com.android.internal.R.string.force_close),
mHandler.obtainMessage(FORCE_CLOSE));
// "报告"按钮(可选)
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:
// 用户选择等待,关闭Dialog
mService.mUiHandler.sendEmptyMessage(DISMISS_DIALOG);
break;
case FORCE_CLOSE:
// 用户选择关闭,杀死应用
mService.killAppAtUsersRequest(mProc, AppNotRespondingDialog.this);
break;
case APP_INFO:
// 用户选择报告,打开应用信息页
mService.mUiHandler.obtainMessage(SHOW_APP_ERROR_REPORT, mProc)
.sendToTarget();
break;
}
}
};
}
6.3 用户操作的后续处理
用户选择"等待":
java
// 什么都不做,只是关闭Dialog
// ANR进程继续运行
// 如果还是没响应,可能再次触发ANR
用户选择"关闭应用":
java
void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
synchronized (this) {
// 1. 标记为用户主动关闭
app.crashing = false;
app.crashingReport = null;
app.notResponding = false;
app.notRespondingReport = null;
// 2. 杀死进程
Process.killProcess(app.pid);
// 3. 清理进程记录
handleAppDiedLocked(app, false, true);
}
}
用户选择"报告"(某些系统):
java
// 打开Feedback或BugReport应用
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);
七、ANR分析实战
理论讲了这么多,让我们通过几个真实案例,看看如何实战分析ANR。
7.1 案例1:主线程等锁导致的ANR
现象: 用户点击按钮后,应用卡住5秒,弹出ANR
logcat日志:
markdown
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分析:
php
----- 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)
...
分析步骤:
- 看主线程状态 :
Blocked- 主线程被阻塞了 - 看阻塞位置 :
DataManager.getData()第45行,在等一个锁<0x0a1b2c3d> - 找持锁线程 : Thread-15持有这个锁(
locked <0x0a1b2c3d>) - 看持锁线程在干嘛 : 正在进行Binder调用
transactNative(),查询ContentProvider - 根本原因: 线程15持锁做耗时操作,主线程等不到锁
解决方案:
java
// ❌ 原代码
public class DataManager {
private final Object mLock = new Object();
public void onClick() {
// 主线程调用
synchronized (mLock) {
Data data = getData();
updateUI(data);
}
}
private Data getData() {
// 还是在持锁状态!
return loadFromDatabase(); // Binder调用,很慢
}
}
// ✅ 修复后
public class DataManager {
private final Object mLock = new Object();
public void onClick() {
// 异步加载
new Thread(() -> {
Data data;
synchronized (mLock) {
data = getData();
}
// 在主线程更新UI
runOnUiThread(() -> updateUI(data));
}).start();
}
}
7.2 案例2:Binder调用超时导致的ANR
现象: 应用在后台,突然收到Broadcast ANR
logcat:
ini
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:
ini
"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)
...
分析:
- ANR类型: Broadcast ANR
- 主线程状态 :
Native- 正在执行Native方法 - 卡在哪里 :
BinderProxy.transactNative()- Binder调用 - 在做什么: 注册ContentObserver
- 为什么慢: 可能System Server繁忙或Binder资源耗尽
根因: 在BroadcastReceiver中进行同步Binder调用,而System Server响应慢
解决方案:
java
// ❌ 原代码
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 同步注册ContentObserver,可能很慢
context.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS),
false,
new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
// ...
}
});
}
}
// ✅ 修复方案1:使用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();
}
}
// ✅ 修复方案2:延迟注册
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 启动Service延迟注册
Intent serviceIntent = new Intent(context, RegistrationService.class);
context.startService(serviceIntent);
}
}
7.3 案例3:主线程做数据库操作
traces.txt:
less
"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)
...
分析:
- 主线程在执行SQLite查询
getCount()会加载所有数据到Cursor- 数据量大时会很慢
解决方案:
java
// ❌ 原代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 主线程查询数据库
Cursor cursor = mDbHelper.queryAllRecords();
int count = cursor.getCount(); // 触发实际查询
processData(cursor);
}
// ✅ 使用AsyncTask(已废弃,仅作示例)
@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);
}
}
// ✅ 使用Kotlin Coroutines(推荐)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
val cursor = withContext(Dispatchers.IO) {
dbHelper.queryAllRecords()
}
processData(cursor)
}
}
// ✅ 使用Room + LiveData(最佳实践)
@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)
}
}
八、ANR的预防和监控
预防胜于治疗。让我们看看如何在开发阶段就避免ANR,以及如何建立有效的监控体系。
8.1 开发阶段的预防措施
1. 使用StrictMode检测主线程违规
java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads() // 检测磁盘读取
.detectDiskWrites() // 检测磁盘写入
.detectNetwork() // 检测网络操作
.detectCustomSlowCalls() // 检测自定义慢调用
.penaltyLog() // 输出到logcat
.penaltyDeath() // 直接crash(开发阶段)
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() // 检测数据库泄漏
.detectLeakedClosableObjects() // 检测未关闭的对象
.penaltyLog()
.build());
}
}
}
2. 使用BlockCanary检测卡顿
java
// 初始化BlockCanary
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
BlockCanary.install(this, new AppBlockCanaryContext()).start();
}
}
// 自定义配置
public class AppBlockCanaryContext extends BlockCanaryContext {
@Override
public int provideBlockThreshold() {
return 500; // 超过500ms判定为卡顿
}
@Override
public boolean displayNotification() {
return BuildConfig.DEBUG; // 仅debug环境显示通知
}
}
3. 使用TraceView分析性能
java
// 在可疑代码前后添加trace
Debug.startMethodTracing("my_trace");
// 可疑的代码
suspiciousMethod();
Debug.stopMethodTracing();
// 导出trace文件
adb pull /sdcard/Android/data/<package>/files/my_trace.trace .
// 使用Android Studio的Profiler分析
8.2 线上监控体系
1. ANR捕获和上报
java
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. 记录当前时间
final long startTime = System.currentTimeMillis();
final AtomicLong executionTime = new AtomicLong();
// 2. 向主线程发送任务
mHandler.post(() -> {
executionTime.set(System.currentTimeMillis());
});
// 3. 等待一段时间
try {
Thread.sleep(mTimeout);
} catch (InterruptedException e) {
return;
}
// 4. 检查任务是否执行
long execTime = executionTime.get();
if (execTime == 0 || System.currentTimeMillis() - execTime > mTimeout) {
// 检测到ANR!
onAnrDetected();
}
}
}
private void onAnrDetected() {
// 1. 收集堆栈信息
Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
// 2. 上报到服务器
ANRReporter.report(stackTraces);
}
}
// 启动监控
new ANRWatchDog(5000).start();
2. 使用Bugly等第三方平台
java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 初始化Bugly
CrashReport.initCrashReport(getApplicationContext(), "YOUR_APP_ID", BuildConfig.DEBUG);
// 配置ANR监控
CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(getApplicationContext());
strategy.setAppReportDelay(10000); // 延迟上报
CrashReport.initCrashReport(getApplicationContext(), "YOUR_APP_ID", BuildConfig.DEBUG, strategy);
}
}
3. 监控关键指标
java
public class PerformanceMonitor {
// 监控主线程消息处理时间
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)) {
// 消息开始处理
mStartTime = System.currentTimeMillis();
} else if (msg.startsWith(END)) {
// 消息处理完毕
long duration = System.currentTimeMillis() - mStartTime;
if (duration > 100) { // 超过100ms
// 记录慢消息
logSlowMessage(msg, duration);
}
}
}
});
}
// 监控方法执行时间
@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) { // 超过50ms
String method = joinPoint.getSignature().toShortString();
Log.w("Performance", method + " took " + duration + "ms");
}
return result;
}
}
8.3 ANR优化的最佳实践
1. 主线程的黄金法则
markdown
主线程只做三件事:
1. 更新UI
2. 分发事件
3. 启动异步任务
绝对不能做:
1. 磁盘I/O (读写文件、数据库)
2. 网络I/O (HTTP请求、Socket通信)
3. 复杂计算 (大数据处理、加密解密)
4. 同步Binder调用 (ContentProvider、System Service)
5. 等待锁 (synchronized、Lock)
2. 使用异步机制
| 场景 | 推荐方案 | 示例 |
|---|---|---|
| 简单异步任务 | HandlerThread + Handler |
后台数据处理 |
| 生命周期感知 | ViewModel + LiveData |
UI数据加载 |
| 复杂协程 | Kotlin Coroutines |
网络请求+数据库 |
| 定时任务 | WorkManager |
定期同步 |
| 文件I/O | Executors.newSingleThreadExecutor() |
日志写入 |
| 数据库 | Room + LiveData |
本地数据访问 |
3. 优化Binder调用
java
// ❌ 主线程同步调用ContentProvider
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
// ✅ 使用ContentProviderOperation批量操作
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);
// ✅ 使用AsyncQueryHandler异步查询
new AsyncQueryHandler(getContentResolver()) {
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
processData(cursor);
}
}.startQuery(0, null, uri, null, null, null, null);
4. 减少锁竞争
java
// ❌ 粗粒度锁
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); // 耗时操作在持锁状态!
mCache.put(key, data);
}
return data;
}
}
// ✅ 细粒度锁 + 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); // 只有必要时才持锁
mCache.put(key, data);
}
}
}
return data;
}
}
// ✅ 使用Lock + tryLock避免死等
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 {
// 获取锁失败,返回默认值或异步加载
return getDefaultValue();
}
}
}
九、总结与展望
让我们回顾一下ANR机制的全貌:
9.1 核心要点回顾
ANR的4种类型:
- Input ANR: 5秒无响应,由InputDispatcher检测
- Broadcast ANR: 10秒/60秒,由ActivityManagerService检测
- Service ANR: 20秒/200秒,由ActivityManagerService检测
- ContentProvider ANR: 10秒,由ActivityManagerService检测
ANR检测机制:
- Input: 事件入队时记录时间戳,定时检查waitQueue
- Broadcast/Service: 发送延时消息,超时触发Handler回调
ANR处理流程:
scss
检测超时 → 调用appNotResponding() → 收集CPU/内存信息
→ dump线程堆栈 → 生成traces.txt → 写入DropBox → 弹出Dialog
traces.txt的关键信息:
- 线程状态: Blocked/Waiting/Native/Runnable
- 堆栈信息: 方法调用链
- 锁信息: waiting to lock / locked
9.2 ANR优化的金字塔

9.3 实战建议
-
开发阶段:
- 开启StrictMode
- 使用BlockCanary
- Code Review关注主线程操作
-
测试阶段:
- 压力测试
- Monkey测试
- 性能测试(TraceView、Systrace)
-
线上阶段:
- 接入Bugly等监控平台
- 定期分析ANR Top榜
- 建立ANR快速响应机制
9.4 后续文章预告
ANR机制只是稳定性的一个方面。在后续文章中,我们将继续深入:
| 文章 | 主题 | 核心内容 |
|---|---|---|
| 第3篇 | ANR问题排查实战 | traces.txt深度解读、Systrace分析、常见ANR案例 |
| 第4篇 | 异常日志机制 | Crash处理、Tombstone分析、信号机制 |
| 第5篇 | Native Crash深度分析 | debuggerd、符号化、coredump |
| 第6篇 | Java异常与JE分析实战 | Java异常的分析实战 |
| 第7篇 | Watchdog机制 | 系统看门狗、半死锁检测、System Server保护 |
参考资料
- Android官方文档 - ANR
- AOSP源码 - InputDispatcher.cpp
- AOSP源码 - ActivityManagerService.java
- AOSP源码 - BroadcastQueue.java
- Android Performance Patterns
上一篇 :稳定性性能系列之一------Android稳定性基础:系统架构与关键机制
下一篇 :稳定性性能系列之三------ANR问题排查实战:日志分析与工具实战
返回专栏目录 : Android稳定性&性能深入理解专栏介绍
作者简介: 多年Android系统开发经验,专注于系统稳定性与性能优化领域。欢迎关注本系列,一起深入Android系统的精彩世界!