本文为 Android App ANR 系列的第二篇,主要分享 ANR 分析套路和关键 Log 介绍,系列文章目录如下
- Android App ANR 系列 1 :理解 Android ANR 设计思想
- Android App ANR 系列 2 :ANR 分析套路和关键 Log 介绍
- Android App ANR 系列 3 :ANR 案例分享
版本说明(2026-10 核查):本文保留历史设备日志和截图;AOSP 字段解释以 android-15.0.0_r1 为核对基线,旧 ART 枚举另标 Android 11。厂商日志、阈值和字段顺序须对照设备版本,不能直接套到所有 Android 系统。下方引文来自 2015 年,保留原貌:ANR 记录归属应用不等于根因在应用,也不只涉及主线程;低发生率本身不能决定是否忽略问题。
ANR(Application Not Responding),应用程序无响应,简单一个定义,却涵盖了很多 Android 系统的设计思想
首先,ANR 属于应用程序的范畴。这不同于 SNR(System Not Respoding),SNR 反映的问题是系统进程(system_server)失去了响应能力,而 ANR 明确将问题圈定在应用程序。SNR 由 Watchdog 机制保证,具体可以查阅 Watchdog 机制以及问题分析; ANR 由消息处理机制保证,Android 在系统层实现了一套精密的机制来发现 ANR,核心原理是消息调度和超时处理
其次,ANR 机制主体实现在系统层。所有与 ANR 相关的消息,都会经过系统进程(system_server)调度,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理。 一旦应用程序处理消息不当,超时限制就起作用了,它收集一些系统状态,譬如 CPU/IO 使用情况、进程函数调用栈,并且报告用户有进程无响应了(ANR 对话框,部分 Rom 不显示 ANR 对话框,而是直接闪退到主界面)
然后,ANR 问题本质是一个性能问题。ANR 机制实际上对应用程序主线程的限制,要求主线程在限定的时间内处理完一些最常见的操作(启动服务、处理广播、处理输入), 如果处理超时,则认为主线程已经失去了响应其他操作的能力。主线程中的耗时操作,譬如密集 CPU 运算、大量 IO、复杂界面布局等,都会降低应用程序的响应能力
最后,部分 ANR 问题是很难分析的。有时候由于系统底层的一些影响,导致消息调度失败,出现问题的场景又难以复现。 这类 ANR 问题往往需要花费大量的时间去了解系统的一些行为,超出了 ANR 机制本身的范畴。有一些 ANR 问题很难调查清楚,因为整个系统不稳定的因素很多,例如 Linux Kernel 本身的 Bug 引起的内存碎片过多、硬件损坏等。这类比较底层的原因引起的 ANR 问题往往无从查起,并且这根本不是应用程序的问题,浪费了应用开发人员很多时间,如果你从事过整个系统的开发和维护工作的话会深有体会。所以我不能保证了解了本章的所有内容后能够解决一切 ANR 问题,如果出现了很疑难的 ANR 问题,我建议最好去和做 Framework、驱动和内核的朋友聊聊,或者,如果问题只是个十万分之一的偶然现象,不影响程序的正常运行,我倒是建议不去理它
– From duanqz
ANR 分析套路
先按 ANR 类型整理证据:线上看 Android Vitals 或自研监控中的 Input dispatch、Service、Broadcast、JobScheduler 等类型、设备版本和影响范围;本地及时抓 bugreport、EventLog、main/system log。/data/anr/anr_* 的直接读取受设备权限限制,不能假定普通量产机可 adb pull。怀疑调度、Binder、锁、I/O 或渲染时,要让 Perfetto/System Trace 覆盖超时之前的时间段。
Android 11(API 30)起可查 ApplicationExitInfo,但它是历史退出记录:时间戳是进程退出时间,ANR trace 可能已被覆盖;应用从 ANR 恢复后因其他原因退出,也可能带着此前的 trace。不要把它当作所有 ANR 的实时清单,详见系列第一篇。
分析时分别检查应用逻辑与系统依赖,再追踪两者之间的等待关系。系统压力可能放大应用原有的同步等待,不能只凭某个进程 CPU 高就决定归属。
ANR 问题一般的分析步骤如下:
拿到一份 ANR trace,先问一句:这是“第一案发现场”吗?日志、CPU 采样和线程 dump 未必停在同一时刻,时间没对上,后面很容易越查越偏。
- 先按包名、PID、用户、设备启动周期和
am_anrreason 对齐事件,防止拿错 trace。区分业务开始、超时检测、CPU 采样、线程 dump 和日志写出的时间;am_anr不保证是故障开始时刻,dump 也可能晚于阻塞现场。 - 搜索
ANR in,记录责任进程、原因、CPU 采样起止时间、各进程/线程占用、Load、PSI 和缺页计数。日志打印者可能是system_server,不要与责任进程混淆。 - 把超时前后相关的启动、解锁、进程变化、慢消息、锁竞争和 Binder 信息放到同一时间线上。可以先把关键行提取到一个文件里,用 VS Code 或 Notepad++ 搜索线索:是重启后第一次解锁,还是短时间内多个应用启动?沿关键线程的等待继续找持锁者、Binder 服务端、调度或 I/O;检查证据是否覆盖整个超时窗口。
- 证据不足时保留未知,在可复现环境补业务日志和 trace。新增采集也会有开销,不要用事后单张堆栈补写未观察到的过程。
区分是应用的问题还是系统的问题
首先应该分析是否是应用的问题
分析应用的问题的关键是需要理清当时用户的操作是什么,应用在用户这个操作的过程中扮演了什么角色,然后再进行进一步的分析
- 分析应用是否关键组件中的生命周期中有耗时操作,可能平时没有暴露出来,一旦系统负载上来,就会暴露问题(建议在关键生命周期函数中加上对应的 Log,方便 Debug)。
- 分析是否出现极端情况,导致应用的逻辑耗时,比如大量的数据处理或者导入,同时运行线程过多等(看应用的 cpu \ io 使用情况)。
- 分析是否存在死锁。
- 分析是否是在等待 binder 返回。
- 分析 Trace 文件中 MainThread 和 RenderThread 是否存在异常。
- 分析 Trace 文件中 MainThread 跟 WorkerThread 是否存在等待关系。
分析系统的状态
- 检查 CPU 使用率、Load 和调度等待,定位
system_server、Audio、SurfaceFlinger 等相关进程。HeapTaskDaemon是应用 ART 的 GC 相关线程,不是独立系统服务。 - 检查 I/O 延迟和
/proc/pressure/io;minor/major 是缺页计数,不是内存访问次数或 I/O 吞吐量,具体见下文。 - 将
dumpsys meminfo、内存 PSI、回收/换页、kswapd和杀进程记录对齐。旧设备可能用内核 lowmemorykiller,现代 Android 常用用户态lmkd,还要区分内核 OOM。 - 怀疑冻结时检查设备的 freezer 状态和解冻日志。D 表示不可中断等待,不能单独证明冻结;旧内核的
__refrigerator栈是 freezer 线索,仍需确认触发者和时间。xxxHansManager是厂商线索,不是 AOSP 通用标签。 - 怀疑系统服务异常时,跟进 Binder 服务端与持锁线程的栈及调度。高负载只能提示调查方向,必须说明它如何阻塞本次超时操作。
继续分析:应用可以解决还是系统可以解决
- 等待链落在系统或其他进程时,提交对应版本、时间线、栈和 trace,让负责该依赖的团队继续定位,不能把“转系统”等同于无解。
- 应用侧仍需评估能否移出主线程、减少持锁范围或避免同步等待;能规避不等于根因就在应用。
- 只有证据能连接超时操作与具体阻塞原因时再归因。缺少系统权限或现场数据,就说明尚未定位。
ANR Log 分析
Log 可由测试使用 Log 工具抓取,可由线上平台获取,也可以自己抓取:
1 | adb bugreport log.zip |
ANR Trace Log 所包含的内容如下
线程详细数据
1 | "main" prio=5 tid=1 Native |
上面 Trace 中 部分字段的含义

