3. 卡顿与ANR的定义及分类

在 Android 稳定性工程中,很多团队把“卡”“慢”“无响应”“闪退”“内存爆”混在一起讨论,导致定位方向完全跑偏。实际上,卡顿、掉帧、Jank、ANR、Crash、OOM、Watchdog 分属不同的故障域:有的属于渲染性能问题,有的属于主线程调度问题,有的属于进程级崩溃,有的属于系统级守护触发。只有先把概念边界划清,才能选对监控指标、日志入口和修复手段。

核心判断链:用户感知到的“卡” → 是画面不流畅(帧问题)?还是操作无响应(主线程阻塞)?还是进程直接消失(Crash/OOM)?还是系统强制重启(Watchdog)?四类问题的证据链完全不同。

一、概念辨析:七个高频术语的边界

卡顿(Lag / Stutter)是用户主观感受的统称,指界面响应迟缓、滑动不跟手、动画不连贯。它是一个体验结论,不是技术根因。卡顿可能由掉帧引起,也可能由主线程耗时任务、I/O 阻塞、锁竞争引起。

掉帧(Dropped Frame)是渲染管线的客观事实:在 60Hz 屏幕上,每帧预算约 16.6ms;在 120Hz 屏幕上约 8.3ms。当某一帧未能在 VSync 周期内完成合成,这一帧就被丢弃,表现为画面停顿或跳跃。掉帧是可量化指标,常用 FrameMetrics、JankStats、gfxinfo 采集。

Jank 通常指“可感知的帧延迟”,比单纯掉帧更贴近用户感受。Android 官方用 Jank 描述帧耗时超过阈值导致的视觉不连续。注意:掉帧不一定被用户感知,Jank 强调可感知性。

ANR(Application Not Responding)是主线程在规定时间内未完成输入事件或广播处理,系统弹出“应用无响应”对话框。典型触发条件:输入事件 5 秒未处理、前台广播 10 秒、后台广播 60 秒、ContentProvider 发布 10 秒等。ANR 是系统判定,不是用户主观感受。

Crash 是进程异常终止,包括 Java 未捕获异常、Native 信号(SIGSEGV、SIGABRT)、系统杀进程等。Crash 的直接证据是 tombstone 或 AndroidRuntime 日志。

OOM 分两层:Java 层 OutOfMemoryError 是堆内存分配失败;LMK(Low Memory Killer)触发的进程被杀是系统内存压力下的回收行为。前者是应用崩溃,后者常表现为“后台被杀”“冷启动变慢”。

Watchdog 是系统级或应用级守护机制。系统 Watchdog 监控关键服务(如 system_server)是否卡死,超时则重启系统;应用也可自建 Watchdog 监控主线程心跳。Watchdog 触发往往意味着更严重的系统性阻塞。

用户感知“卡”背后的四类故障域 从主观体验到客观证据:帧、主线程、进程、系统守护 用户感知 卡 / 慢 / 无响应 / 闪退 渲染帧域 掉帧 · Jank · 卡顿 指标:帧耗时 / 掉帧率 工具:FrameMetrics / JankStats 阈值:16.6ms / 8.3ms 主线程调度域 ANR · 输入超时 · 广播超时 指标:主线程阻塞时长 证据:traces.txt / ANR 日志 进程崩溃域 Crash · OOM · 被杀 证据:tombstone / OOM 日志 类型:Java / Native / LMK 系统守护 Watchdog system_server 关键服务卡死 超时重启 证据链对照: 帧问题 → gfxinfo / FrameMetrics / Perfetto 帧轨道 | ANR → traces.txt 主线程栈 + CPU 占用 | Crash → tombstone / logcat | OOM → dumpsys meminfo / LMK 日志 关键原则:先归类故障域,再选采集工具;不要用 ANR 日志解释掉帧,也不要用帧率数据解释进程被杀。

二、用户感知指标:从“我觉得卡”到“数据能证明”

