16、Logcat 与功耗调试:WakeLock、AlarmManager 日志解读
功耗调试,说白了就是跟手机“偷电”的那些模块斗智斗勇。我做了这么多年系统优化,最头疼的就是用户反馈“手机待机一晚上掉电20%”。这种问题,八成跟 WakeLock 和 AlarmManager 脱不了干系。
今天我们就来聊聊,怎么通过 Logcat 日志,揪出那些“不睡觉”的 App。
16.1 WakeLock 日志:谁在阻止系统休眠?
WakeLock 是 Android 系统里一个很“霸道”的机制。它允许 App 阻止 CPU 休眠,或者阻止屏幕变暗。你想想看,如果某个 App 一直抱着 WakeLock 不放,系统就永远没法进入深度睡眠,耗电自然就上去了。
16.1.1 如何抓到 WakeLock 日志?
我个人习惯,先开启 WakeLock 的日志开关。在 adb shell 里执行:
# 开启 WakeLock 详细日志
adb shell dumpsys power | grep "Wake Locks"
但这样只能看到当前状态。要实时跟踪,得用 logcat 过滤:
adb logcat -s PowerManagerService:D
嗯,这里要注意,不同厂商的 ROM 可能 tag 不一样。有的叫 PowerManagerService,有的叫 PowerMS。我在项目中遇到过小米的机器,tag 居然是 PowerKeeper,当时排查了好久。
16.1.2 日志解读:一条典型的 WakeLock 记录
看这条日志:
D/PowerManagerService( 1234): acquireWakeLock: flags=0x1, tag="MyApp:MyLock", uid=10123, pid=5678
拆开来看:
- flags=0x1:表示这是一个
PARTIAL_WAKE_LOCK,只阻止 CPU 休眠,不阻止屏幕。 - tag="MyApp:MyLock":App 自己起的名字。我建议开发者在这里写清楚用途,比如 "MyA:DownloadLock"。
- uid=10123:App 的用户 ID,可以通过
ps命令反查包名。
如果看到这条日志频繁出现,而且没有对应的 releaseWakeLock 日志,那基本可以断定这个 App 在“偷电”。
adb shell dumpsys package 反查包名。
16.1.3 避坑指南:超时 WakeLock
我曾经遇到一个案例,某个视频 App 播放完视频后,WakeLock 没有释放。但奇怪的是,日志里明明有 releaseWakeLock 的记录。
后来发现,问题出在 acquireWakeLock 时设置了超时时间:
D/PowerManagerService( 1234): acquireWakeLock: flags=0x1, tag="VideoPlayer", timeout=30000
这个 timeout=30000 表示 30 秒后自动释放。但 App 在 30 秒内又调用了 acquire,导致超时时间不断重置。说白了,就是 App 开发者偷懒,用超时机制代替了手动释放。
正确的做法是什么?应该在 onPause() 或 onStop() 里显式释放 WakeLock,而不是依赖超时。
16.2 AlarmManager 日志:谁在频繁唤醒系统?
AlarmManager 是另一个耗电大户。它允许 App 在指定时间唤醒系统执行任务。如果某个 App 设置了大量闹钟,或者闹钟间隔很短,系统就会被频繁唤醒。
16.2.1 查看所有已注册的闹钟
用这个命令:
adb shell dumpsys alarm
输出内容很多,我一般重点关注这几个字段:
| 字段 | 含义 | 排查要点 |
|---|---|---|
| package | 包名 | 看是不是第三方 App |
| type | 闹钟类型 | ELAPSED_REALTIME_WAKEUP 会唤醒系统 |
| when | 触发时间 | 看是不是频繁触发 |
| interval | 重复间隔 | 小于 1 分钟的就要警惕 |
16.2.2 Logcat 中的 AlarmManager 日志
过滤命令:
adb logcat -s AlarmManager:D
典型的日志长这样:
D/AlarmManager( 1234): Sending alarm: Alarm{type=2, when=1234567890, interval=60000, tag=*walarm*:com.example.app/.MyReceiver}
解读一下:
- type=2:表示
ELAPSED_REALTIME_WAKEUP,会唤醒系统。 - interval=60000:每 60 秒触发一次。说实话,这个频率有点高。
- tag=*walarm*:com.example.app/.MyReceiver:
*walarm*前缀表示这是一个唤醒闹钟。
如果你看到某个 App 的 *walarm* 日志每分钟出现一次,那基本可以断定它在“刷存在感”。
adb shell dumpsys alarm | grep "com.example.app" 可以只看某个 App 的闹钟信息。我经常用这个来快速定位问题 App。
16.3 知识体系:功耗调试核心逻辑
下面这张图,是我自己总结的功耗调试流程。你跟着走一遍,基本能解决 80% 的问题。
16.4 实战:一个真实的功耗问题排查
我记得有一次,用户反馈手机待机一晚上掉电 15%。我拿到机器后,先看了 Logcat:
D/AlarmManager( 1234): Sending alarm: Alarm{type=2, interval=30000, tag=*walarm*:com.shady.app/.AlarmReceiver}
D/PowerManagerService( 1234): acquireWakeLock: flags=0x1, tag="com.shady.app:BackgroundWork", uid=10456
好家伙,30 秒一个唤醒闹钟,还持有一个 WakeLock 不放。我直接用 adb shell dumpsys package com.shady.app 查了一下,发现是个天气 App。
为什么天气 App 要这么频繁地唤醒系统?说白了,就是它想实时更新天气数据。但 30 秒刷新一次,有必要吗?
我给的建议是:
- 把闹钟间隔改成 30 分钟以上。
- 使用
setInexactRepeating()代替setRepeating(),让系统可以批量处理闹钟。 - WakeLock 在后台任务完成后立即释放,不要持有超过 10 秒。
- WakeLock 日志看
acquire和release是否成对出现。 - AlarmManager 日志看
*walarm*前缀和interval字段。 - 用
dumpsys命令可以拿到更详细的信息。 - 不要依赖超时机制释放 WakeLock,一定要显式释放。
好了,关于 WakeLock 和 AlarmManager 的日志解读,今天就聊到这里。你下次遇到耗电问题,不妨先按这个思路查一遍 Logcat,大概率能找到元凶。