图是旧版说明,个别数值来自另一份样例,字段应以实际 dump 为准。按 Android 15 ART 读取时:
tid=1是 ART 内部线程 ID,sysTid=5991才是 Linux TID。prio、nice、sched分别来自不同层次,不能互换。sCount是当前 ART suspend 计数,不是历史累计暂停次数,也不证明应用被系统冻结。图中dsCount的旧说明不适用于所有版本;Android 15 输出的是ucsCount,不能据此推断曾连接调试器。schedstat三项依次是累计 CPU 运行时间(ns)、运行队列等待时间(ns)、运行时间片次数。它们不是这次 ANR 窗口内的值;ART 读取失败也可能输出0 0 0,不要把缺失当零。utm/stm是用户态/内核态 clock ticks,按输出的HZ换算;本例HZ=100。core是最近运行的 CPU 编号,不表示永久绑核。
线程堆栈
下面是该线程对应的调用堆栈
1 | native: #00 pc 00000000000c6418 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8) |
栈同时包含 Native 和 Java 帧。这里的 nativePollOnce 表示抓栈时在等待事件;它不能证明整个超时窗口都空闲,也不能排除异步广播等其他线程上的超时。先核对 ANR 类型和抓栈时刻。
线程状态

Java 的 Thread.State、ART 状态和 Linux 调度状态是不同层次。RUNNABLE 或 ART Native 不保证线程正在占用 CPU;还要看 state=S/R/D 与调度轨迹。下面的旧枚举与 Android 11 对应;Android 15 已改为 enum class,例如 kRunnable=0,不能依赖旧数值或把旧表当完整的新版本状态表。
1 | // State stored in our C++ class Thread. |
线程名后的 Blocked 是 ART 状态。图中持锁者 89 是 ART tid,应在同一 dump 中找对应线程再映射 Linux TID;一条等待关系不等于死锁,需要继续检查是否形成等待环。

