ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Android 7系统异常问题排查(六)应用异常(上)—ANR机制全解

2026/8/6 3:59:45 拓冰建站 浏览量
Android 7系统异常问题排查(六)应用异常(上)—ANR机制全解 系列目录第一篇异常机制全景图 | 第二篇Kernel Panic 与系统重启 | 第三篇Tombstone 机制 | 第四篇System Server Watchdog | 第五篇System Server 崩溃 | 第六篇ANR 机制 | 第七篇Java 层崩溃 | 第八篇Trace 机制 | 第九篇日志系统 | 第十篇实战方法论一、ANR 概述你可能遇到过这些场景App 弹出应用无响应对话框但不知道是主线程卡死还是 Binder 调用超时滑动界面时突然卡住几秒后弹出 ANR 对话框后台广播接收器执行过久系统判定 ANRANRApplication Not Responding是 Android 系统在检测到应用主线程长时间无响应时向用户展示的对话框。ANR 的底层机制是系统的超时检测——系统在某些关键操作上设置了一个时间上限如果主线程在限定时间内没有完成操作就触发 ANR。二、ANR 分类与超时阈值在 AOSP 7 中ANR 分为四类各有不同的超时阈值类型超时阈值检测组件触发场景Input ANR5 秒InputDispatcher应用未及时处理输入事件触摸/按键Broadcast ANR前台 10s / 后台 60sBroadcastQueue广播接收器onReceive()执行过久Service ANR前台 20s / 后台 200sActiveServices服务onCreate()/onStartCommand()执行过久ContentProvider ANR10 秒ActivityManagerServiceProvider 发布超时较少见三、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_TIMEOUT5000*1000000LL;// 5 sec// InputDispatcher::dispatchOnce() 核心逻辑voidInputDispatcher::dispatchOnce(){// 从队列取出事件发送给目标 App// 启动 5 秒超时计时器// 如果 App 在 5 秒内调用了 finishInputEvent()// → 正常完成取消计时器// 如果 5 秒内未收到确认// → 触发 Input ANR// → 通知 AMS 进行 ANR 处理}关键设计DEFAULT_INPUT_DISPATCHING_TIMEOUT是 5000ms5 秒这个值定义了 Input ANR 的超时阈值。3.3 特殊机制聚焦窗口优先InputDispatcher 会区分前台窗口事件和后台窗口事件前台聚焦窗口未及时响应→一定触发 ANR后台窗口未及时响应→ 不直接触发 ANR而是将这些事件丢弃等待窗口重新聚焦时重发这也是为什么后台 App 卡死不一定会直接弹 ANR 的原因。四、Broadcast ANR —— 广播超时4.1 超时检测位置源码路径frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.javapublicfinalclassBroadcastQueue{// ...// 超时常量定义在 AMS 中// static final int BROADCAST_FG_TIMEOUT 10*1000; // 10秒// static final int BROADCAST_BG_TIMEOUT 60*1000; // 60秒staticfinalintBROADCAST_TIMEOUT_MSGActivityManagerService.FIRST_BROADCAST_QUEUE_MSG1;finallongmTimeoutPeriod;// 由构造函数传入// ...}4.2 超时检测流程源码路径frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.javapublicfinalclassBroadcastQueue{// ...finalvoidprocessNextBroadcast(booleanfromMsg){synchronized(mService){// 遍历广播队列while(mParallelBroadcasts.size()0||mOrderedBroadcasts.size()0){// 取出一个广播接收者BroadcastRecordrgetNextBroadcast();// 设置超时检测if(r.receiver!null){// 发送超时检测消息longtimeoutTimer.receiverTimemTimeoutPeriod;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.javapublicclassActivityManagerServiceextendsActivityManagerNative{// ...staticfinalintBROADCAST_FG_TIMEOUT10*1000;// 10秒staticfinalintBROADCAST_BG_TIMEOUT60*1000;// 60秒// 前台广播队列和后台广播队列分别使用不同超时mFgBroadcastQueuenewBroadcastQueue(this,mHandler,foreground,BROADCAST_FG_TIMEOUT,false);mBgBroadcastQueuenewBroadcastQueue(this,mHandler,background,BROADCAST_BG_TIMEOUT,true);}关键设计前台广播的超时要求更严格10 秒因为用户正在等待操作完成如短信发送、网络切换等。五、Service ANR —— 服务超时5.1 超时检测位置源码路径frameworks/base/services/core/java/com/android/server/am/ActiveServices.javapublicfinalclassActiveServices{// ...staticfinalintSERVICE_TIMEOUT20*1000;// 20秒staticfinalintSERVICE_BACKGROUND_TIMEOUTSERVICE_TIMEOUT*10;// 200秒// ...}5.2 超时检测流程源码路径frameworks/base/services/core/java/com/android/server/am/ActiveServices.javapublicfinalclassActiveServices{// ...voidrealStartServiceLocked(ServiceRecordr,ProcessRecordapp){// 通知 App 进程创建 ServicebumpServiceExecutingLocked(r,create);app.thread.scheduleCreateService(r,...);// 设置超时检测scheduleServiceTimeoutLocked(app);}voidscheduleServiceTimeoutLocked(ProcessRecordproc){MessagemsgmAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);msg.objproc;// 发送延迟消息// 前台服务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.javapublicclassActivityManagerServiceextendsActivityManagerNativeimplementsWatchdog.Monitor,BatteryStatsImpl.BatteryCallback{// ...finalvoidappNotResponding(ProcessRecordproc,ActivityRecordactivity,...){// 1. 记录 ANR 日志Slog.e(TAG,ANR in proc.processName);// 2. 收集 CPU 使用情况updateCpuStatsNow();// 3. dump 所有线程堆栈到 /data/anr/traces.txtFiletracesFileActivityManagerService.dumpStackTraces(true,firstPids,...);// 4. 记录到 DropBoxManageraddErrorToDropBox(anr,proc,...);// 5. 根据 ANR 类型决定是否弹对话框if(!isSilentAnr){// 弹出 ANR 对话框前台应用MessagemsgmHandler.obtainMessage(SHOW_NOT_RESPONDING_UI_MSG);msg.obj...;mHandler.sendMessage(msg);}// 6. 广播 ANR 事件供调试工具监听IntentintentnewIntent(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 prio5 tid1 Native | groupmain sCount1 dsCount0 obj0x12c0e0a0 | sysTid1234 nice-2 cgrpdefault sched0/0 | stateS 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()检查条件变量是否被 notifyTimedWaiting调用了Thread.sleep()或带超时的wait()检查 sleep 时间是否合理SleepingThread.sleep()主线程不应 sleep7.3 主线程 Blocked 判断traces.txt 中主线程tid1处于 Blocked 状态是最典型的 ANR 根因main prio5 tid1 Blocked at com.example.Utils.expensiveOperation(Utils.java:50) - waiting to lock 0x12345678 held by Binder:1234_2 tid10 Binder:1234_2 tid10 Runnable at com.example.Utils.expensiveOperation(Utils.java:45) - locked 0x12345678解读Binder 线程持有锁主线程等待锁 → 主线程被阻塞 → ANR。八、常见 ANR 根因与规避8.1 主线程执行 IO 操作// 错误做法主线程读取文件OverrideprotectedvoidonCreate(BundlesavedInstanceState){FileInputStreamfisnewFileInputStream(/sdcard/large_file.bin);fis.read(buffer);// 可能耗时几秒 → ANR!}规避使用AsyncTask、HandlerThread、IntentService、RxJava等异步方案。8.2 主线程 Binder 同步调用// 错误做法主线程同步调用跨进程服务OverrideprotectedvoidonResume(){IRemoteServiceservicegetService();service.doHeavyWork();// 远程服务耗时长 → 主线程等待 → ANR!}规避将跨进程调用移到后台线程或使用异步 Binderoneway。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 tid1 的状态 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 各有不同的检测者InputDispatcher5s、BroadcastQueue10s/60s、ActiveServices20s/200s。前台 vs 后台的超时差异巨大前台 5-20s后台 60-200s——主线程问题在前台更容易暴露。traces.txt 是 ANR 分析的宝典线程状态 堆栈 CPU 使用三位一体定位根因。预防胜于治疗StrictMode 异步化 代码审查在开发阶段就规避 ANR 风险。下一篇将分析应用异常的另一面——APK Java 层崩溃。本文基于 AOSP 7Android Nougat源码编写。