ANR定位手册

前言摘要

本文面向 Android 客户端工程实践,按"速查→定位→交叉验证→归因与修复"的顺序梳理 ANR 排查方法。全文配套可执行步骤、日志示例与术语释义,以 Reason 与主线程 Busy/Waiting 判别为主线,并结合系统信号进行交叉验证,帮助在有限时间内给出清晰结论与化治理方案。

速查清单

  • 确认ANR : 在日志中搜索 ActivityManager: ANR inam_anrReason:
  • 找原因(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 framesOpenGLRenderer DaveyCPU usage 升高、SurfaceFlinger 弹出 ANR 层。
  • 初步归因与止血: 若为主线程重任务/同步 IO,先降级/关闭路径或临时异步化,阻断复现;随后进入深度分析。

深入分析路径

第 1 步:确认 ANR 与类型

ANR 分类与触发条件

  • 输入分发 ANR(Input dispatching timed out,常见 5s)
    • 触发:目标窗口未在时限内处理输入/焦点/按键事件,日志含 server is not respondingWaited 5000ms for FocusEvent/KeyEvent
    • 常见原因:主线程重计算/同步 IO/锁竞争/跨进程阻塞,导致消息队列无法及时取出事件。
  • BroadcastReceiver 超时(前台 10s / 后台 60s)
    • 触发:onReceive 未在时限内返回,日志常见 Broadcast of Intent ... timeoutBroadcastQueue 相关栈。
    • 常见原因:在 onReceive 内做网络/磁盘/解码等耗时,或间接等待锁/跨进程调用。
  • Service 执行超时(前台 20s / 后台 200s)
    • 触发:Service 执行过久或 onStartCommand/onBind 等未及时处理完成,日志常见 executing service/Service timeout for ...
    • 常见原因:主线程执行长任务、死循环/锁等待、长 Binder 调用、未切到后台线程。
  • ContentProvider 发布/调用超时(10s)
    • 触发:ContentProvideronCreate 发布或在 query/insert 中长时间阻塞,日志常见 ContentProvider not responding/Timeout executing ContentProvider
    • 常见原因:Provider 中进行磁盘/网络重操作或初始化复杂组件放在主线程。

第 2 步:从 Reason 锁定"谁卡了谁"

  • 关注 WindowManager/InputDispatcher 的超时日志,通常包含:
    • 目标窗口/组件(如 .../TargetActivity
    • 角色(server is not responding 通常指应用端未响应)
    • 事件与等待时长(如 Waited 5003ms for FocusEvent(hasFocus=false)
plain 复制代码
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.doTraversalChoreographer Skipped framesOpenGLRenderer Davey
    • 处理方向:
      • 重任务移出主线程(Dispatchers.Default/Dispatchers.IO、线程池、WorkManager
      • 降采样/缓存/批处理;分片+让步(处理 N 个单元后 post/yield
      • 主线程启用 StrictMode 禁同步 I/O;渲染路径减负(减少无效 invalidate、控制位图尺寸/上传)
  • B) 主线程在等(Waiting)
    • 总判据:state=Blocked/Waiting/D,或栈顶出现等待/系统调用/跨进程桥接
    • 常见子类(表现 / 处理方向):
      • 同步等待/锁竞争:Object.waitReentrantLock.lock/awaitCountDownLatch.awaitFuture.getThread.join
        • 处理:UI 线程禁同步等待;缩小临界区与锁粒度;加超时/降级,改回调/异步
      • 跨进程(Binder)阻塞:BinderProxy.transact,调用 ContentResolver/PackageManager/WindowManager/Media 等;对端(如 system_server)负载高
        • 处理:慢 Binder 后台化并设超时/重试;结果缓存与幂等;拆小 payload、降频
      • 同步 I/O 阻塞:read/write/fsync/epollSQLiteDatabase.queryOkHttp.execute;系统侧 iowait 偏高或 state=D
        • 处理:统一 IO 线程池/异步;合并/批量写;内存/磁盘缓存;主线程启用 StrictMode
      • 渲染/图形等待:ThreadedRenderer.nSyncAndDrawFrameeglSwapBuffersSurface/BufferQueue、sync-fence;伴随丢帧/Davey
        • 处理:降低每帧工作量;控制位图尺寸与上传;合并绘制批次
      • GC/堆锁等待:日志含 "Waiting for GC to complete",或类表锁;GC 直方图偏高
        • 处理:降分配/对象复用;避免主线程创建大对象与巨图
      • 窗口/输入管线同步等待:Input event dispatching timed out 等待 FocusEvent/KeyEventViewRootImpl 布局/relayoutWindow 同步点停滞
        • 处理:切页/焦点变更路径移除重任务与阻塞;初始化与 I/O 外移;分片让步
      • 死锁/循环依赖:多线程/跨进程互等(互持锁、互相 join/await/transact
        • 处理:统一锁顺序、拆锁/顺序化桥接;跨进程调用加超时;必要时重构打环
plain 复制代码
"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 弹窗创建时间与上述信号对齐,可作为时间锚点
plain 复制代码
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; ...
plain 复制代码
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
plain 复制代码
D SurfaceFlinger: createLayer: name=Application Not Responding: com.abc.android.xyz.launcher

第 5 步:归因与修复

  • 主线程在干重活
    • 判断:栈现 encode/decode/measure/layout/draw 等重计算;进程 user% 高;伴随丢帧/Davey
    • 动作:重任务出主线程(Dispatchers.Default/Dispatchers.IOWorkManager);降采样/缓存;分片让步
  • 同步 IO 阻塞(文件/网络/数据库)
    • 判断:栈现 read/write/fsync/epollSQLiteDatabaseOkHttp.execute;线程 state=D 或系统 iowait 偏高
    • 动作:全量改异步/IO 线程池;合并/批量写;启用 StrictMode 禁主线程 IO;必要时降级
  • 锁竞争/死锁
    • 判断:Blocked/Waiting + wait/await/join/get;多线程互等或锁顺序不一致
    • 动作:缩小临界区、拆锁/顺序化;回调/异步替代等待;所有等待加超时;必要时重构打环
  • 跨进程阻塞(Binder)
    • 判断:栈顶 BinderProxy.transact;对端 system_server/他进程 负载或锁高
    • 动作:慢 Binder 后台化+超时/重试;结果缓存与幂等;拆小 payload、降频
  • 渲染管线瓶颈
    • 判断:ThreadedRenderer.nSyncAndDrawFrameeglSwapBuffersBufferQueue;大量丢帧/Davey
    • 动作:降低每帧工作量(布局/绘制/动画);控制位图尺寸与上传;合并绘制批次

扩展阅读

堆栈详解

进程头(Header)

plain 复制代码
----- pid 2739 at 2025-09-02 12:03:36 -----
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 组件。示例:
plain 复制代码
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)

plain 复制代码
"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)

plain 复制代码
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(等待通道)

plain 复制代码
----- Waiting Channels: pid 2559 at 2025-09-02 12:03:38 -----
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 usageiowait/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 编码,优先迁移后台并降采样/分片,让出主线程。
相关推荐
用户2018792831673 小时前
🎨 Android View背景选择:Shape、PNG与SVG的奥秘
android
大马力拖拉机3 小时前
经验之谈-Fragment中监听返回键
android
张可3 小时前
Kotlin 函数式编程思想
android·前端·kotlin
悟乙己3 小时前
如何区分 Context Engineering 与 Prompt Engineering
android·java·prompt
4Forsee4 小时前
【Android】从复用到重绘的控件定制化方式
android
翻滚丷大头鱼4 小时前
android View详解—自定义ViewGroup,流式布局
android·数据结构
胖虎14 小时前
Android入门到实战(八):从发现页到详情页——跳转、传值与RecyclerView多类型布局
android·recyclerview·多类型布局