系列目录 :第一篇:异常机制全景图 | [第二篇:Kernel Panic 与系统重启](#第二篇:Kernel Panic 与系统重启) | [第三篇:Tombstone 机制](#第三篇:Tombstone 机制) | [第四篇:System Server Watchdog](#第四篇:System Server Watchdog) | [第五篇:System Server 崩溃](#第五篇:System Server 崩溃) | [第六篇:ANR 机制](#第六篇:ANR 机制) | [第七篇:Java 层崩溃](#第七篇:Java 层崩溃) | [第八篇:Trace 机制](#第八篇:Trace 机制) | 第九篇:日志系统 | 第十篇:实战方法论
一、ANR 概述
你可能遇到过这些场景:
- App 弹出"应用无响应"对话框,但不知道是主线程卡死还是 Binder 调用超时
- 滑动界面时突然卡住,几秒后弹出 ANR 对话框
- 后台广播接收器执行过久,系统判定 ANR
ANR (Application Not Responding)是 Android 系统在检测到应用主线程长时间无响应时,向用户展示的对话框。ANR 的底层机制是系统的超时检测------系统在某些关键操作上设置了一个时间上限,如果主线程在限定时间内没有完成操作,就触发 ANR。
二、ANR 分类与超时阈值
在 AOSP 7 中,ANR 分为四类,各有不同的超时阈值:
| 类型 | 超时阈值 | 检测组件 | 触发场景 |
|---|---|---|---|
| Input ANR | 5 秒 | InputDispatcher | 应用未及时处理输入事件(触摸/按键) |
| Broadcast ANR | 前台 10s / 后台 60s | BroadcastQueue | 广播接收器 onReceive() 执行过久 |
| Service ANR | 前台 20s / 后台 200s | ActiveServices | 服务 onCreate()/onStartCommand() 执行过久 |
| ContentProvider ANR | 10 秒 | ActivityManagerService | Provider 发布超时(较少见) |
三、Input ANR ------ 输入事件超时
3.1 InputDispatcher 的角色
Input ANR 的检测发生在 Native 层的 InputDispatcher。
源码路径 :frameworks/native/services/inputflinger/InputDispatcher.cpp
触摸事件流:
InputReader(从驱动读事件)
↓
InputDispatcher(分发到目标窗口)
↓
目标 App 主线程(处理事件)
↓
处理完成 → finishInputEvent() → 确认回执
3.2 超时检测机制
源码路径 :frameworks/native/services/inputflinger/InputDispatcher.cpp
cpp
// 默认输入分发超时:5 秒
const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
// InputDispatcher::dispatchOnce() 核心逻辑
void InputDispatcher::dispatchOnce() {
// 从队列取出事件,发送给目标 App
// 启动 5 秒超时计时器
// 如果 App 在 5 秒内调用了 finishInputEvent()
// → 正常完成,取消计时器
// 如果 5 秒内未收到确认
// → 触发 Input ANR
// → 通知 AMS 进行 ANR 处理
}
关键设计 :
DEFAULT_INPUT_DISPATCHING_TIMEOUT是 5000ms(5 秒),这个值定义了 Input ANR 的超时阈值。
3.3 特殊机制:聚焦窗口优先
InputDispatcher 会区分"前台窗口事件"和"后台窗口事件":
- 前台聚焦窗口未及时响应 → 一定触发 ANR
- 后台窗口未及时响应 → 不直接触发 ANR,而是将这些事件丢弃,等待窗口重新聚焦时重发
这也是为什么后台 App 卡死不一定会直接弹 ANR 的原因。
四、Broadcast ANR ------ 广播超时
4.1 超时检测位置
源码路径 :frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
java
public final class BroadcastQueue {
// ...
// 超时常量定义在 AMS 中
// static final int BROADCAST_FG_TIMEOUT = 10*1000; // 10秒
// static final int BROADCAST_BG_TIMEOUT = 60*1000; // 60秒
static final int BROADCAST_TIMEOUT_MSG =
ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
final long mTimeoutPeriod; // 由构造函数传入
// ...
}
4.2 超时检测流程
源码路径 :frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
java
public final class BroadcastQueue {
// ...
final void processNextBroadcast(boolean fromMsg) {
synchronized (mService) {
// 遍历广播队列
while (mParallelBroadcasts.size() > 0
|| mOrderedBroadcasts.size() > 0) {
// 取出一个广播接收者
BroadcastRecord r = getNextBroadcast();
// 设置超时检测
if (r.receiver != null) {
// 发送超时检测消息
long timeoutTime = r.receiverTime + mTimeoutPeriod;
setBroadcastTimeoutLocked(timeoutTime);
// 发送广播给接收者
deliverToRegisteredReceiverLocked(...);
}
}
}
}
final void broadcastTimeoutLocked(boolean fromMsg) {
// 如果广播还没有处理完成 → ANR
if (!didProcess) {
// 触发 ANR
mService.appNotResponding(...);
// 重新调度超时检测
setBroadcastTimeoutLocked(mTimeoutPeriod);
}
}
}
关键设计 :超时检测通过 Handler 延迟消息实现。
BROADCAST_TIMEOUT_MSG在超时时间到达时触发broadcastTimeoutLocked(),如果此时广播仍未处理完成,就触发 ANR。
4.3 前台 vs 后台广播的超时差异
| 广播类型 | 超时 | AOSP 7 判断条件 |
|---|---|---|
| 前台广播 | 10 秒 | Intent.FLAG_RECEIVER_FOREGROUND 或 isFg |
| 后台广播 | 60 秒 | 普通广播(默认) |
源码路径 :frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
java
public class ActivityManagerService extends ActivityManagerNative {
// ...
static final int BROADCAST_FG_TIMEOUT = 10*1000; // 10秒
static final int BROADCAST_BG_TIMEOUT = 60*1000; // 60秒
// 前台广播队列和后台广播队列分别使用不同超时
mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
"foreground", BROADCAST_FG_TIMEOUT, false);
mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
"background", BROADCAST_BG_TIMEOUT, true);
}
关键设计:前台广播的超时要求更严格(10 秒),因为用户正在等待操作完成(如短信发送、网络切换等)。
五、Service ANR ------ 服务超时
5.1 超时检测位置
源码路径 :frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
java
public final class ActiveServices {
// ...
static final int SERVICE_TIMEOUT = 20*1000; // 20秒
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; // 200秒
// ...
}
5.2 超时检测流程
源码路径 :frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
java
public final class ActiveServices {
// ...
void realStartServiceLocked(ServiceRecord r, ProcessRecord app) {
// 通知 App 进程创建 Service
bumpServiceExecutingLocked(r, "create");
app.thread.scheduleCreateService(r, ...);
// 设置超时检测
scheduleServiceTimeoutLocked(app);
}
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
// 发送延迟消息
// 前台服务:SERVICE_TIMEOUT (20s)
// 后台服务:SERVICE_BACKGROUND_TIMEOUT (200s)
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT
: SERVICE_BACKGROUND_TIMEOUT);
}
void serviceTimeout(ProcessRecord proc) {
// 如果服务还没响应 → ANR
if (proc.executingServices.size() > 0) {
// 触发 ANR
mAm.appNotResponding(proc, ...);
}
}
}
关键设计 :Service ANR 的超时也是通过 Handler 延迟消息实现。
SERVICE_TIMEOUT是 20 秒,SERVICE_BACKGROUND_TIMEOUT是 200 秒(10 倍)。
5.3 前台 vs 后台服务超时差异
| 服务类型 | 超时 | 触发条件 |
|---|---|---|
| 前台服务 | 20 秒 | startForeground() 调用的服务 |
| 后台服务 | 200 秒 | 普通 startService() |
六、ANR 触发后的处理流程
6.1 AMS.appNotResponding() ------ 核心处理
源码路径 :frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
java
public class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
// ...
final void appNotResponding(ProcessRecord proc,
ActivityRecord activity, ...) {
// 1. 记录 ANR 日志
Slog.e(TAG, "ANR in " + proc.processName);
// 2. 收集 CPU 使用情况
updateCpuStatsNow();
// 3. dump 所有线程堆栈到 /data/anr/traces.txt
File tracesFile = ActivityManagerService.dumpStackTraces(
true, firstPids, ...);
// 4. 记录到 DropBoxManager
addErrorToDropBox("anr", proc, ...);
// 5. 根据 ANR 类型决定是否弹对话框
if (!isSilentAnr) {
// 弹出 ANR 对话框(前台应用)
Message msg = mHandler.obtainMessage(SHOW_NOT_RESPONDING_UI_MSG);
msg.obj = ...;
mHandler.sendMessage(msg);
}
// 6. 广播 ANR 事件(供调试工具监听)
Intent intent = new Intent("android.intent.action.ANR");
broadcastIntentLocked(...);
}
}
关键设计 :ANR 处理的核心是
dumpStackTraces()------通过向目标进程发送 SIGQUIT 信号,让各进程的 Signal Catcher 线程输出堆栈到/data/anr/traces.txt。
6.2 /data/anr/traces.txt 的生成
dumpStackTraces() 流程:
1. 确定需要 dump 的进程:
- 触发 ANR 的进程(优先)
- 关键系统进程(system_server、surfaceflinger、mediaserver)
- 其他可能有关系的进程
2. 向每个目标进程发送 SIGQUIT 信号
3. 各进程的 Signal Catcher 线程响应 SIGQUIT
4. 输出各线程的堆栈到 /data/anr/traces.txt
七、traces.txt 解读方法
7.1 文件结构
----- pid 1234 at 2024-01-01 12:00:00 -----
Cmd line: com.example.app
Build fingerprint: 'Android/aosp_...'
ABI: 'arm64'
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 obj=0x12c0e0a0
| sysTid=1234 nice=-2 cgrp=default sched=0/0
| state=S schedstat=( ... )
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:456)
at com.example.SomeService$Stub$Proxy.doWork(SomeService.java:100)
at com.example.MainActivity.onCreate(MainActivity.java:50)
...
----- end 1234 -----
7.2 线程状态字典
| 状态 | 含义 | 定位线索 |
|---|---|---|
| Native | 正在执行 Native 代码(JNI 或系统调用) | 可能是 Binder 调用、IO 等待 |
| Runnable | 正在运行或等待 CPU | 检查是否有密集计算 |
| Blocked | 等待获取对象锁 | 死锁/锁竞争的重点排查对象 |
| Waiting | 调用了 Object.wait() |
检查条件变量是否被 notify |
| TimedWaiting | 调用了 Thread.sleep() 或带超时的 wait() |
检查 sleep 时间是否合理 |
| Sleeping | Thread.sleep() | 主线程不应 sleep |
7.3 主线程 Blocked 判断
traces.txt 中"主线程(tid=1)处于 Blocked 状态"是最典型的 ANR 根因:
"main" prio=5 tid=1 Blocked
at com.example.Utils.expensiveOperation(Utils.java:50)
- waiting to lock <0x12345678> held by "Binder:1234_2" tid=10
"Binder:1234_2" tid=10 Runnable
at com.example.Utils.expensiveOperation(Utils.java:45)
- locked <0x12345678>
解读:Binder 线程持有锁,主线程等待锁 → 主线程被阻塞 → ANR。
八、常见 ANR 根因与规避
8.1 主线程执行 IO 操作
java
// 错误做法:主线程读取文件
@Override
protected void onCreate(Bundle savedInstanceState) {
FileInputStream fis = new FileInputStream("/sdcard/large_file.bin");
fis.read(buffer); // 可能耗时几秒 → ANR!
}
规避 :使用 AsyncTask、HandlerThread、IntentService、RxJava 等异步方案。
8.2 主线程 Binder 同步调用
java
// 错误做法:主线程同步调用跨进程服务
@Override
protected void onResume() {
IRemoteService service = getService();
service.doHeavyWork(); // 远程服务耗时长 → 主线程等待 → ANR!
}
规避 :将跨进程调用移到后台线程,或使用异步 Binder(oneway)。
8.3 BroadcastReceiver 中执行耗时操作
java
// 错误做法:广播接收器中执行耗时操作
public class MyReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
// 这个操作必须在 10s(前台)/ 60s(后台)内完成
doNetworkRequest(); // 耗时操作 → Broadcast ANR!
}
}
规避 :onReceive() 中启动 Service 处理,自身快速返回。
8.4 死锁
java
// 经典死锁场景
// 线程A: synchronized(objA) { synchronized(objB) { ... } }
// 线程B: synchronized(objB) { synchronized(objA) { ... } }
规避 :统一锁的获取顺序,使用 java.util.concurrent 的锁工具。
8.5 预防工具:StrictMode
java
// 开发阶段开启 StrictMode 检测
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
}
九、ANR 定位实战流程
1. 获取 traces.txt
→ adb pull /data/anr/traces.txt
(如果现场已丢失,从 bugreport 中提取)
2. 定位主线程状态
→ 搜索 "Cmd line: com.example.app"
→ 找到 "main" tid=1 的状态
3. 分析阻塞原因
├─ Blocked → 找持有的锁和等待的锁
├─ Native → 看 Binder 调用栈,找对端进程
├─ Runnable → 看是否密集计算或等待 CPU
└─ Waiting → 看 wait/join/sleep 调用
4. 结合 CPU 使用情况
→ traces.txt 开头有各进程的 CPU 占用
→ 判断是 CPU 不足还是逻辑阻塞
5. 查看 logcat 上下文
→ logcat -d | grep "ANR in"
→ 查看 ANR 前后 1 分钟的日志
十、总结
-
ANR 是主线程的"超时罚单":系统给了主线程明确的时间窗口,超时就触发。
-
四类 ANR 各有不同的检测者:InputDispatcher(5s)、BroadcastQueue(10s/60s)、ActiveServices(20s/200s)。
-
前台 vs 后台的超时差异巨大:前台 5-20s,后台 60-200s------主线程问题在前台更容易暴露。
-
traces.txt 是 ANR 分析的"宝典":线程状态 + 堆栈 + CPU 使用,三位一体定位根因。
-
预防胜于治疗:StrictMode + 异步化 + 代码审查,在开发阶段就规避 ANR 风险。
下一篇将分析应用异常的另一面------APK Java 层崩溃。
本文基于 AOSP 7(Android Nougat)源码编写。