正常主线程 Trace
1 | "main" prio=5 tid=1 Native |
该样例仅说明抓栈时处于事件等待,不能据此排除此前的阻塞。
异常主线程 Trace
1 | "main" prio=5 tid=1 Blocked |
这里的锁等待需要继续追踪持锁线程;是否造成 ANR 取决于持续时间和等待链。
CPU 使用率分析
搜索 ANR in 后,先读 CPU 采样区间,不能统一称为“ANR 前”。下面是 2020 年旧日志及解释性注释,later 区间可能包含检测之后的诊断工作;两个区间的百分比不能直接相加。缺页注释已按 Linux 定义纠正,原统计数值保留。
1 | ActivityManager: ANR in com.xxx.launcher (com.xxx.launcher/.Launcher)(进程名) |
按 Android 15 ProcessCpuTracker,进程/线程的用户态和内核态 CPU 时间以采样 uptime 时长为分母。多线程并行时,进程出现 191% 或 290% 并不矛盾;TOTAL 则按聚合 CPU 时间计算,分母不同。faults 是采样间的增量。应把这些统计与关键线程调度对齐,不能仅靠进程总量归因。
CPU 负载
1 | ActivityManager: Load: 15.29 / 5.19 / 1.871 |
Load 是约 1、5、15 分钟尺度的平均活跃任务数,包含可运行任务和不可中断等待任务;不是 CPU 利用率。CPU 刚空闲时历史平均值仍可能很高。0.7、1、5 都不能作为手机通用的告警线,需要结合在线核心、调度限制和 I/O 等待判断。大小核的能力、频率和热限制不同,八核不能简单理解为单核能力乘八。Linux loadavg 定义
/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state(若内核提供)记录各频率驻留时间,不是核心忙碌时间,也不是应用 CPU 时间;统计从驱动初始化或重置开始。上游文档的 usertime 单位为 10 ms,厂商扩展须核对实现。参考 CPUFreq Stats。上面的独立摘录为 1.871,前一完整日志为 1.87,这里保留原摘录精度,不据此计算新结论。
Memory 负载
1 | ActivityManager: ----- Output from /proc/pressure/memory -----(内存压力) |
这是 /proc/pressure/memory 的 PSI 输出;已把旧文转录的 somavg10 修为 some avg10,数值未改。avg10/60/300 是对应时间尺度的平滑压力百分比;total 是累计停滞时间,单位为微秒(µs),不是毫秒。
some 表示至少有任务因该资源停滞,full 表示所有非 idle 任务同时因该资源停滞。这里度量的是资源压力,不能把百分比直接读成 CPU 使用率。Linux PSI 文档
下面用两个任务的 I/O 等待示意 some/full:

