Android稳定性-51-Android 稳定性问题复盘模板:现象、证据、根因、修复和预防
稳定性问题复盘如果只写“问题现象、原因分析、解决方案”,通常很快就会变成缺陷单的加长版。它可能记录了发生过什么,却没有让团队下次更快发现、更准定位、更早预防。真正有价值的复盘,不是为了证明谁对谁错,而是把一次故障转化为可以复用的工程经验。
Android 稳定性问题尤其需要复盘。因为很多问题不是单点代码错误,而是跨 App、Framework、Native Service、HAL、Kernel、测试环境、版本节奏和监控能力的组合结果。一次 system_server Watchdog,表面是系统卡死,背后可能是锁竞争、Binder 调用、I/O 阻塞、低内存、供应商服务超时和测试场景叠加。复盘如果只停在“修复某处死锁”,下个版本可能还会以另一个形态出现。
这篇给一套可直接使用的问题复盘方法。重点放在现象、证据、根因、修复和预防五个部分,但每个部分都要写到能被别人复查和复用。
一、复盘要先定义边界
复盘开始前,先确认这次复盘到底复盘什么。是复盘一个缺陷,还是一类问题;是复盘技术根因,还是复盘版本决策;是复盘测试漏检,还是复盘线上止损。边界不清,复盘很容易发散成所有人都能补充一句,但没有结论。
建议在复盘文档开头写四个边界:时间边界、版本边界、影响边界、责任边界。时间边界说明问题从什么时候被引入、什么时候被发现、什么时候被修复。版本边界说明影响哪些 build、哪些渠道、哪些机型。影响边界说明用户或测试路径受到什么影响。责任边界说明哪些团队参与复盘,不是为了追责,而是为了确认后续动作谁能落地。
一个清楚的边界示例:
1 | 复盘对象:V6.1.3 灰度阶段锁屏后偶发 system_server Watchdog |
有了边界,后面的证据和改进才不会漂移。
二、现象要写用户视角和系统视角
稳定性问题的现象至少要写两层。第一层是用户或测试看到的表现:黑屏、卡死、重启、闪退、无响应、耗电、发热、连接失败。第二层是系统记录到的表现:进程 crash、ANR traces、Watchdog、dropbox、tombstone、pstore、binder wait、I/O blocked、thermal throttling。
只写用户视角,无法定位;只写系统视角,别人看不懂影响。两层都写,复盘才能连接质量影响和技术证据。
| 视角 | 应写内容 | 示例 |
|---|---|---|
| 用户视角 | 操作路径、可见表现、影响后果 | 锁屏 5 分钟后按电源键无响应,约 60 秒后自动重启 |
| 测试视角 | 场景、脚本、设备、概率 | 20 台 A05,锁屏唤醒循环 12 小时,2 台复现 |
| 系统视角 | 日志、进程、时间线 | Watchdog: PowerManagerService monitor blocked 60s |
| 版本视角 | 首现版本、关联改动 | V6.1.1 合入 sensor suspend 优化后出现 |
现象写清楚后,复盘读者不需要再翻缺陷系统才能理解问题。
三、证据要能还原时间线
复盘里的证据不是贴几段日志。好的证据能还原问题发生前、发生中、发生后的时间线。
建议时间线至少包含:触发动作、异常前状态、第一条异常日志、系统决策、用户可见表现、恢复或失败结果。时间戳要统一,最好使用设备时间,并说明是否和服务器时间有偏差。
1 | 14:02:10 测试脚本进入锁屏待机,设备电量 78%,温度 34C |
这类时间线比单独贴 Watchdog 栈更有价值,因为它能暴露因果顺序。很多复盘失败,就是把最后一条错误当成根因。
四、根因要区分直接原因、触发条件和系统性原因
根因分析最常见的问题,是把直接原因当成全部原因。比如“PowerManagerService 持锁等待 sensor 返回”是直接原因,但为什么会持锁等待,为什么测试前没有发现,为什么灰度才暴露,为什么监控没有提前告警,这些才决定预防动作。
建议根因拆成三层。
第一层是直接技术原因:哪段代码、哪个状态机、哪个资源等待导致异常。第二层是触发条件:什么场景、设备、配置、压力组合让问题出现。第三层是系统性原因:评审、测试、监控、准入、知识库中哪个环节没有提前识别风险。
| 层级 | 说明 | Watchdog 示例 |
|---|---|---|
| 直接原因 | 导致故障的技术点 | PowerManagerService 持锁等待 vendor sensor flush |
| 触发条件 | 问题出现所需条件 | 锁屏待机 + 特定 sensor 固件 + 低温恢复场景 |
| 系统性原因 | 流程或能力缺口 | suspend/resume 专项没有覆盖该固件批次 |
只有三层都写,复盘才能从修 bug 走向防复发。
五、修复方案要说明为什么这样改
复盘中的修复方案不要只贴 commit。要写清楚修复思路、替代方案、风险和验证点。
比如 Watchdog 案例里,可能有几个方案:缩短 vendor sensor 等待时间、把等待移出锁、改成异步回调、在 suspend 前增加状态检查、回滚低功耗优化。最终选择哪一个,要说明原因。如果只是写“已修复锁等待”,别人下次看不出为什么不是简单加 timeout。
一个好的修复描述可以这样写:
1 | 修复方案:PowerManagerService 不再在持有全局锁期间同步等待 sensor flush 完成,改为记录 pending 状态并由异步回调更新 suspend 结果。vendor sensor 超过 2s 未返回时记录 error event,但不阻塞系统睡眠主流程。 |
这种写法能帮助后续维护者理解改动意图。
六、验证要覆盖原场景、相邻场景和反向风险
稳定性问题修复后,不能只复测最初路径。要覆盖三类场景。
原场景验证用于证明问题不再复现。相邻场景验证用于证明同一链路的其他入口没有类似问题。反向风险验证用于证明修复没有引入新的问题。
以锁屏 Watchdog 为例,原场景是锁屏待机唤醒循环;相邻场景包括来电唤醒、闹钟唤醒、指纹解锁、低电量待机、弱网待机;反向风险包括待机功耗是否劣化、sensor 状态是否异常、唤醒耗时是否增加。
| 验证类型 | 场景 | 通过标准 |
|---|---|---|
| 原场景 | 20 台 x 24h 锁屏唤醒循环 | 无 Watchdog,无同类 blocked 日志 |
| 相邻场景 | 来电、闹钟、指纹、低电量待机 | 无唤醒失败和输入无响应 |
| 反向风险 | 待机功耗、sensor 状态、唤醒耗时 | 不劣于基线 |
验证结论要写样本量和版本号。不要只写“回归通过”。
七、完整案例:一次 Watchdog 复盘
下面把前面的内容串成一个完整案例。
V6.1.3 灰度第 7 小时,线上监控出现 2 台 A05 设备 watchdog 重启。用户反馈是锁屏后按电源键无响应,随后设备自动重启。灰度比例只有 1%,但 Watchdog 属于基础可用性问题,版本团队立即暂停扩大灰度。
证据收集包括:dropbox watchdog 记录、bugreport、pstore、bootreason、灰度上报事件、最近合入列表。两台设备的 Watchdog 栈都指向 PowerManagerService.monitor(),system_server 主线程持有 power lock,另一个 binder 线程等待 sensor service 返回。vendor log 显示 sensor flush 在特定固件版本上偶发超过 60 秒。
时间线显示,问题不是重启后才出现的随机异常,而是在锁屏流程中先发生 sensor flush 长等待,随后 PowerManagerService 持锁阻塞,最后 Watchdog 触发。关联改动是 V6.1.1 合入的低功耗优化:为了确保 suspend 前 sensor 状态一致,Framework 同步等待 vendor 回调。
根因拆分如下:直接原因是 PowerManagerService 在持有全局锁时同步等待 vendor sensor flush;触发条件是 A05 某批 sensor 固件在低温恢复后 flush 偶发超时;系统性原因是低功耗优化评审时只覆盖了常温待机场景,没有覆盖该固件批次和低温恢复,也没有把 sensor flush 耗时纳入灰度监控。
修复采用异步等待方案,避免阻塞 power 主流程;vendor 侧同时修复 flush 超时。验证分三轮:实验室 30 台 x 48 小时锁屏唤醒循环,低温箱 10 台 x 12 小时恢复测试,G1 灰度 24 小时观察。验证期间无 Watchdog,sensor flush 超时降为 0,待机功耗与上一版本持平。
预防动作包括:新增 suspend/resume 专项用例,覆盖低温恢复和不同 sensor 固件;灰度监控增加 sensor_flush_timeout 事件;Framework 评审规则增加“系统锁内不得等待 vendor 长耗时回调”;案例沉淀到 Watchdog 案例库。
这个复盘的结论不是“某模块修了一个 bug”,而是明确了代码规则、测试覆盖和监控指标三类预防动作。
八、复盘会议要控制讨论结构
复盘会议不要从“谁来解释一下”开始。建议按固定顺序推进:先确认事实,再确认根因,再确认修复,再确认预防。事实阶段不讨论责任,根因阶段不讨论排期,预防阶段不重新争论事实。
会议前,测试 owner 应该准备时间线、证据索引、影响范围和问题状态。研发 owner 准备根因假设、修复方案和风险。版本 owner 准备发布影响和决策记录。质量 owner 准备流程改进建议。
会议结束必须产出 action item,不能只产出共识。每个 action item 要有 owner、截止时间、验收方式。
九、常见误判
第一,把最后报错的模块当成根因。Watchdog 报 PowerManagerService,不代表 PowerManagerService 一定是唯一根因;它可能在等待 vendor、I/O 或 Binder。
第二,把复现困难当成无法分析。低概率问题更需要时间线、版本差异和环境差异,而不是等到稳定复现才开始行动。
第三,把修复合入当成复盘完成。复盘完成的标志是预防动作落地,包括用例、监控、规则或知识沉淀。
第四,把复盘写成追责材料。追责会让信息变少,工程复盘要让事实变多。
第五,只关注技术原因,不看准入和监控。很多线上问题不是没人修,而是发布前没有识别出风险,发布后没有及时止损。
十、检查清单
- 复盘对象是否有清楚的时间、版本、影响和参与边界。
- 现象是否同时包含用户视角和系统视角。
- 证据是否能还原异常前、中、后的时间线。
- 根因是否区分直接原因、触发条件和系统性原因。
- 修复方案是否说明选择理由、替代方案和引入风险。
- 验证是否覆盖原场景、相邻场景和反向风险。
- 结论是否包含版本号、样本量、设备范围和通过标准。
- 预防动作是否落到用例、监控、准入、代码规则或案例库。
- 每个后续动作是否有 owner、截止时间和验收方式。
- 复盘材料是否能让未参与问题的人读懂并复用。
十一、输出物模板
1 | # 稳定性问题复盘:<版本 + 场景 + 现象> |
十二、把复盘动作转成门禁和用例
复盘里最容易流失的是预防动作。会议上大家都同意“后续加强覆盖”,但一个月后没人知道加强了什么。预防动作必须落到具体载体:自动化用例、准入门禁、日志告警、代码评审规则、平台脚本或案例库条目。
以 Watchdog 复盘为例,预防动作可以这样落地:自动化用例增加锁屏唤醒循环和低温恢复;准入门禁增加 system_server Watchdog 为硬阻断;日志告警增加 sensor flush 超时事件;代码评审规则增加系统锁内禁止等待 vendor 长耗时回调;案例库新增 power-sensor suspend 条目。每一项都有不同 owner,也有不同验收方式。
| 复盘发现 | 落地载体 | 验收方式 |
|---|---|---|
| 锁内等待 vendor 回调 | 代码评审规则 | 新增 checklist,模块 owner 审阅 |
| 低温恢复未覆盖 | 自动化专项 | CI 或周度长稳包含该场景 |
| 灰度无 flush 耗时指标 | 监控事件 | 看板能按版本统计超时次数 |
| 新人不熟悉 Watchdog 路径 | 案例库 | 可通过 watchdog/power/sensor 检索 |
复盘 owner 应该在问题关闭后一周内检查这些动作是否进入相应系统。没有进入系统的预防动作,通常会在忙碌中消失。
十三、复盘数据口径要统一
复盘经常因为数据口径不一致产生争议。测试说复现 3 次,研发说只看到 1 次;线上说 Crash 率上升,版本说实验室没有异常。很多时候不是谁错了,而是统计窗口、设备范围、版本范围、去重规则不同。
复盘材料里要写清数据口径。比如复现次数是按设备计、按 run 计,还是按用户事件计;Crash 率的分母是活跃设备、启动次数,还是场景次数;重启是否排除了手动重启、低电关机和刷机;ANR 是否只统计目标进程,还是包含系统进程。口径一旦清楚,争议会少很多。
1 | 数据口径示例: |
统一口径不是为了让数据更好看,而是让复盘结论可比较。下次同类问题发生时,团队才能判断风险是变大还是变小。
十四、复盘质量也要审阅
复盘文档写完后,最好有一次轻量审阅。审阅重点不是文字是否漂亮,而是事实是否完整、证据是否支撑结论、预防动作是否可验收。
可以由质量 owner 或领域专家做 15 分钟审阅,按清单打回不合格复盘。常见打回原因包括:没有时间线、根因只写了直接原因、验证没有样本量、预防动作没有 owner、结论没有版本范围、把猜测写成事实。
| 审阅项 | 合格标准 |
|---|---|
| 事实 | 时间、版本、设备、影响范围清楚 |
| 证据 | 日志和结论之间能对应 |
| 根因 | 直接原因、触发条件、系统性原因分开 |
| 修复 | 写明取舍和引入风险 |
| 验证 | 有样本量、场景和通过标准 |
| 预防 | 有 owner、截止时间和验收方式 |
复盘质量审阅会让文档少一些故事,多一些可复用的工程信息。
十五、复盘要保留已排除路径
很多复盘只记录最终根因,不记录排除过什么。这样会丢掉大量定位经验。实际上,已排除路径对后续同类问题非常有价值。它能告诉别人哪些方向看过、凭什么排除、当时用了什么证据。
比如相机黑屏最终根因在 provider 会话释放,但复盘中也应该记录:已排除 App 权限,因为权限状态正常且多个 App 复现;已排除 SurfaceFlinger 死亡,因为进程未重启且其他 layer 正常;已排除低内存,因为 meminfo 和 lmk 日志无异常。下次遇到类似问题,排查者可以直接复用这些排除方法。
已排除路径要避免写成“不是某模块问题”这种结论式表达。更好的写法是“检查了什么证据,证据显示什么,因此暂时排除什么”。这比简单甩锅更专业,也更容易被其他团队接受。
十六、复盘里的责任不是追责
稳定性复盘必须谈责任,但责任不是追责。责任的含义是:哪个环节可以改变现状,哪个团队可以落实预防动作,哪个角色要确认验收结果。如果复盘把责任理解成找错人,参与者会倾向于少说、晚说、模糊说;如果责任被定义为行动归属,复盘会更容易得到真实信息。
可以把责任分成四类。技术责任是代码、配置、架构或供应商实现的问题归属。验证责任是测试覆盖、样本选择、场景设计的问题归属。监控责任是线上指标、日志字段、告警规则的问题归属。决策责任是准入、灰度、回滚和遗留审批的问题归属。一次事故可能四类责任都有,但每一类的改进动作不同。
| 责任类型 | 关注问题 | 改进动作 |
|---|---|---|
| 技术责任 | 为什么代码或配置会触发故障 | 修复、重构、评审规则 |
| 验证责任 | 为什么测试没有提前发现 | 用例、样本、专项矩阵 |
| 监控责任 | 为什么线上没有及时发现 | 埋点、日志、告警、看板 |
| 决策责任 | 为什么风险被放大 | 准入、灰度、回滚机制 |
复盘文档里写责任时,建议直接写动作归属,而不是写情绪化评价。比如“Power 模块补充锁内等待评审项,owner 张三,4 月 30 日前合入 checklist”,比“Power 模块评审不充分”更有用。
十七、复盘要记录决策过程
很多问题的技术根因很清楚,但事故仍然发生,是因为中间做过一些决策:是否带风险灰度、是否扩大流量、是否暂停发布、是否回滚、是否接受遗留。复盘如果不记录这些决策,团队无法判断决策机制是否需要改进。
决策记录不需要很长,但要写清当时已知信息、选择了什么、为什么这么选、结果如何。比如某个相机 Watch 项进入灰度,当时已知修复验证 3000 次无复现,但仍有 provider warning;团队选择 1% 灰度并增加监控;灰度 12 小时后 warning 上升但失败率未超门槛;24 小时后出现用户黑屏投诉,暂停扩大。这样的记录能帮助团队判断停止条件是否太宽、监控指标是否滞后。
1 | 决策记录模板: |
复盘不是用事后视角苛责当时决策,而是检查当时的信息和规则是否足够支持正确选择。
十八、复盘结论要能进入培训
一篇好的复盘,应该能被拿来培训新人。不是让新人背事故,而是学习问题分析方法。为了做到这一点,复盘结论要有可教学的结构:典型现象、关键证据、错误路径、正确路径、修复取舍、预防规则。
比如 Watchdog 复盘可以转成一页培训材料:看到 Watchdog 不要只看最后栈;先还原 60 秒前时间线;检查 system_server 锁、Binder、I/O、vendor 回调;确认 bootreason 和 dropbox;验证修复时覆盖原场景、相邻场景和反向风险。这样的材料比“某版本发生过一次 Watchdog”更能提升团队能力。
复盘进入培训后,还能暴露文档是否真正清楚。如果讲不明白,通常说明复盘本身还有缺口:可能证据链不完整,可能根因层级混在一起,可能预防动作太抽象。
十九、复盘后的跟踪不能丢
复盘会结束后,真正困难的是跟踪。很多 action item 在会议纪要里写得很好,但没有进入缺陷系统、需求系统或测试平台,几周后就没人再看。建议复盘动作必须有一个统一承载位置,可以是缺陷子任务、质量改进任务、用例变更单或监控需求单。
跟踪时要区分短期动作和长期动作。短期动作包括修复、回归、灰度观察、回滚策略,通常一两个版本内完成。长期动作包括平台能力、工具脚本、专项矩阵、供应商规范,周期更长,但也要有里程碑。复盘 owner 不一定亲自完成所有动作,但要确保每个动作被系统记录、有人接收、可以验收。
| 动作类型 | 承载位置 | 验收方式 |
|---|---|---|
| 修复回归 | 缺陷单 / Top Issue | 修复包和样本验证通过 |
| 用例补充 | 测试用例库 | 新用例可执行,有通过记录 |
| 监控补充 | 日志平台需求 | 看板或告警上线 |
| 规则更新 | 准入标准 / 评审 checklist | 下个版本评审使用 |
| 案例沉淀 | 案例库 | 可检索,有标签和 owner |
复盘结束两周后,可以做一次轻量回看:哪些动作已完成,哪些延期,延期是否影响当前版本。如果没有这个回看,复盘很容易停留在文档层面。
二十、复盘材料要保留原始证据索引
复盘文档为了可读性,通常只贴关键日志片段。但原始证据不能丢。半年后同类问题再出现,团队需要回看完整 bugreport、traces、pstore、tombstone、测试脚本和设备信息。只保留截图或复制片段,会让历史案例失去复查价值。
建议复盘文档里保留证据索引表,记录文件名、采集时间、设备、版本、用途和存储位置。证据可以放在制品库、日志平台或内部对象存储,但路径要稳定。涉及隐私或用户数据的材料要脱敏,并写明访问权限。
1 | 证据索引示例: |
原始证据索引让复盘经得起回看,也让案例库能从复盘中提取可靠材料。
二十一、复盘结论要回到版本风险
稳定性复盘最后还要回到版本风险:这个问题是否只影响当前版本,是否已经进入灰度监控,是否需要调整准入标准,是否影响同平台其他机型。很多复盘把技术修复写清了,却没有说明版本后续怎么处理,导致版本 owner 仍然不知道能否继续扩大。
建议在复盘末尾写一段版本影响结论:当前版本是否可继续、遗留风险是什么、观察到什么时候、同平台是否需要排查、下一版是否要补专项。这样复盘不只服务技术团队,也能服务发布决策。
二十二、小结
问题复盘的价值,不在于把一次故障写完整,而在于让团队下次少走弯路。现象要连接用户影响和系统证据,证据要能还原时间线,根因要拆到直接原因、触发条件和系统性原因,修复要说明取舍,预防要有可验收动作。
当复盘能沉淀成用例、监控、准入规则、代码评审规则和案例库条目时,它才真正结束。否则它只是一次写得更长的缺陷关闭说明。