13. 唤醒锁的调试与优化:dumpsys power命令、唤醒锁泄漏检测、battery historian分析
说到唤醒锁的调试,我脑子里第一个蹦出来的场景就是:产品经理拿着手机,一脸严肃地跟我说「这手机待机一晚上掉电 20%,你看看吧」。嗯,这种时候,dumpsys power 就是我的第一把手术刀。
13.1 dumpsys power:你的第一现场
先别急着上什么高大上的工具。我个人的习惯是,发现问题后第一时间连上 adb,敲一行命令:
adb shell dumpsys power
输出内容很多,但核心就几块。我带你快速过一遍。
13.1.1 关键字段解读
你会在输出里看到类似这样的东西:
Power Manager State:
mWakefulness=Asleep
mWakeLockSummary=0x0
mScreenOn=false
mInteractive=false
mDirty=0x00000000
Wake Locks: size=3
PARTIAL_WAKE_LOCK 'MyApp:location_lock' (ref count=1)
PARTIAL_WAKE_LOCK 'System:network_sync' (ref count=0)
SCREEN_DIM_WAKE_LOCK 'Launcher:keep_awake' (ref count=1)
这里我一般先看 mWakefulness。如果它显示 Asleep,但屏幕还亮着,那就有问题了。再看 Wake Locks 列表,重点关注那些 ref count 不为 0 的锁。
核心思路:一个正常的待机状态,除了系统必要的锁(比如网络同步),不应该有任何第三方应用的唤醒锁长期持有。
13.1.2 我常用的排查套路
拿到 dumpsys power 的输出后,我一般按这个顺序查:
- 看唤醒锁总数:如果超过 10 个,基本可以断定有泄漏嫌疑。
- 看每个锁的持有时间:有些锁虽然数量少,但一持就是几小时,这比数量多更可怕。
- 看锁的持有者:是系统服务还是第三方 App?如果是第三方,直接定位到包名。
我曾经遇到过一个案例,某个视频 App 在退出播放后,还持有一个 PARTIAL_WAKE_LOCK 长达 8 小时。你想想看,这 App 后台什么都没干,但 CPU 就是没法睡。最后定位到是播放器 SDK 的一个 bug,退出时没释放锁。
13.2 唤醒锁泄漏检测:从现象到根因
泄漏检测说白了就是「找不归的锁」。我把它分成两个层面:
13.2.1 静态代码检测
在代码层面,我习惯用 lint 加上自定义规则。比如检查 acquire() 和 release() 是否成对出现。一个典型的泄漏代码长这样:
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyTag");
wl.acquire();
// 执行一些异步操作
// 但忘记在回调里调用 wl.release()
这种问题在异步回调里特别常见。我建议你在 acquire() 之后,立刻用 try-finally 包起来:
wl.acquire();
try {
// 你的业务逻辑
} finally {
wl.release();
}
嗯,这看起来是基本功,但我在 code review 里至少见过 20 次遗漏。
13.2.2 动态运行时检测
光靠静态检查不够。有些锁是在特定条件下才 acquire 的,比如网络请求超时后。这时候我会用 dumpsys power 配合 dumpsys batterystats 做动态监控。
我写过一个简单的脚本,每隔 5 秒抓一次 dumpsys power,然后对比唤醒锁列表的变化。如果某个锁在 30 秒内一直存在且没有变化,基本就是泄漏了。
小技巧:在 acquire() 时传入一个唯一的 tag,比如带上文件名和行号。这样在 dumpsys 输出里一眼就能看出是哪个模块的问题。
13.3 Battery Historian:可视化你的耗电轨迹
说实话,dumpsys power 只能看到「当前」的状态。但电池耗电是个时间过程,你需要看「历史」。这时候 Battery Historian 就派上用场了。
13.3.1 数据采集
先重置统计,然后跑你的测试场景:
adb shell dumpsys batterystats --reset
// 跑你的测试,比如待机 2 小时
adb bugreport bugreport.zip
然后把 bugreport.zip 上传到 Battery Historian 的网页工具里。我个人习惯用 Docker 部署本地版本,这样数据不用出内网。
13.3.2 怎么看图
打开 Historian 后,你会看到一条时间轴。我重点关注这几个区域:
- Wake Lock 行:看有没有长条形的色块。一个正常的唤醒锁应该是短脉冲,如果出现持续几分钟的色块,那就是泄漏。
- Top 唤醒锁:右侧会列出持锁时间最长的几个锁。我一般先看这里,直接定位到「罪魁祸首」。
- CPU 运行状态:如果 CPU 在屏幕关闭后还频繁醒来,那大概率是唤醒锁或 Alarm 导致的。
我记得有一次,一个社交 App 在后台每 30 秒就持锁一次,每次持续 2 秒。从 Historian 上看,CPU 几乎没停过。最后发现是它的心跳包机制写死了,没做合并和退避。
注意:Battery Historian 的数据是「事后分析」。它告诉你「发生了什么」,但不会告诉你「为什么发生」。根因定位还是得靠 dumpsys power 和代码审查。
13.4 实战:一个完整的排查流程
我拿一个真实案例给你串一遍。用户反馈手机待机一晚掉电 15%。我的排查步骤:
- 第一步:连 adb,跑
dumpsys power。发现Wake Locks列表里有一个com.example.weather:update_lock,ref count 为 1,且mWakefulness为Asleep。嗯,有嫌疑。 - 第二步:跑
dumpsys batterystats,看这个锁的累计持有时长。结果显示它持有了 6 小时 23 分钟。实锤了。 - 第三步:用 Battery Historian 看时间轴。发现这个锁在屏幕关闭后 5 分钟开始持有,一直持续到早上。CPU 在这段时间内一直处于浅睡眠状态。
- 第四步:反编译这个天气 App 的代码(或者看日志),发现它在后台更新天气数据时,
acquire()了锁,但网络请求超时后没有走release()分支。
修复方案很简单:在 finally 块里加 release()。但排查过程花了整整一个下午。你想想看,如果没有这些工具,光靠猜,得试到什么时候?
13.5 知识体系总览
下面这张图是我自己总结的唤醒锁调试知识体系,你可以把它当成一个检查清单:
说白了,调试唤醒锁没有银弹。工具只是帮你缩小范围,最终定位还是得靠对代码逻辑的理解。我个人的经验是:先怀疑,再验证。不要轻易相信「这个锁肯定没问题」——我吃过太多这种亏了。
公众号:蓝海资料掘金营,微信deep3321