ANR 速查手册_sncr操作手册
前言摘要
本文面向 Android 客户端工程实践,按“速查→定位→交叉验证→归因与修复”的顺序梳理 ANR 排查方法。全文配套可执行步骤、日志示例与术语释义,以 Reason 与主线程 Busy/Waiting 判别为主线,并结合系统信号进行交叉验证,帮助在有限时间内给出清晰结论与化治理方案。
速查清单
- 确认ANR : 在日志中搜索 ActivityManager: ANR in 、 am_anr 、 Reason: 。
- 找原因(Reason) : 从 WindowManager / InputDispatcher 找到 Input event dispatching timed out / FocusEvent 等关键词与超时时间(常见 5s/10s)。
- 锁定受害者/施害者 : 明确是谁“server is not responding”,是哪一个 Activity /进程。
- 看主线程在做什么 : 捞取堆栈文件 /data/anr/anr_*.txt ,定位 "main" 线程顶部 10~30 行,识别是否重计算/IO/锁等待。
- 交叉验证 : 是否有 Choreographer Skipped frames 、 OpenGLRenderer Davey 、 CPU usage 升高、 SurfaceFlinger 弹出 ANR 层。 #技术分享
- 初步归因与止血 : 若为主线程重任务/同步 IO,先降级/关闭路径或临时异步化,阻断复现;随后进入深度分析。
深入分析路径
第 1 步:确认 ANR 与类型
ANR 分类与触发条件
- 输入分发 ANR(Input dispatching timed out,常见 5s)
- 触发:目标窗口未在时限内处理输入/焦点/按键事件,日志含 server is not responding 、 Waited 5000ms for FocusEvent/KeyEvent 。
- 常见原因:主线程重计算/同步 IO/锁竞争/跨进程阻塞,导致消息队列无法及时取出事件。
- BroadcastReceiver 超时(前台 10s / 后台 60s)
- 触发: onReceive 未在时限内返回,日志常见 Broadcast of Intent ... timeout 、 BroadcastQueue 相关栈。
- 常见原因:在 onReceive 内做网络/磁盘/解码等耗时,或间接等待锁/跨进程调用。
- Service 执行超时(前台 20s / 后台 200s)
- 触发: Service 执行过久或 onStartCommand/onBind 等未及时处理完成,日志常见 executing service / Service timeout for ... 。
- 常见原因:主线程执行长任务、死循环/锁等待、长 Binder 调用、未切到后台线程。
- ContentProvider 发布/调用超时(10s)
- 触发: ContentProvider 在 onCreate 发布或在 query/insert 中长时间阻塞,日志常见 ContentProvider not responding / Timeout executing ContentProvider 。
- 常见原因:Provider 中进行磁盘/网络重操作或初始化复杂组件放在主线程。
第 2 步:从 Reason 锁定“谁卡了谁”
- 关注 WindowManager / InputDispatcher 的超时日志,通常包含:
- 目标窗口/组件(如 .../TargetActivity )
- 角色( server is not responding 通常指应用端未响应)
- 事件与等待时长(如 Waited 5003ms for FocusEvent(hasFocus=false) )
09-02 12:03:36.411705 1412 1514 I WindowManager: Input event dispatching timed out sending to com.abc.android.xyz.launcher/com.abc.android.xyz.app.TargetActivity. Reason: ff485e3 com.abc.android.xyz.launcher/com.abc.android.xyz.app.TargetActivity (server) is not responding. Waited 5003ms for FocusEvent(hasFocus=false)
09-02 12:03:36.507450 1412 1514 D WindowManager: notifyANR took 96ms
09-02 12:03:36.508364 1412 5205 I am_anr : [0,2739,com.abc.android.xyz.launcher,685293253,Input dispatching timed out (... Waited 5003ms for FocusEvent(hasFocus=false))]
第 3 步:堆栈看主线程
- 先定位 "main" 线程,然后按两大类判断:
- A) 主线程在忙(Busy)
- 判据:
- state=R 或长时间 Runnable ;目标进程 user% 明显偏高
- 栈包含密集计算/编解码/图片处理/复杂布局与绘制/字符串或 JSON 重解析
- 主线程出现同步磁盘/网络/数据库调用(虽属 I/O,但“在忙”的常见诱因)
- 典型信号: at com.abc.android.xyz...encode/decode/... 、 ViewRootImpl.doTraversal 、 Choreographer Skipped frames 、 OpenGLRenderer Davey
- 处理方向:
- 重任务移出主线程( Dispatchers.Default / Dispatchers.IO 、线程池、 WorkManager )
- 降采样/缓存/批处理;分片+让步(处理 N 个单元后 post/yield )
- 主线程启用 StrictMode 禁同步 I/O;渲染路径减负(减少无效 invalidate 、控制位图尺寸/上传)
- B) 主线程在等(Waiting)
- 总判据: state=Blocked/Waiting/D ,或栈顶出现等待/系统调用/跨进程桥接
- 常见子类(表现 / 处理方向):
- 同步等待/锁竞争: Object.wait 、 ReentrantLock.lock/await 、 CountDownLatch.await 、 Future.get 、 Thread.join
- 处理:UI 线程禁同步等待;缩小临界区与锁粒度;加超时/降级,改回调/异步
- 跨进程(Binder)阻塞: BinderProxy.transact ,调用 ContentResolver/PackageManager/WindowManager/Media 等;对端(如 system_server )负载高
- 处理:慢 Binder 后台化并设超时/重试;结果缓存与幂等;拆小 payload、降频
- 同步 I/O 阻塞: read/write/fsync/epoll 、 SQLiteDatabase.query 、 OkHttp.execute ;系统侧 iowait 偏高或 state=D
- 处理:统一 IO 线程池/异步;合并/批量写;内存/磁盘缓存;主线程启用 StrictMode
- 渲染/图形等待: ThreadedRenderer.nSyncAndDrawFrame 、 eglSwapBuffers 、 Surface/BufferQueue 、sync-fence;伴随丢帧/ Davey
- 处理:降低每帧工作量;控制位图尺寸与上传;合并绘制批次
- GC/堆锁等待:日志含 “Waiting for GC to complete”,或类表锁;GC 直方图偏高
- 处理:降分配/对象复用;避免主线程创建大对象与巨图
- 窗口/输入管线同步等待: Input event dispatching timed out 等待 FocusEvent/KeyEvent ; ViewRootImpl 布局/ relayoutWindow 同步点停滞
- 处理:切页/焦点变更路径移除重任务与阻塞;初始化与 I/O 外移;分片让步
- 死锁/循环依赖:多线程/跨进程互等(互持锁、互相 join/await/transact )
- 处理:统一锁顺序、拆锁/顺序化桥接;跨进程调用加超时;必要时重构打环
"main" prio=5 tid=1 Native
native: #00 ... libandroidndkgif.so ... SimpleGCTGifEncoder::reduceColor
native: #01 ... libandroidndkgif.so ... SimpleGCTGifEncoder::encodeFrame
native: #02 ... libandroidndkgif.so ... GifEncoder_nativeEncodeFrame
at com.waynejo.androidndkgif.GifEncoder.nativeEncodeFrame(Native method)
at com.waynejo.androidndkgif.GifEncoder.encodeFrame(SourceFile:99)
at com.abc.android.xyz.utils.BitmapUtilsKt.saveGIF(SourceFile:423)
at com.abc.android.xyz.utils.BitmapUtilsKt.saveToFile(SourceFile:339)
... at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7705)
- 初步结论:主线程正在进行 GIF 编码与图片写入(计算+IO),致使输入/焦点事件无法在 5 秒内被处理,可以初步定位为(Busy)状态。
第 4 步:交叉验证
- 目标:验证(Busy)初步结论:主线程因计算/渲染链路过重而无法在时限内响应
- 观察项与口径:
- Choreographer: Skipped 297 frames → 主线程卡顿显著,符合重计算/主线程阻塞特征
- InputDispatcher: ... spent 7387ms processing FocusEvent → 输入管线等待被动方在应用侧(目标窗口未及时处理),而非输入系统自身故障
- OpenGLRenderer: Davey! duration≈7447ms → 单帧渲染(含测量/布局/绘制/提交)整体拉长,常与主线程业务/绘制过重一致
- ActivityManager CPU usage:进程 ≈113% ,构成 ≈101% user + 12% kernel ,且 iowait 低 → 以用户态计算为主,佐证“在忙”而非主要等 I/O
- SurfaceFlinger: createLayer Application Not Responding → ANR 弹窗创建时间与上述信号对齐,可作为时间锚点
I Choreographer: Skipped 297 frames! The application may be doing too much work on its main thread.
I InputDispatcher: ... spent 7387ms processing FocusEvent(hasFocus=false)
I OpenGLRenderer: Davey! duration=7447ms; ...
E ActivityManager: ANR in com.abc.android.xyz.TargetActivity
E ActivityManager: PID: 2739
E ActivityManager: CPU usage from ...
E ActivityManager: 113% 2739/com.abc.android.xyz.launcher: 101% user + 12% kernel
D SurfaceFlinger: createLayer: name=Application Not Responding: com.abc.android.xyz.launcher
第 5 步:归因与修复
- 主线程在干重活
- 判断:栈现 encode/decode/measure/layout/draw 等重计算;进程 user% 高;伴随丢帧/Davey
- 动作:重任务出主线程( Dispatchers.Default / Dispatchers.IO 、 WorkManager );降采样/缓存;分片让步
- 同步 IO 阻塞(文件/网络/数据库)
- 判断:栈现 read/write/fsync/epoll 、 SQLiteDatabase 、 OkHttp.execute ;线程 state=D 或系统 iowait 偏高
- 动作:全量改异步/IO 线程池;合并/批量写;启用 StrictMode 禁主线程 IO;必要时降级
- 锁竞争/死锁
- 判断: Blocked/Waiting + wait/await/join/get ;多线程互等或锁顺序不一致
- 动作:缩小临界区、拆锁/顺序化;回调/异步替代等待;所有等待加超时;必要时重构打环
- 跨进程阻塞(Binder)
- 判断:栈顶 BinderProxy.transact ;对端 system_server/他进程 负载或锁高
- 动作:慢 Binder 后台化+超时/重试;结果缓存与幂等;拆小 payload、降频
- 渲染管线瓶颈
- 判断: ThreadedRenderer.nSyncAndDrawFrame 、 eglSwapBuffers 、 BufferQueue ;大量丢帧/Davey
- 动作:降低每帧工作量(布局/绘制/动画);控制位图尺寸与上传;合并绘制批次
扩展阅读
堆栈详解
进程头(Header)
-----
Cmd line: com.abc.android.xyz.launcher Build fingerprint: 'device/product:11/XYZ/20250817:user/test-keys' ABI: 'arm' Build type: optimized Libraries: ... libandroidndkgif.so ... libmmkv.so ... (省略) Heap: 28% free, 60MB/84MB; 508568 objects
- pid : 进程 ID。用于与 ActivityManager: PID: 、 CPU usage 等对齐。
- Cmd line : 进程名/包名。例如: com.abc.android.xyz.launcher 。
- Build fingerprint : 设备/系统构建指纹,定位系统版本、ROM 差异。
- ABI : 架构,如 arm/arm64 ,决定符号/库位数。
- Build type : user/userdebug/eng ,影响日志与调试能力。
- Zygote loaded classes / Classes initialized : ART 类加载与初始化统计,通常用于诊断冷启动问题。
- Intern table / JNI : 运行时内部状态,只在极端内存/JNI 泄漏分析时参考。
- Libraries : 已加载本/三方 .so 列表。用于判断是否命中某个 native 组件。示例:
Libraries: ... base.apk!/lib/armeabi-v7a/libandroidndkgif.so ... libmmkv.so ... libwebviewchromium.so ...
- Heap : 28% free, 60MB/84MB; 508568 objects ,表示堆使用率、对象数,可用于判断是否因 GC 频繁导致抖动。
- GC timings : GC 各阶段耗时直方图,定位 GC 压力。
线程头(Thread Header)
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x7217f360 self=0xe5800e10
| sysTid=2739 nice=-10 cgrp=default sched=0/0 handle=0xee056470
| state=R schedstat=( 184997268070 19701663585 123413 ) utm=14650 stm=3849 core=4 HZ=100
| stack=0xff639000-0xff63b000 stackSize=8192KB
| held mutexes=
- name : 线程名,如 "main" 。
- prio : Java 层线程优先级(数值越小优先级越高)。
- tid : ART 线程 ID(与 Linux sysTid 不同)。
- Native/Waiting/Runnable : 标识当前在 native/Java 或等待/可运行等。
- group : 线程组名。
- sCount/dsCount : suspend 计数/调试 suspend 计数。
- flags : 线程标志位(内部调度用途)。
- obj/self : Java 对象地址/ART 线程对象地址(调试符号分析用)。
- sysTid : Linux 线程 ID(/proc 可查询)。
- nice : 调度 nice 值(-20 至 19,值越小优先级越高)。
- cgrp : 所属 cgroup(调度/资源控制组)。
- sched : 调度策略/参数(如 0/0 代表 CFS 默认)。
- handle : 线程句柄地址。
- state : 线程状态: R (Running)、 S (Sleeping)、 D (Uninterruptible IO)、 T (Stopped)、 Z (Zombie)。
- schedstat : (sum_exec_runtime run_delay? switches) 的内核统计,单位纳秒/次数,用于大致估算调度与运行时间。
- utm/stm : 用户态/内核态 CPU 时间(以 HZ 为时间基,如 HZ=100 即 1tick=10ms)。
- core : 最近运行的 CPU 核心 ID。
- stack/stackSize : 栈地址范围与大小,用于识别栈溢出风险。
- held mutexes : 当前持有的互斥锁列表(为空表示无持有)。
堆栈帧(Frames)
native: #00 pc 00011476 .../base.apk!libandroidndkgif.so (... SimpleGCTGifEncoder::reduceColor+254)
...
at com.waynejo.androidndkgif.GifEncoder.nativeEncodeFrame(Native method)
at com.abc.android.xyz.utils.BitmapUtilsKt.saveGIF(SourceFile:423)
- native: : 本地帧,包含指令地址(pc)、库名、符号与偏移。可用于符号化与火焰图。
- at com...nativeEncodeFrame(Native method) : Java → Native 桥接调用点。
- at com...saveGIF(SourceFile:423) : 业务 Java/Kotlin 方法栈,直观定位业务代码位置(含 SourceFile:行号)。
Waiting Channels(等待通道)
-----
Cmd line: com.abc.devicestat
sysTid=2559 SyS_epoll_wait sysTid=2578 do_sigtimedwait sysTid=2579 futex_wait_queue_me ... sysTid=2591 binder_ioctl_write_read
- 字段说明:
- pid :本段快照对应的进程 ID;需先确认它是否为目标进程或关键对端(如 system_server )。
- Cmd line :该进程的进程名/包名(示例为 com.abc.devicestat )。
- sysTid=XXXX :该进程内每个线程的系统 TID 与其当前“等待通道/系统调用”。
- 常见等待通道解读:
- SyS_epoll_wait :事件循环的空闲等待,通常表示线程在等事件/IO,常见于非问题线程。
- do_sigtimedwait :Signal Catcher 等待信号,多见于框架线程,通常可忽略。
- futex_wait_queue_me :futex 锁等待,可能提示锁竞争/死锁,需要结合持锁方线程栈进一步分析。
- binder_ioctl_write_read :Binder 线程在收发/等待对端,若主线程在 transact ,应联动查看对端进程(如 system_server )的 Binder 线程与服务栈。
- 其他:如 read/write/fsync/__io_schedule (I/O 等)、 poll_schedule_timeout (超时轮询)等,结合上下文判断。
- 使用建议:
- 优先查看“目标进程”和 system_server 的 Waiting Channels;其他进程仅在它作为关键对端时值得深挖。
- 将等待通道与线程头的 state (如 D/S/Blocked/Waiting )和 CPU usage 的 iowait / kernel% 一起对照,判断是 I/O 等待、锁等待还是跨进程阻塞。
用户态 vs 内核态:含义与在 ANR 排查中的价值
- 是什么
- 用户态(user space) :应用与大多数 Java/Kotlin/业务 native 代码运行的空间。系统调用需“陷入”内核。
- 内核态(kernel space) :内核与驱动、VFS、Binder 驱动、网络栈、调度器等执行空间。
- 在哪儿看
- 日志: ActivityManager: CPU usage 行:如 101% user + 12% kernel + 0.4% iowait + 0.6% irq + 0.3% softirq 。
- 堆栈:线程头的 utm/stm :分别是该线程的用户态/内核态累计 CPU 时间; state=D 常伴随 I/O 等待。
- 怎么解读
- user% 高 :多为应用计算密集(编解码、图片处理、复杂布局/绘制、JSON 解析、加解密、热循环)。
- 方向:移出主线程、降采样/缓存、算法/结构优化、采样/Trace 找热点。
- kernel% 高 :频繁系统调用或内核繁忙(频繁小块 read/write/fsync 、Binder 调用密集/大 payload、 ioctl 图形/相机、锁争用落到内核)。
- 方向:合并/批量化 I/O、减少系统调用次数、优化 Binder 交互、检查对端系统服务/驱动路径。
- iowait 高 :在等块设备 I/O;常见于同步文件访问、大量缺页/磁盘抖动。
- 方向:异步 I/O、内存/磁盘缓存、预加载,禁止主线程磁盘访问。
- irq/softirq 高 :中断负载(网络/存储/显示)偏高,系统层面拥塞。
- 方向:关注全局负载,避开热点时段,降低频繁中断源带来的抖动。
- 与 ANR 的关系
- 输入分发 ANR :目标进程 user% 高 + 主线程重计算栈,或 iowait 偏高 + 主线程卡在 read/write/ioctl/epoll 。
- 跨进程阻塞 :应用 user% 不高,但 system_server 或对端进程 user/kernel% 升高,主线程在 transact ;应联合对端堆栈定位。
- 小示例(解读口径)
- 113% 目标进程: 101% user + 12% kernel → 以应用计算为主,少量内核开销;若主线程在图片/GIF 编码,优先迁移后台并降采样/分片,让出主线程。