用户不会说“我的帧耗时 32ms”,他们只会说“滑动像幻灯片”。因此需要把主观感受映射为可采集指标:

  • 帧耗时(Frame Duration):单帧从开始到完成的时间,超过 VSync 周期即掉帧。
  • 掉帧率(Dropped Frame Rate):掉帧数 / 总帧数,适合衡量滑动、动画场景。
  • Jank 率:可感知卡顿帧占比,Android 官方推荐用 JankStats 采集。
  • 输入延迟(Input Latency):从触摸事件到画面反馈的时间,直接影响“跟手”感。
  • ANR 率:ANR 次数 / 启动次数或使用时长,属于稳定性核心指标。
  • Crash 率 / OOM 率:崩溃与内存溢出占比,通常按版本、机型、场景维度聚合。
  • Watchdog 触发次数:系统级或应用级守护触发,代表严重阻塞。

三、案例场景:同一次“卡死”如何被误判

某电商 App 在低端机上出现“点击商品详情后卡住 3 秒,然后闪退”。团队最初按“卡顿”优化,只做了布局层级裁剪,问题依旧。重新按故障域拆解:

  1. 用户点击后主线程执行了同步数据库查询,阻塞超过 5 秒,系统先判定 ANR;
  2. ANR 期间应用仍在分配大图,触发 OOM,进程被 LMK 杀死;
  3. 用户看到的是“卡住然后闪退”,但根因是主线程 I/O + 内存峰值,不是渲染掉帧。

如果一开始只盯帧率,就会错过 traces.txt 中的主线程栈和 meminfo 中的内存曲线。正确做法是:先看 ANR 日志确认主线程阻塞点,再看 OOM 日志确认内存峰值,最后才评估渲染层是否需要优化。

四、误区与复盘:常见概念混淆清单

误区 错误理解 正确认知
把 ANR 当卡顿 ANR 只是“卡得久一点” ANR 是系统判定,有明确超时阈值和日志证据
把掉帧当 Jank 掉帧数高就一定卡 掉帧是客观事实,Jank 强调用户可感知
把 OOM 当 Crash 都是闪退,不用区分 OOM 有内存分配失败或 LMK 杀进程证据,修复方向不同
把 Watchdog 当 ANR 都是无响应 Watchdog 监控系统关键服务,触发后果更严重
只看平均值 平均帧率 55fps 就合格 要看 P95/P99 帧耗时和 Jank 率,长尾才是体验杀手
排障时先问三个问题:是画面不流畅,还是操作没反应?是进程消失,还是系统重启?是内存问题,还是主线程阻塞?这三个问题能把问题快速归入帧域、主线程域、进程域或系统守护域。

五、采集入口与关键参数

不同故障域对应不同采集命令和日志入口,下面给出常用组合:

# 帧与 Jank 采集
adb shell dumpsys gfxinfo com.example.app framestats
adb shell dumpsys SurfaceFlinger --latency

# ANR 日志与主线程栈
adb shell cat /data/anr/traces.txt
adb bugreport

# Crash / Native 崩溃
adb logcat -b crash
adb shell ls /data/tombstones/

# 内存与 OOM
adb shell dumpsys meminfo com.example.app
adb shell dumpsys activity lru

# 系统 Watchdog
adb logcat -s Watchdog

关键参数上,60Hz 屏幕单帧预算 16.6ms,120Hz 为 8.3ms;ANR 输入事件超时 5s,前台广播 10s,后台广播 60s;这些阈值是判断“是否越界”的硬标准。实际工程中,建议把帧耗时 P95、Jank 率、ANR 率、Crash 率、OOM 率、Watchdog 触发次数作为稳定性看板的核心六项指标,按版本和机型维度持续跟踪。

概念辨析的最终目的不是背定义,而是建立“症状 → 故障域 → 证据 → 修复手段”的映射。卡顿与 ANR 只是用户感知入口,真正的工程动作发生在帧管线、主线程调度、内存管理和系统守护四个层面。