首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
ANR 上报了一堆,能定位的没几个:一份够用的记录该包含什么
📅 2026/10/15 4:08:27
✍️ 爱科研究院
👁 阅读 3,247
先看一份真实记录。系统明确判定发生了 ANR但交到手上的内容只有这些ANR_EXCEPTION Find process anr, but unable to get anr message. main prio5 tid1 Native | stateS schedstat( 612313101 492845062 1829 ) | utm38 stm22 core2 HZ100 native: ... __epoll_pwait native: ... android::Looper::pollOnce at android.os.MessageQueue.nativePollOnce(Native method)从这份记录里能确认的只有一件事采集这一刻主线程正在 Looper 里等待。系统当时在等什么、异常发生在哪一段时间、主线程依赖谁一个都答不上来。我在《主线程明明是空闲的为什么仍然 ANR3 个真实 Trace 的证据链复盘》下称案例篇里把这种情况归到 D 级现有证据不足。但那篇回答的是拿到这样的记录之后怎么写结论。这里要往前问一步为什么会拿到这样的记录答案通常不在分析侧在采集侧。系统在判定 ANR 的那一刻手上掌握的信息相当完整只是在把现场交到我们手上的过程中该留的东西没留下来。分析的上限由采集决定。一份缺了 Reason、缺了时间线、只有一段栈的记录换谁来读结论都只能停在证据不足。一、先分清两个词ANR 记录和 ANR 现场系统判定 ANR 时它知道这些事在等什么Reason、等的是哪个进程和组件、期限多久、超时那一刻主线程在做什么。这份信息要经过好几道手才变成后台里那条ANR 记录系统判定 - 系统采集线程 Trace - 平台或第三方 SDK 抓取 / 接收 - 序列化与压缩 - 上报 - 存储与展示每过一道都可能掉一部分。掉到最后最常见的结果就是一段主线程栈 一个进程名。定因需要什么和上报里通常有什么是两张不同的表定因需要常见上报里有缺了会怎样完整 Reason含后半句限定有时只有ANR三个字母方向定不下来几条路径并列事件时间线 T0–T3通常只有上报时间无法判断 Trace 有没有拍中现场主线程在等谁锁 / Binder / 窗口只有采样瞬间的栈只能写当时在这写不出因果同期系统状态Load / CPU / 内存大多没有系统侧的放大因素无法确认或排除前后几十秒的消息与生命周期没有空闲栈分不清恢复之后还是从头没卡过这张表右列越空左列越无从下手。案例篇那三份样本之所以两份收在证据不足不是分析不够细是右列本来就空着。二、四个时间点现在普遍只剩一个一次 ANR 从发生到进你的后台至少经过四个时间点T0 真正的异常开始 T1 达到系统响应期限ANR 条件成立 T2 system_server 完成线程 Trace 采集 T3 平台整理、上报、落库需要解释的是 T0 到 T1 这一段。单份线程栈描述的是 T2。上报时间记录的是 T3。只留得下 T3 的时候T0 到 T2 之间发生了什么就只能靠猜。猜出来的每个版本都自洽但都没法验证。四个点各自怎么留T1判定时刻。系统侧 Trace 文件通常带判定时间平台侧一般也会记录。这一项丢得最少。T2采样时刻。Trace 自带时间戳但口径不统一有的写墙钟有的写 uptime有的用微秒时间戳。案例篇第一个样本里Trace 原始时间是1716654492626784按微秒解释落在00:28:12.626而 CPU 统计区间结束在00:28:11.750最终日志输出在00:28:20.58。三个时间点分别来自三种口径不换算到同一基准就没法比较。采集侧至少要做一件事把时间戳连同它的口径一起记下来别只存一个裸数字。T3上报时刻。平台一般都有但要注意它和 T1 之间可能差几秒到几分钟。这段时间足够主线程恢复正常也足够窗口状态变完。T0异常起点。这一个系统给不了。它只在超过期限之后才判定不记录应用侧的慢积累事后能补的只有一段系统级 CPU 区间说不出应用是从哪一刻开始慢下来的。T0 只能应用自己埋。慢消息记录、生命周期打点、锁等待计时都是为了让 T0 有来源。缺了它就只能对着 T1 和 T2 之间那段空白编故事。三、Reason 是最该保住的一项也是最容易丢的Reason 决定调查方向。案例篇第二个样本就栽在它丢了。上报里写着unable to get anr message于是输入超时、Service 超时、广播超时、JobService、前台服务未及时前台化五条路径并列哪条都走不通。而且 Reason 的后半句往往比前半句值钱。同样是Input dispatching timed out后面接does not have a focused window→ 问题在窗口焦点建立主线程空闲不矛盾后面接窗口已有焦点但事件处理不完 → 才轮到查长消息、锁、Binder前半句只是分类后半句才是方向。上报里只留前半句等于留了个标题。Reason 有三个来源各自的丢失场景不一样来源能拿到什么什么时候会丢系统 Trace 文件/data/anr/完整 Reason含后半句限定线上应用读不到该路径记录被后续 ANR 覆盖平台只截取了线程栈部分没带文件头ActivityManager 日志ANR in pkg与 Reason 原文日志被轮转或截断平台只采应用自身日志没采系统日志段ApplicationExitInfoAPI 30 起退出原因分类、系统 Trace、时间戳保留条数有限、会被后来的记录覆盖分类是REASON_ANR不等于完整 Reason 文本三个来源都会失效只是失效的场合不同。只依赖一个就会在它恰好失效的那一次丢干净。可落地的三条应用侧在ApplicationExitInfo可读时尽早读、限长、带时间戳落盘。读取代码在《Android ANR 到底怎么定位从触发原理到日志判断的完整方法》下称方法篇第六节这里不重复。平台侧把系统日志ActivityManager 段配进采集范围别只采应用自身日志。上报体里给 Reason 单开一个字段。塞进一个大字符串里后面任何一次截断都可能把它切掉。四、Trace 是快照用事件序列补上那段时间Trace 只能证明采样那一刻这是它天生的限制改不掉。能补的是应用侧的事件序列把 T0 到 T2 这段填上。要补的不是完整堆栈是什么时候发生了什么事主线程每条消息的分发起止时间加上 Message 目标what、callback 类名Activity / Fragment 的生命周期窗口焦点变化锁等待与 Binder 同步调用代价允许的话承载它的结构是环形缓冲固定容量只留最近 N 秒或 N 条新记录覆盖旧的ANR 或卡顿阈值触发时整段带出去。采集方式有取舍常见几种方式能拿到什么代价Looper.getMainLooper().setMessageLogging(Printer)每条消息的起止文本每条消息都拼字符串开销可观自定义 Printer只记时间戳和引用起止时间、目标标识小但要绕开字符串拼接Choreographer 帧回调掉帧、帧间隔只反映渲染侧拿不到非 UI 消息子线程轮询主线程卡住时的栈精度取决于轮询间隔且只在卡住时才有输出setMessageLogging是上手最快的一种但它对每条消息都做一次字符串拼接消息越密这部分开销越明显。这也是为什么它更适合开发期验证不适合线上长期开着。更省的做法是绕开字符串在 Printer 里只取时间戳和 Message 的标识拼装留到异步线程。这样主线程上的工作就剩下几个赋值。五、采集本身必须轻否则监控会变成新的 ANR 源这条在方法篇第六节提过一句监控本身必须轻量。这里把代价说清楚。在主线程上做下面这些事本身就是在给 ANR 添砖同步生成完整堆栈new Throwable()之后printStackTrace()或者getStackTrace()逐帧拼字符串同步序列化 JSON、写文件、落数据库同步等待上传或网络返回正确的分工是主线程取时间戳、记对象引用、塞进队列 - 只做这几个 异步线程拼字符串、序列化、压缩、落盘、上传再加两条容易忽略的采样而不是全量。不是每条消息都要记可以按阈值或采样率。环形缓冲本身也是采样的一种它只保证出事时手上有最近一段不保证每一条都留。监控要能远程关。线上排查最怕的情形是怀疑监控本身有问题却没法在不发版的情况下把它关掉于是只能关掉监控再看看。留一个远程开关比事后争论有没有影响要省事得多。六、无人值守设备采集优先级和手机不一样企业平板、手持终端、POS、广告机这类设备和普通手机 App 的 ANR 采集约束是不同的没有用户点击。输入超时类 ANR 少Service、广播、前台服务、保活拉活相关的更多。而保活和拉活本身就是高 ANR 风险操作Service 启动超时、startForegroundService()后没及时进前台都在这条线上。没有用户反馈。手机上的 ANR 用户会骂、会截图这类设备出问题现场不会有人告诉你。漏采一次就是永久丢失。设备在客户手里。拿不到机器也就没有复现一下再看看这条路。必须一次采全。长期运行。记录会滚内存更紧环形缓冲的容量要比手机上保守。进程会被杀、会被拉活。谁拉起的、隔了多久起来这些本身就是要留的证据。所以这类设备的采集优先级要往前挪Reason 和 T1、T2 时间点从应该有变成必须留因为没有第二次机会。另外建议单独记一类自愈动作。什么时候检测到异常、做了什么重启服务、重新拉活、降级有没有把问题掩盖掉。自愈和采集是一对矛盾修得越勤越可能把原始现场盖住。把动作记下来事后才能判断这条记录是一次失败还是第十次失败。七、一张够用的最小采集清单把上面几节收成一张表可以直接拿去对照自己现有的上报系统项字段来源优先级触发事实完整 Reason含后半句限定系统 Trace 头 / ActivityManager 日志必须进程与组件包名、进程名、Activity / Service / ReceiverReason 上下文必须版本Android 版本、targetSdk、厂商 ROM应用自采必须四个时间点T0应用埋、T1、T2、T3系统 应用必须时间戳口径每个时间点用的是墙钟、uptime 还是微秒采集侧标注必须主线程采样ART 状态 Linux state Java 与 native 栈系统 Trace必须前若干秒事件消息分发起止、生命周期、焦点变化应用环形缓冲强烈建议系统环境Load、CPU 区间、内存、GC平台建议依赖对象锁等待、Binder 对端、窗口状态应用 系统有条件时对着这张表检查一遍历史记录大致能看出问题出在哪一层Reason 常年为空说明采集源选错了时间点只有一个说明采集侧没做对齐栈永远停在nativePollOnce说明缺的是应用侧的事件序列不是系统的栈。回到那三件事方法篇最后把 ANR 分析收成三件事系统在等什么、异常覆盖了哪段时间、哪条依赖链解释了超时。这三件事都是分析侧的。而它们能不能被回答取决于采集侧有没有把对应的事实留下来系统在等什么对应 Reason。采集侧没保住分析侧就只能并列几条路径覆盖了哪段时间对应四个时间点。只留一个时间线就永远是断的哪条依赖链对应采样栈加前后事件。只有一段快照因果就无从建立所以采集不是分析的准备工作它就是分析的一部分。把上面这张清单填满比在只有一段栈的记录上多花三个小时要值。系列文章Android ANR 到底怎么定位从触发原理到日志判断的完整方法主线程明明是空闲的为什么仍然 ANR3 个真实 Trace 的证据链复盘参考资料Android DevelopersANRAndroid DevelopersApplicationExitInfoAndroid Developers系统跟踪概览
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/15 4:08:26
【Agent与工作流实战】如何避免不同用户的信息混在一起:设计会话标识与数据隔离
2026/10/15 4:03:26
Codex对比Claude Code
2026/10/15 4:03:26
想做成品类冠军可以找哪家品牌咨询公司?
2026/10/15 5:58:36
2026年IT就业市场分化:大模型应用开发成高薪赛道,供需剪刀差决定薪资
2026/10/15 5:58:36
从零到一:技术博客的标题、正文、关键词与摘要写作指南
2026/10/15 5:58:36
Vue+Python实现社区停车管理系统:从车位状态到并发控制
2026/10/15 5:58:36
Python手写数值积分:复化梯形、辛普森与龙贝格算法原理与避坑指南
2026/10/15 5:58:36
论文写作辅助工具实测:从选题到答辩的九类工具选型指南
2026/10/15 5:53:36
EMC/EMI/EMS 指什么
2026/10/15 0:02:59
AI Coding时代的柔性之美:从静态软件到自演化软件,软件研发正在重新进化
2026/10/15 0:02:59
亨得利客户服务中心官方网点分布一览:2026年全国多城门店地址与最新联系电话公布
2026/10/15 0:02:59
防关联浏览器哪个好?三款产品实测差异解析
2026/10/13 12:25:09
流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南
2026/10/14 18:35:19
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别
2026/10/14 14:52:59
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容
2026/10/15 1:07:55
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/15 5:07:59
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/11 23:43:10
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)