系列目录:第一篇:异常机制全景图 | 第二篇:Kernel Panic 与系统重启 | 第三篇:Tombstone 机制 | 第四篇:System Server Watchdog | 第五篇:System Server 崩溃 | 第六篇:ANR 机制 | 第七篇:Java 层崩溃 | 第八篇: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
// 默认输入分发超时:5 秒constnsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT=5000*1000000LL;// 5 sec// InputDispatcher::dispatchOnce() 核心逻辑voidInputDispatcher::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
publicfinalclassBroadcastQueue{// ...// 超时常量定义在 AMS 中// static final int BROADCAST_FG_TIMEOUT = 10*1000; // 10秒// static final int BROADCAST_BG_TIMEOUT = 60*1000; // 60秒staticfinalintBROADCAST_TIMEOUT_MSG=ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG+1;finallongmTimeoutPeriod;// 由构造函数传入// ...}4.2 超时检测流程
源码路径:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
publicfinalclassBroadcastQueue{// ...finalvoidprocessNextBroadcast(booleanfromMsg){synchronized(mService){// 遍历广播队列while(mParallelBroadcasts.size()>0||mOrderedBroadcasts.size()>0){// 取出一个广播接收者BroadcastRecordr=getNextBroadcast();// 设置超时检测if(r.receiver!=null){// 发送超时检测消息longtimeoutTime=r.receiverTime+mTimeoutPeriod;setBroadcastTimeoutLocked(timeoutTime);// 发送广播给接收者deliverToRegisteredReceiverLocked(...);}}}}finalvoidbroadcastTimeoutLocked(booleanfromMsg){// 如果广播还没有处理完成 → ANRif(!didProcess){// 触发 ANRmService.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
publicclassActivityManagerServiceextendsActivityManagerNative{// ...staticfinalintBROADCAST_FG_TIMEOUT=10*1000;// 10秒staticfinalintBROADCAST_BG_TIMEOUT=60*1000;// 60秒// 前台广播队列和后台广播队列分别使用不同超时mFgBroadcastQueue=newBroadcastQueue(this,mHandler,"foreground",BROADCAST_FG_TIMEOUT,false);mBgBroadcastQueue=newBroadcastQueue(this,mHandler,"background",BROADCAST_BG_TIMEOUT,true);}关键设计:前台广播的超时要求更严格(10 秒),因为用户正在等待操作完成(如短信发送、网络切换等)。
五、Service ANR —— 服务超时
5.1 超时检测位置
源码路径:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
publicfinalclassActiveServices{// ...staticfinalintSERVICE_TIMEOUT=20*1000;// 20秒staticfinalintSERVICE_BACKGROUND_TIMEOUT=SERVICE_TIMEOUT*10;// 200秒// ...}5.2 超时检测流程
源码路径:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
publicfinalclassActiveServices{// ...voidrealStartServiceLocked(ServiceRecordr,ProcessRecordapp){// 通知 App 进程创建 ServicebumpServiceExecutingLocked(r,"create");app.thread.scheduleCreateService(r,...);// 设置超时检测scheduleServiceTimeoutLocked(app);}voidscheduleServiceTimeoutLocked(ProcessRecordproc){Messagemsg=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);}voidserviceTimeout(ProcessRecordproc){// 如果服务还没响应 → ANRif(proc.executingServices.size()>0){// 触发 ANRmAm.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
publicclassActivityManagerServiceextendsActivityManagerNativeimplementsWatchdog.Monitor,BatteryStatsImpl.BatteryCallback{// ...finalvoidappNotResponding(ProcessRecordproc,ActivityRecordactivity,...){// 1. 记录 ANR 日志Slog.e(TAG,"ANR in "+proc.processName);// 2. 收集 CPU 使用情况updateCpuStatsNow();// 3. dump 所有线程堆栈到 /data/anr/traces.txtFiletracesFile=ActivityManagerService.dumpStackTraces(true,firstPids,...);// 4. 记录到 DropBoxManageraddErrorToDropBox("anr",proc,...);// 5. 根据 ANR 类型决定是否弹对话框if(!isSilentAnr){// 弹出 ANR 对话框(前台应用)Messagemsg=mHandler.obtainMessage(SHOW_NOT_RESPONDING_UI_MSG);msg.obj=...;mHandler.sendMessage(msg);}// 6. 广播 ANR 事件(供调试工具监听)Intentintent=newIntent("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 操作
// 错误做法:主线程读取文件@OverrideprotectedvoidonCreate(BundlesavedInstanceState){FileInputStreamfis=newFileInputStream("/sdcard/large_file.bin");fis.read(buffer);// 可能耗时几秒 → ANR!}规避:使用AsyncTask、HandlerThread、IntentService、RxJava等异步方案。
8.2 主线程 Binder 同步调用
// 错误做法:主线程同步调用跨进程服务@OverrideprotectedvoidonResume(){IRemoteServiceservice=getService();service.doHeavyWork();// 远程服务耗时长 → 主线程等待 → ANR!}规避:将跨进程调用移到后台线程,或使用异步 Binder(oneway)。
8.3 BroadcastReceiver 中执行耗时操作
// 错误做法:广播接收器中执行耗时操作publicclassMyReceiverextendsBroadcastReceiver{publicvoidonReceive(Contextcontext,Intentintent){// 这个操作必须在 10s(前台)/ 60s(后台)内完成doNetworkRequest();// 耗时操作 → Broadcast ANR!}}规避:onReceive()中启动 Service 处理,自身快速返回。
8.4 死锁
// 经典死锁场景// 线程A: synchronized(objA) { synchronized(objB) { ... } }// 线程B: synchronized(objB) { synchronized(objA) { ... } }规避:统一锁的获取顺序,使用java.util.concurrent的锁工具。
8.5 预防工具:StrictMode
// 开发阶段开启 StrictMode 检测if(BuildConfig.DEBUG){StrictMode.setThreadPolicy(newStrictMode.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)源码编写。