Android ANR 全解析 与 traces 分析实战

技术复盘 · 面试复习手册

Android ANR 全解析

与 traces 分析实战

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

目录

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

二、四类 ANR 与超时阈值 1

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

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

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

六、三个典型实战案例 1

七、定位工具箱 1

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

九、面试速答卡 1

元信息 1

摘要(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 s Reason: Input dispatching timed out InputDispatcher 上报 AMS

广播超时 广播接收者的 onReceive() 未在时限内返回 前台 10 s / 后台 60 s Broadcast of Intent { ... } BroadcastQueue

Service 超时 onCreate / onStartCommand / onBind 等生命周期回调未完成 前台 20 s / 后台 200 s executing service 包名/服务名 ActiveServices

ContentProvider 超时 Provider 进程拉起或 publishProvider 未完成 10 s ContentProvider not responding ProcessList(进程启动超时)

记忆口诀:输入 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、FileInputStream onCreate 里读配置、写日志

Binder 同步阻塞 栈顶在 BinderProxy.transactNative 主线程调系统服务或其它进程的 AIDL 同步接口

消息队列堆积 栈顶在 MessageQueue.next 之下的业务代码,且 utm 很高 大量 Handler 消息串行执行,输入事件排在队尾

CPU 饥饿 主线程栈正常(甚至 nativePollOnce),但 CPU usage 段显示被占满 后台线程死循环、其它进程抢占

GC 抖动 栈中 Daemon/HeapTaskDaemon 活跃,utm 偏高 内存抖动频繁触发 GC

系统服务端阻塞 主线程卡在 ActivityManagerProxy / PackageManagerProxy PMS 安装扫描、WMS 布局耗时、system_server 锁竞争

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

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

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

5.1 第一段:现象行与 Reason

----- pid 4213 at 2026-05-20 10:30:12 -----Cmd line: com.example.appBuild 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.IActivityManagerStubStubStubProxy.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.IPackageManagerStubStubStubProxy.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 shell ls -l /data/anr/adb pull /data/anr/anr_2026-05-20-10-30-00# 2. 事件日志中的 ANR 埋点adb logcat -b events | grep -E "am_anr|am_proc_start"# 3. 实时观察输入分发是否卡住adb shell dumpsys input | grep -A 20 "FocusedWindow"# 4. 查看当前进程与线程 CPU 占用adb shell top -H -p 进程号# 5. 系统服务锁竞争(userdebug)adb shell dumpsys activity processesadb 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 主线为准)

相关推荐
敲代码的瓦龙2 小时前
Jetpack?lifecycle!!!
android·kotlin
苦瓜花2 小时前
Hilt用使用及原理
android
深度学习04073 小时前
MySQL InnoDB Cluster 部署完整文档
android·mysql·adb
邪修king3 小时前
Re:Linux系统篇(三十七):进程间通信Chapter4:System V 共享内存深度解析:为什么它是最快的 IPC?
android·linux·运维
用户337922545684 小时前
Kiora SDK:在 Android 上组织端侧模型、工具与 A2A 协作
android
洞见前行4 小时前
你在 XML 时代学的渲染优化,进了 Compose 大多不再是重点——Recomposition 才是新的战场
android
ii_best4 小时前
按键精灵手机助手 v5.2.1 实测:inputText 逐字延时 + 调试面板多行复制,安卓脚本开发的两个输入痛点一次解决
android·开发语言·智能手机·自动化·ai编程·按键精灵
hunterandroid4 小时前
OkHttp 源码解析:拦截器链、连接池与缓存策略的设计精髓
android
蒲公英内测分发5 小时前
智能手环出海测试,海外合作方分散在不同国家,Android 测试包怎么持续分发和更新?
android·前端·人工智能·测试工具·智能手表