☰
Android ANR 全解析 与 traces 分析实战
2026/10/24 7:38:33 网站建设 项目流程

Android ANR 全解析与 traces 分析实战

技术复盘 · 面试复习手册

四类超时判定 · 系统级 Watchdog · 三段式栈分析

目录

  • 一、先厘清概念:ANR 到底是谁判定的
  • 二、四类 ANR 与超时阈值
  • 三、还有一类"系统级 ANR":Watchdog
  • 四、按根因分类:比背类型更有价值
  • 五、traces 文件怎么读:三段式方法
  • 六、三个典型实战案例
  • 七、定位工具箱
  • 八、预防:应用侧与系统侧
  • 九、面试速答卡

摘要(TL;DR)

ANR 不是"卡了 5 秒"这么简单:它是 AMS 对四类超时(输入 5 秒、广播 10/60 秒、Service 20/200 秒、Provider 10 秒)的判定结果,而 traces 文件里的 main 线程栈顶、锁持有者和 CPU usage 段,才是定位根因的三把钥匙。

一、先厘清概念:ANR 到底是谁判定的

ANR(Application Not Responding)的本质是系统对应用响应能力的一次超时判定,判定方不是用户、也不是应用自己,而是运行在system_server中的 AMS 以及 InputDispatcher。

判定发生后,系统会做三件事:

  1. 把应用所有线程的堆栈 dump 到/data/anr/目录(Android 11 起文件名形如anr_2026-05-20-10-30-00,早期统一写traces.txt);
  2. 在 logcat 中打印ANR in 包名与Reason:;
  3. 弹出"应用无响应"对话框(部分场景如后台 Service、Provider 超时,不弹框直接记日志)。

注意

只有主线程被卡住才会 ANR。后台线程再慢也不会直接触发 ANR,但它可能占满 CPU 或持有主线程需要的锁,从而间接导致 ANR。

二、四类 ANR 与超时阈值

类型触发条件阈值日志特征判定位置
输入事件分发超时InputDispatcher 已把按键/触摸事件发给窗口,应用 5 秒内未消费5 sReason: Input dispatching timed outInputDispatcher 上报 AMS
广播超时广播接收者的onReceive()未在时限内返回前台 10 s / 后台 60 sBroadcast of Intent { ... }BroadcastQueue
Service 超时onCreate/onStartCommand/onBind等生命周期回调未完成前台 20 s / 后台 200 sexecuting service 包名/服务名ActiveServices
ContentProvider 超时Provider 进程拉起或 publishProvider 未完成10 sContentProvider not respondingProcessList(进程启动超时)

记忆口诀:输入 5、广播 10/60、服务 20/200、Provider 10。

几个容易答错的细节:

  • 服务端超时口径在 Android 8 之后固定为前台 20 秒、后台 200 秒,不要再背老版本的"后台 20 分钟";
  • 广播的"前台/后台"取决于发送方是否处于前台,不是接收方;
  • Android 12 起前台服务启动限制更严,但 ANR 阈值未变。

三、还有一类"系统级 ANR":Watchdog

面试官如果问"你处理过系统死机吗",答的就是这一层。

system_server内部有一个 Watchdog 线程,默认 60 秒为一轮,向若干被监控者(AMS 的 main handler、PMS 的 handler、WMS 的全局锁等)投放检测消息。若 60 秒内没有回执,Watchdog 会:

  1. dump 所有 Java 线程栈与 native 栈到/data/anr/;
  2. 打印*** WATCHDOG KILLING SYSTEM PROCESS ***;
  3. 主动杀死system_server,zygote 感知后重启框架(部分平台直接整机重启)。

所以"手机突然黑屏重启、看不到应用崩溃"的现场,第一件事就是找WATCHDOG KILLING这个关键字。

四、按根因分类:比背类型更有价值

根因典型栈特征常见场景
锁等待Blocked,栈中有waiting to lock ... held by thread N主线程与子线程共用锁;子线程做耗时 IO 时持锁
主线程 IO栈顶在SharedPreferencesImpl、SQLiteConnection、FileInputStreamonCreate里读配置、写日志
Binder 同步阻塞栈顶在BinderProxy.transactNative主线程调系统服务或其它进程的 AIDL 同步接口
消息队列堆积栈顶在MessageQueue.next之下的业务代码,且 utm 很高大量 Handler 消息串行执行,输入事件排在队尾
CPU 饥饿主线程栈正常(甚至nativePollOnce),但 CPU usage 段显示被占满后台线程死循环、其它进程抢占
GC 抖动栈中Daemon/HeapTaskDaemon活跃,utm 偏高内存抖动频繁触发 GC
系统服务端阻塞主线程卡在ActivityManagerProxy/PackageManagerProxyPMS 安装扫描、WMS 布局耗时、system_server 锁竞争