图中红色表示 I/O 阻塞;理想化的 60 秒区间里,同时阻塞占 16.66%,至少一个阻塞占 50%。这只是区间比例示意,不是内核 avg60 平滑算法的直接计算结果。阻塞可以分散在多段时间内,如下图。

旧图解延伸阅读:内核工匠。
IO 负载
1 | ActivityManager: 30% 5991/com.xxx.launcher: 23% user + 6.4% kernel / faults: 118172 minor(无需从存储加载页面的缺页) 2 major(需要从存储加载页面的缺页) |
- Minor fault:处理时不需要从存储加载页面的缺页,例如页面已在内存、但需要建立映射。它不是 CPU cache miss,也不是每次内存访问都会发生。
- Major fault:需要从存储加载页面的缺页。它不覆盖所有 I/O,例如普通文件读写未必产生进程 major fault。
因此 118172 minor / 2 major 是采样窗口的缺页增量,不能换算成 I/O 次数或带宽。大量 major 是进一步检查缺页延迟、I/O PSI 和关键线程等待的线索,不能单独证明本次 ANR 由存储造成。Linux /proc/pid/stat 定义
进程负载
1 | ActivityManager: 30% 5991/com.xxx.launcher: 23% user + 6.4% kernel / faults: 118172 minor(无需从存储加载页面的缺页) 2 major(需要从存储加载页面的缺页) |
23% user 与 6.4% kernel 分别是用户态和内核态占用。总计显示 30% 涉及格式化精度,不应要求已舍入的各项相加后逐位相等。分母和多核解释见前面的 CPU 使用情况。
CPU 异常进程
以下是排查方向,不是凭进程名即可成立的原因:
system_server高占用:找具体线程,以及本次业务等待的服务/锁。kswapd活跃:核对内存回收、换页和关键线程是否被拖慢。logd高占用:检查日志量和实际写日志耗时,不能推断所有进程都同步阻塞在日志输出。vold高占用:沿存储操作和 Binder 依赖找等待。- 应用自身高占用:分清主线程、工作线程,以及调度竞争;应用低 CPU 也可能因锁或 Binder 等待而 ANR。
- 应用线程为 D:继续读阻塞栈;冻结判断见前文,不能仅凭 D 状态归因功耗策略。
- CPU 行前
+/-:表示采样跟踪中的新增/移除状态,不是精确的进程创建/死亡时间戳。结合进程生命周期日志确认。
系统关键 Log 介绍
应用冻结
1 | xxxHansManager : freeze uid: 10245 package: com.tencent.mm reason: LcdOff |
这条厂商日志给出 UID、包名和 LcdOff 原因,只是一次冻结操作线索。需要结合进程状态、解冻时间和超时窗口判断是否有关,不能直接推出冻结有 Bug。Android 11 起的 AOSP cached apps freezer 使用 cgroup v2,不能与旧内核 __refrigerator 栈或厂商 Hans 日志视作同一实现。
ActivityManager : Slow operation
下面保留旧版 AMS 的 checkTime 示例。Android 15 的相应进程启动检查可见 ProcessList.checkSlow,超过 50 ms 后按检查点输出从 startTime 起的累计耗时;并非所有 AMS 操作都会自动接受这项检查。
1 | void checkTime(long startTime, String where) { |
1 | ActivityManager: Slow operation: 138ms so far, now at startProcess: done updating battery stats |
三行 138ms so far 可以来自同一次操作的连续检查点,不是三个步骤各耗时 138 ms。频繁出现值得与调度、锁和 I/O 对齐,但它本身不证明 ANR 根因。
Looper : Slow dispatch
1 | Looper : Slow dispatch took 418ms main h=android.app.ActivityThread$H c=android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA m=0 |
418ms 是这次 dispatchMessage 的 uptime 耗时(结束减开始),包含执行、等待和被抢占,不等于 CPU 运行时间。h/c/m 分别帮助定位 Handler、callback、what。只有相应慢日志配置启用且达到阈值才输出,不是固定 500 ms 的全局规则。Android 15 Looper
Looper : Slow delivery
1 | Looper : Slow delivery took 361ms android.ui h=com.android.server.am.ActivityManagerService$UiHandler c=null m=53 |
361ms 是开始分发时间减 msg.when,表示超过计划执行时间多久,不是消息从创建/入队到执行的总时长。延迟投递的消息本来就有未来的执行时间;需继续找它到期后为何没被及时分发。
Looper:Slow Looper
1 | W/Looper: Slow Looper main: Activity com.androidperformance.memoryfix/.MainActivity is 439ms late (wall=0ms running=0ms ClientTransaction{ callbacks=[android.app.servertransaction.TopResumedActivityChangeItem] }) because of 3 msg, msg 2 took 268ms (seq=2 running=207ms runnable=15ms late=1ms h=android.app.ActivityThread$H w=110), msg 3 took 171ms (seq=3 running=140ms runnable=5ms io=1ms late=268ms h=android.app.ActivityThread$H w=159) |
这是厂商扩展格式。late/wall/running/runnable/io 和消息历史如何统计,应查该 ROM 的实现,不能直接当作 AOSP Looper 的稳定接口。样例可提示前序消息占用,但没有完整 trace 时不要把这些字段拼成确定的根因。
onTrimMemory
1 | ClockTag AlarmClockApplication: onTrimMemory: 80 |
80 是旧的 TRIM_MEMORY_COMPLETE 等级,不是 80 MB 或 80%。官方 API 文档说明:Android 14(API 34)起不再通知该等级,常量在 API 35 废弃。因此这条旧日志可帮助读历史现场,不能作为新系统必然出现的低内存告警,也不证明随后一定被杀。
dvm_lock_sample
1 | dvm_lock_sample:[system_server,1,Binder_9,1500,ActivityManagerService.java,6403,-,1448,0] |
这是旧式九字段样例:进程名、敏感线程标志、等待线程名、等待毫秒数、等待方文件/行号、持锁方文件/行号、采样百分比。- 只表示文件名相同,不证明锁对象就是 AMS 本体;应按对应系统源码找两处位置。最后的 0 与常见非零采样条件不一致,原始设备实现不明,保留数值,不补猜采样策略。
它不是“等待一超过 500 ms 就实时输出”。ART 在获得锁后记录已完成的竞争,阈值由运行时配置控制,阈值以下也可概率采样;Android 15 格式还含方法名,不能用旧九字段位置硬解新日志。Monitor、LogContentionEvent
binder_sample
1 | 6628 6628 I binder_sample: [android.view.accessibility.IAccessibilityManager,6,2010,com.xxx.community,100] |
这条旧日志的 PID/TID 都为 6628,说明记录线程是主线程。按 EventLogTags,列表含接口描述符、transaction code、耗时(ms)、blocking package、采样百分比。样例为 IAccessibilityManager、code 6、2010 ms、com.xxx.community、100%。保留字段定义不代表所有版本都开启这项采集,也没有通用的 500 ms 阈值。
transaction code 必须查同版本生成的 Stub/Proxy 或 AIDL。下面是旧 Android API 30 源码视图,FIRST_CALL_TRANSACTION + 5 对应 6,在该版本映射到 addAccessibilityInteractionConnection;不要把这个编号跨版本当固定方法名。耗时也不等于服务端纯 CPU 时间。

Long monitor contention
1 | 16809 14216 W system_server: Long monitor contention with owner InputDispatcher (17039) at android.content.res.Configuration com.android.server.wm.ActivityTaskManagerService.getGlobalConfigurationForPid(int)(ActivityTaskManagerService.java:1066) waiters=0 in boolean com.android.server.wm.WindowProcessController.hasActivities() for 141ms |
art/runtime/monitor.cc
1 | std::string Monitor::PrettyContentionInfo(const std::string& owner_name, |
日志写出线程是 TID 14216,记录的持锁者是 InputDispatcher (17039)。141ms 是锁等待耗时,不是持锁者的 CPU 时间;waiters=0 是记录点的计数,不能解释为从未有人等待。上面保留旧版格式化代码,字段应按相同 ART 版本核对;Android 15 Monitor 在竞争结束后记录等待信息。
Binder Thread
1 | E IPCThreadState: binder thread pool (31 threads) starved for 120 ms |
这表示该进程的 libbinder 线程池出现饱和线索,31 是此处配置值,不是所有进程的通用上限。Android 15 IPCThreadState 在执行线程数达到上限时开始计时,降到上限以下后,若超过 100 ms 则打印。它不表示所有 Binder 通信都停止,也不能单独证明死锁;继续查看池内线程的锁、同步调用与耗时。
am_kill
1 | am_kill : [0,18555,com.sina.weibo.image,945,remove task] |
这是旧的五字段样例:用户 0、PID 18555、进程名、OomAdj 945、原因 remove task;已去掉旧文末尾多出的转录字符 1。Android 15 EventLogTags 还增加了 RSS 字段,解析器须匹配版本。以下五张图是旧实现的 kill 路径/原因索引,保留作历史查阅,不是当前版本所有杀进程路径的完整清单。
force-stop

异常杀进程

主动杀进程

调度杀进程

其他杀进程

由框架相应 kill 路径产生的 am_kill 与所有进程死亡不是一回事。Process.killProcess(pid)、shell 发信号或 LMK/lmkd 的行为未必经过该路径,但仍可能有其他日志;发信号受 UID、权限和 SELinux 等限制,不能杀任意 PID。
缺少 am_kill 也可能是日志覆盖或采集不全。要结合死亡、退出和内核记录,不能从“没这条日志”反推出一定由某种方式杀死。
am_proc_died
1 | am_proc_died: [0,13210,com.xxx.gallery3d,935,19] |
按 Android 15 EventLogTags,字段是用户、PID、进程名、OomAdj、ProcState。这里的 935/19 不是死亡原因;该事件表示系统观察到进程死亡,还要关联 am_kill、crash、lmkd 或退出记录。
am_anr
1 | am_anr : [0,8769,com.android.updater,952680005,Broadcast of Intent { act=android.intent.action.BOOT_COMPLETED flg=0x9000010 cmp=com.android.updater/.BootCompletedReceiver (has extras) }] |
字段依次是用户、PID、进程名、ApplicationInfo flags 和 reason。本例用户 0、PID 8769、com.android.updater,reason 指向 BOOT_COMPLETED 广播。952680005 不是超时时长;Intent 的 flg=0x9000010 与前面的应用 flags 也不是同一字段,且未设置 0x10000000(FLAG_RECEIVER_FOREGROUND)。应按实际版本、广播标志及接收路径判断超时规则,不凭十六进制看起来大就套短期限。日志时间是事件记录时间,不等于广播开始时间。
Lowmemorykiller
1 | lowmemorykiller: Kill 'com.heytap.themestore' (15545), uid 10136, oom_adj 995 to free 105068kB |
这是旧设备的内核 lowmemorykiller 格式,不代表现代 lmkd 总会在 kernel log 打印。首行列出进程名、PID 15545、UID 10136、标作 oom_adj 的 995 和预计回收量 105068 kB。995 不能按旧 Linux oom_adj 的 -17~15 范围解码;需核对厂商实现是否使用 oom_score_adj 量纲。
第二行没有 PID,不能仅靠相邻两行就认定同一受害进程,或把 606 无条件解释为 min_adj。日志中的预计/报告回收量也不保证等于系统最终净释放的物理内存。现代低内存调查可参考 AOSP lmkd。
Blocked msg
这是内部/厂商增补日志,旧文的 1.5 s 是该采集实现的慢消息门槛,不是 AOSP 通用 ANR 超时。cost=32436 ms 是消息处理耗时,不等于 CPU 时间;Current msg List 是当时待处理队列,不是已经执行完的消息列表。when 的负数表示相对预定执行时间的差值,不能直接当每条消息的 CPU 耗时。what=110/114/121 等编号需按对应版本 ActivityThread.H 查义。
1 | E ANR_LOG : >>> msg's executing time is too long |
通用场景 Log 介绍
灭屏 Log
关键是 power_screen_broadcast_done、screen_toggled 和 power_screen_state
1 | 1810 2470 I intercept_power: [ACTION_DOWN,1,0] |
以上是旧日志序列。power_screen_broadcast_done 的三项是 on、广播耗时(ms)、wakelockCount;611 是广播流程耗时,不是屏幕面板熄灭延迟。power_screen_broadcast_send: 1 的 1 是 wakelockCount,不是亮屏标志。Android 15 Notifier
亮屏 Log
关键是 power_screen_broadcast_done、screen_toggled 和 power_screen_state
其中
power_screen_state
- offOrOn|1|5
- becauseOfUser|1|5
- totalTouchDownTime|2|3
- touchCycles|1|1
- latency|1|3
1 | 1810 2470 I intercept_power: [ACTION_DOWN,1,0] |
同理,633 ms 是亮屏广播耗时,不是从按键到屏幕可见的总延迟。上面的 |1|5 等是 EventLog 字段类型/单位元数据,不是日志中的实际数值。厂商 power_screen_state 的 reason/latency 还要核对写入点,不能把该旧序列视为每台设备必有的日志。
解锁完整 Log
wm_set_keyguard_shown 的值
- (keyguardShowing|1) :Keyguard 是否在显示
- (aodShowing|1) :aodShowing 是否在显示
- (keyguardGoingAway|1) :Keyguard 消失
- (Reason|3) :原因
1 | 1810 5711 I wm_set_keyguard_shown: [1,0,1,keyguardGoingAway] |
这是旧四字段格式。Android 15 的 wm_set_keyguard_shown 已包含 DisplayId、keyguardShowing、aodShowing、keyguardGoingAway、occluded、Reason 六项,不能按旧位置解析。setKeyguardShown 是调用原因,不证明桌面已呈现或可交互;还要对齐窗口、绘制和输入证据。WindowManager EventLogTags
通知栏下拉完整 Log
1 | 2768 2768 I sysui_multi_action: [757,803,799,panel_open,802,1] |
sysui_multi_action 的数字是版本相关的 Metrics 编码,应查对应定义。notification_visibility 的顺序为 key、visible、lifespan、freshness、exposure、rank;这些时长字段不是事故的绝对时间戳。visible=1 提示通知可见,不能单独还原一次完整手势。
通知栏折叠完整 Log
1 | 2768 2768 I sysui_multi_action: [757,111,758,2] |
这里 notification_visibility 的 visible 变为 0。日志开关和版本会影响可见内容;没有这几行不能证明用户没展开/收起通知栏。
应用启动完整 Log
1 | // 创建 Task |
原摘录有 0615 20615、8803 18803 等疑似缺失首位的 PID 文本,未取得原文件,不补造数字;定位进程需结合 am_proc_start 和完整现场。wm_activity_launch_time 的 471 ms 是该版本启动统计点,不保证包含完全绘制或可交互的全部阶段。
Window Focus 相关的流程
1 | // 从桌面启动 App,focus 变化 :Launcher => null => App |
短暂 Launcher → null → App 可以是普通窗口切换。要判断 NoFocusedWindow ANR,需结合等待焦点事件、有无 focused application、无焦点持续时间及对应超时规则,不能看到 null 就归因。焦点日志还受调试开关影响,细节见系列第一篇。
ANR 相关资料分享
- 西瓜视频稳定性治理体系建设一:Tailor 原理及实践
- 西瓜视频稳定性治理体系建设二:Raphael 原理及实践
- 西瓜视频稳定性治理体系建设三:Sliver 原理及实践
- 西瓜卡顿 & ANR 优化治理及监控体系建设
- 今日头条 ANR 优化实践系列 - 设计原理及影响因素
- 今日头条 ANR 优化实践系列 - 监控工具与分析思路
- 今日头条 ANR 优化实践系列分享 - 实例剖析集锦
- 今日头条 ANR 优化实践系列 - Barrier 导致主线程假死
- 今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待
- Android ANR|原理解析及常见案例
参考资料
- https://duanqz.github.io/2015-10-12-ANR-Analysis#1-%E6%A6%82%E8%A7%88
- https://duanqz.github.io/2015-10-12-ANR-Analysis
- http://gityuan.com/2016/12/02/app-not-response/
- http://gityuan.com/2017/01/01/input-anr/
- https://xiaozhuanlan.com/topic/5097486132
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球 : 历史介绍(已停止运营)
一个人可以走的更快 , 一群人可以走的更远