1. ANR基础概念与系统机制:触发条件、超时类型、主线程消息循环
1.1 现象识别
用户在使用 Android 应用时,如果触摸屏幕或按键后应用超过 5 秒仍未响应,系统会弹出提示框:“应用无响应”,并列出“等待”或“关闭”选项。这就是典型的 ANR(Application Not Responding)表象。
从技术视角看,ANR 的本质是 主线程(UI 线程)被阻塞,无法处理新到达的输入事件或系统广播。阻塞原因通常是主线程上执行了耗时操作:I/O 读写、网络请求、大数据量数据库操作、复杂 Bitmap 处理或死锁。
判断规则 R1:如果用户在操作应用时连续 5 秒以上界面无刷新、点击无响应,且系统弹出 ANR 对话框,即可判定为 ANR 事件。
1.2 数据采集
当 ANR 发生时,系统会在 /data/anr/traces.txt 中记录主线程及所有存活线程的堆栈快照。这是排查 ANR 的核心数据源。使用以下命令提取:
adb pull /data/anr/traces.txt ./traces_anr.log
adb logcat -b events | grep "anr"
同时收集 logcat 中 am_anr 和 cpu 标签的日志,用于判断 ANR 的类型及系统当时的 CPU 负载。
| 数据源 | 关键信息 | 获取命令 |
|---|---|---|
| traces.txt | 主线程堆栈、锁状态、线程状态 | adb pull /data/anr/traces.txt |
| logcat (events) | ANR 触发时间、包名、超时类型 | adb logcat -b events | grep anr |
| logcat (system) | CPU 负载、进程优先级 | adb logcat -b system | grep CPU |
采集要点:ANR 发生后应尽快提取 traces.txt,系统在后续 ANR 发生时可能覆盖该文件。优先使用
adb pull 保存现场数据。
1.3 原因定位
触发条件
ANR 由 Android 系统的 超时监控机制(Watchdog) 触发。当主线程在以下时间窗口内未完成指定操作时,系统判定 ANR:
- 输入事件(InputEvent):5 秒内未完成分发或处理。
- 广播接收器(BroadcastReceiver):前台广播 10 秒、后台广播 60 秒内未返回。
- 服务(Service):Service 的
onCreate()、onStartCommand()或onDestroy()在 20 秒内未执行完毕。 - ContentProvider:
onCreate()超过 10 秒未完成。
三种核心超时类型对比
| 超时类型 | 超时阈值 | 触发场景 | 典型堆栈特征 |
|---|---|---|---|
| InputDispatchingTimeout | 5 秒 | 触摸、按键、轨迹球事件未响应 | 主线程卡在 View.onTouchEvent() 或 onClick() |
| BroadcastTimeout | 前台 10 秒 / 后台 60 秒 | BroadcastReceiver.onReceive() 未及时返回 |
主线程卡在 onReceive() 内,常有 I/O 或网络调用 |
| ServiceTimeout | 20 秒 | Service 生命周期方法执行超时 | 主线程卡在 onStartCommand() 或 onBind() |
常见误区:ANR 并不只在主线程“死掉”时才发生。如果主线程做了 4.9 秒的耗时操作后恢复,虽然未触发 ANR,但已经造成用户可感知的卡顿。阈值是系统判定 ANR 的底线,不是编码质量标准。
主线程消息循环机制
Android 主线程启动后进入 Looper.loop() 无限循环,从 MessageQueue 中依次取出消息,交给对应的 Handler 处理。这个循环是 UI 事件、绘制、生命周期回调、广播等全部主线程操作的唯一通道。一旦某个消息处理耗时过长,后续所有消息(包括输入事件、绘制请求)都会排队等待,直接导致 ANR。
// 主线程消息循环简化模型
public static void loop() {
for (;;) {
Message msg = queue.next(); // 可能阻塞等待新消息
msg.target.dispatchMessage(msg); // 处理消息——若这里耗时 > 5s → ANR
msg.recycle();
}
}
ANR 故障树分析
从“ANR 发生”顶层事件出发,向下分解的直接原因和根因路径如下:
ANR 发生
├── 主线程消息处理阻塞
│ ├── 【I/O 阻塞】File / Network / SharedPreferences 大量写入
│ ├── 【数据库】SQLite 事务未优化,大数据量查询
│ ├── 【计算】Bitmap 缩放 / JSON 解析 / 正则匹配
│ ├── 【锁竞争】主线程等待子线程锁释放
│ └── 【Binder 调用】同步调用远程服务耗时
└── 超时监控触发
├── InputDispatchingTimeout (5s) — 输入事件未消费
├── BroadcastTimeout (10s / 60s) — 广播未返回
└── ServiceTimeout (20s) — 服务生命周期未完成
定位捷径:查看 traces.txt 中主线程的堆栈,如果看到
android.os.MessageQueue.nativePollOnce() 表示线程正在空闲等待;如果看到 java.io.*、android.database.sqlite.*、android.os.BinderProxy.transact() 等调用,说明主线程正卡在具体的阻塞操作上。
1.4 修复动作
针对主线程阻塞的根因,采取以下 标准修复策略:
- 耗时操作异步化:使用
HandlerThread、AsyncTask(旧)、Coroutine(Kotlin)或RxJava将 I/O、网络、数据库操作移至子线程。 - 减少主线程计算量:Bitmap 采样加载、JSON 流式解析、避免在
onCreate()/onBind()中做大量初始化。 - 锁优化:避免主线程持有锁等待子线程;使用
synchronized块尽量缩小范围,优先使用ReentrantLock带超时。 - Binder 调用异步:对远程 Service 调用使用
oneway或异步回调,避免主线程同步等待。 - BroadcastReceiver 快速返回:在
onReceive()中仅做状态转发,使用goAsync()或JobService处理耗时逻辑。
// 错误示例:主线程直接执行耗时 I/O
public void onClick(View v) {
String data = fileReader.readLargeFile(); // 可能耗时 >5s → ANR
textView.setText(data);
}
// 正确示例:使用 HandlerThread 异步处理
HandlerThread worker = new HandlerThread("worker");
worker.start();
Handler workerHandler = new Handler(worker.getLooper());
public void onClick(View v) {
workerHandler.post(() -> {
String data = fileReader.readLargeFile();
runOnUiThread(() -> textView.setText(data));
});
}
1.5 复测
修复后需验证 ANR 是否不再复现。推荐 三阶段验证:
- 场景复现测试:在低端机(2GB RAM 以下)上重复触发原 ANR 场景,确认不再弹出 ANR 对话框。
- StrictMode 检测:开启主线程磁盘/网络访问检测,确保修复后无违规调用。
- Monkey/压力测试:使用
adb monkey发送 5000 次随机事件,检查是否触发 ANR。
// 启用 StrictMode 检测主线程违规
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
}
复测判断规则 R2:修复后连续 3 次重复原操作场景均未触发 ANR,且 StrictMode 未输出主线程违规日志,视为修复有效。
1.6 预防
从编码规范层面建立 主线程保护机制:
- 代码审查清单:所有
Activity.onCreate()、Fragment.onViewCreated()、BroadcastReceiver.onReceive()、Service.onCreate()中不得包含网络请求、文件 I/O、数据库查询、Bitmap 解码。 - 线程模型标准化:项目统一使用 Kotlin Coroutine + Dispatchers.IO 或 RxJava Schedulers.io 处理耗时任务,主线程仅做 UI 更新。
- 性能监控埋点:在关键入口和退出方法加入耗时打点,单次主线程消息处理超过 200ms 即输出警告日志。
- 自动化检测:CI 流水线集成
blockcanary或matrix框架,自动捕获主线程卡顿和 ANR 堆栈。
| 预防手段 | 实施时机 | 工具/方法 |
|---|---|---|
| 代码审查 | MR 合并前 | Checklist + 人工 Review |
| 线程模型标准化 | 项目架构阶段 | Coroutine / RxJava 规范文档 |
| 性能监控埋点 | 开发阶段 | 自定义 Looper 日志 / Choreographer |
| 自动化检测 | CI 流水线 | BlockCanary / Matrix / StrictMode |
核心原则:任何可能超过 16ms 的操作都不应放在主线程。ANR 是卡顿的极端表现,日常开发应以 16ms 一帧(60fps) 为性能基准,主线程单次消息处理不应超过 5ms(输入事件响应)或 16ms(绘制帧)。