五、traces 文件怎么读:三段式方法

读 traces 的关键不是"看得多",而是"看得准"。下面按三段固定顺序拆解,每一段只回答一个问题。

一份 traces 文件包含多个进程的完整栈,几万行起步。不要从头读,按下面顺序看三段。

5.1 第一段:现象行与 Reason

----- pid 4213 at 2026-05-20 10:30:12 ----- Cmd line: com.example.app Build fingerprint: 'xxx/release-keys' ABI: 'arm64'

往上翻日志里的ANR in com.example.app,重点看Reason:与PID:,先把"哪一类 ANR、卡在哪个进程"确定下来。

5.2 第二段:main 线程栈(核心中的核心)

"main" prio=5 tid=1 Blocked | group="main" sCount=1 dsCount=0 flags=1 obj=0x12c00000 self=0x7f8a1c0000 | sysTid=4213 nice=-10 cgrp=default sched=0/0 handle=0x7f9b2c4a50 | state=S schedstat=( 1200000000 8500000000 4200 ) utm=95 stm=25 core=3 HZ=100 | stack=0x7fe3a00000 stackSize=8192KB | held mutexes= at com.example.core.LockHelper.refresh(LockHelper.java:42) - waiting to lock <0x0a1b2c3d> (a java.lang.Object) held by thread 27 at com.example.MainActivity.onResume(MainActivity.java:88) at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1456)

这份栈要读出四个信息:

  1. 线程状态Blocked:不是Running,说明主线程在等锁;
  2. waiting to lock ... held by thread 27:锁被 27 号线程持有,马上去找Thread-27的栈;
  3. utm=95 stm=25:主线程累计用户态 95 个时钟 tick,说明它不是刚被唤醒,而是已经干了不少活;
  4. schedstat的第二项 8.5 秒:可运行但没抢到 CPU 的时间,这个值大就高度怀疑 CPU 饥饿。

常见的良性栈形(别误判):

  • at android.os.MessageQueue.nativePollOnce(Native method)且状态为Native/Waiting:主线程空闲等待消息,正常;
  • at java.lang.Object.wait且是Waiting:等待通知,需结合业务判断。

5.3 第三段:CPU usage 段

CPU usage from 0ms to 5000ms later: 78% 4213/com.example.app: 70% user + 8% kernel / faults: 1200 minor 52% 4213/com.example.app (main thread) 18% 4218/OkHttp Dispatcher 12% 812/media.codec: 12% user

解读规则:

  • 主线程自身 CPU 高:主线程在死循环、密集计算或频繁 GC;
  • 主线程 CPU 低但其它线程/进程高:CPU 饥饿型 ANR,主线程抢不到时间片;
  • 总和接近 100%:系统整体过载,需要看是否有第三方进程或系统服务异常。

5.4 补充:Binder 阻塞怎么确认

如果 main 栈顶是下面的样子,说明主线程在等system_server或其它进程返回:

at android.os.BinderProxy.transactNative(Native method) at android.app.IActivityManager$Stub$Proxy.startService(...)

此时要找 traces 中对应进程(通常是system_server)的栈,看它卡在哪把锁上——这才是"跨进程死锁"的真实位置。

六、三个典型实战案例

下面三个现场覆盖了八成以上的 ANR 投诉:锁、跨进程、CPU。每个案例只保留关键栈片段,训练"从栈到结论"的直觉。

案例 1:主线程等锁(最高频)

现象:点击首页按钮后 5 秒弹 ANR。

关键栈:

"main" prio=5 tid=1 Blocked at com.example.db.ConfigDao.get(ConfigDao.java:31) - waiting to lock <0x0a1b2c3d> held by thread 27 "Thread-27" prio=5 tid=27 Runnable at com.example.net.HttpClient.query(HttpClient.java:120) - locked <0x0a1b2c3d> (a java.lang.Object)

结论:子线程 27 持锁做网络请求,主线程被迫等待。

修复:锁粒度收窄,把网络请求移出临界区;主线程改为异步回调。

案例 2:Binder 同步调用引发的跨进程死锁

现象:开机向导进入设置页必现 ANR,且system_server也出现卡顿。

应用侧:

"main" ... Native at android.os.BinderProxy.transactNative(Native method) at android.content.pm.IPackageManager$Stub$Proxy.getPackageInfo(...)

system_server侧:

"Binder:1234_5" ... Blocked at com.android.server.wm.ActivityTaskManagerService.startActivity(...) - waiting to lock <0x0b2c> held by "main" "main" ... Blocked at com.android.server.wm.WindowManagerService.layoutWindow(...) - waiting to lock <0x0c3d> held by "Binder:1234_5"

结论:应用主线程同步等 PMS,PMS 又在等 WMS 的锁,而 WMS 的锁被另一个正在等应用的 Binder 线程持有,形成环。

修复:应用侧改异步查询;系统侧减少持锁时间,避免在锁内做跨进程调用。

案例 3:CPU 饥饿型(最容易被误判为"代码写得烂")

现象:低端机上偶发 ANR,栈看起来完全正常。

关键特征:

  • main 栈顶为nativePollOnce(空闲);
  • schedstat第二项达数秒;
  • CPU usage 段显示某后台进程占 60% 以上。

结论:不是应用代码卡住,而是系统整体 CPU 被占满,输入事件分发不出去。

修复:定位抢占进程(多为异常后台服务、日志抓取、频繁 GC 的第三方 SDK),必要时用 cgroup 限流或调低优先级。

七、定位工具箱

# 1. 抓取现网 ANR 文件(需 root 或 userdebug)adb shellls-l/data/anr/ adb pull /data/anr/anr_2026-05-20-10-30-00# 2. 事件日志中的 ANR 埋点adb logcat-bevents|grep-E"am_anr|am_proc_start"# 3. 实时观察输入分发是否卡住adb shell dumpsys input|grep-A20"FocusedWindow"# 4. 查看当前进程与线程 CPU 占用adb shelltop-H-p进程号# 5. 系统服务锁竞争(userdebug)adb shell dumpsys activity processes adb shell dumpsys window

离线分析顺序建议:Reason→ main 栈 → 锁持有者 → 对端进程栈 → CPU usage。按这个顺序,八成现场能在十分钟内定性。

八、预防:应用侧与系统侧

ANR 治理要分两侧看:应用侧决定"会不会卡",系统侧决定"卡了之后能不能快速恢复"。

应用侧(写代码时就能规避)

  1. 主线程不做 IO、不碰数据库、不读大文件;
  2. onReceive()中超过 10 秒的工作改用goAsync()或转 Service/WorkManager;
  3. 避免主线程同步调用 Binder,改用回调或线程池;
  4. 减少synchronized范围,尤其不要"持锁做网络/IO";
  5. 开启 StrictMode,在开发期暴露主线程磁盘与网络访问;
  6. Application 的onCreate只做初始化,重活延迟到首屏后。

系统侧(ROM/平台工程师的职责)

  1. 缩短system_server关键锁的持有时间,避免锁内跨进程调用;
  2. 监控开机后前 3 分钟的 CPU 峰值,限制预装应用的并发启动;
  3. 对高频 ANR 的服务做专项治理(如 PMS 扫描、WMS 布局);
  4. 在 userdebug 版本常开 Watchdog 日志,量产版本保留触发时的 dump 能力。

九、面试速答卡

问:ANR 有哪几种?

答:表面四类超时——输入 5 秒、广播前台 10 秒后台 60 秒、Service 前台 20 秒后台 200 秒、Provider 10 秒;本质都是主线程 5 秒内无法处理输入事件。再往上一层还有system_server的 Watchdog,60 秒无响应会杀系统进程,属于整机级的"系统 ANR"。

问:怎么定位 ANR?

答:先看Reason定类型,再看 traces 里 main 线程的栈顶与状态,然后顺着held by thread N找到锁持有者,必要时对比对端进程(Binder 对端)的栈,最后用 CPU usage 段区分"自己在忙"还是"CPU 被抢"。四步能覆盖绝大多数现场。

问:你做过哪些 ANR 治理?

答:分两层讲——应用层收敛主线程 IO 与锁粒度,系统层治理system_server锁竞争与开机 CPU 峰值;配合 traces 自动化解析脚本,把高频 ANR 聚合成 TOP 榜,按模块派单。

元信息

项目说明
目标读者Android 系统开发工程师、Framework 层开发者、面试冲刺者
前置知识Android 进程模型、Binder 机制、AMS 基本职责、Linux 线程状态
技术栈版本Android 8.0 至 Android 14(阈值与路径以 AOSP 主线为准)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询