卡住的部分wakelock
部分wakelock是PowerManager API给用户提供的允许用户在黑屏后(不管是系统超时还是用户按下电源键)保持CPU运行的机制。应用通过调用acquire()和参数PARTIAL_WAKE_LOCK获得部分wakelock。如果你的应用获得了一个部分wakelock,一直在后台运行(对用户不可见),那么这个wakelock就卡住了。这种情况会耗尽电量,因为他会阻止设备进入低电状态。部分wakelock只应该在必要的时候使用,用完应立即释放。
如果你的应用有卡住wakelock的问题,你可以通过本文来发现和解决这一问题。
发现问题
你可能不知道自己的wakelock卡住了。如果你的应用发布了,Android vitals可以帮你发现问题。
Android vitals
当应用有卡住的wakelock时,Android vitals可以通过Play Console通知你的应用存在性能问题。Android vitals会报告wakelock卡住一小时,并至少满足下面一点时:
- 至少累计消耗0.7%的电量会话
- 至少后台消耗0.1%的电量会话
电量会话是两次充满电之间的部分。
意识到存在卡住wakelock的问题后,再定位问题。
解决问题
Wakelock是在早期的Android平台中引入的,随着时间的推移,很多之前需要用wakelock的情况,在新版本上有更好的功能代替了,像JobScheduler和Firebase JobDispatcher。这段为解决wakelock问题给出建议,但长期来看,还是要移植应用满足最好的联系。
找到用wakelock的地方,比如newWakeLock(int, String)或者WakefulBroadcastReceiver。一些建议:
- 建议在wakelock标签中添加包,类和方法名,以便定位。更多建议:
- 不要使用个人信息,例如,email帐号。否则,设备日志将把名字计为:_UNKNOWN。
- 不要用程序获得类名或方法名,例如getName()方法,因为他们会被Proguard混淆。用硬编码字符串。
- 不要在wakelock标签中添加计数器或统一标识符。系统不能统计同一个方法创建的wakelock,因为他们有统一的标识符。
- 确保代码释放了所有获得过的wakelock。确保所有调用acquire()的都有对应的release()。下面的例子中wakelock因为异常没有被释放:
void doSomethingAndRelease() throws MyException {
mWakeLock.acquire();
doSomethingThatThrows();
mWakeLock.release(); // does not run if an exception is thrown
}
正确的版本是:
void doSomethingAndRelease() throws MyException { try { mWakeLock.acquire(); doSomethingThatThrows(); } finally { mWakeLock.release(); } }
- 确保wakelock不用了马上回收。例如,如果你正在使用wakelock来完成后台任务,确保任务完成后wakelock马上被释放。如果持有wakelock时间超过使用时间,说明持有wakelock时间过长。
解决了上面的问题后,用下面的工具验证wakelock是否正确释放:
- dumpsys – 设备系统服务状态信息工具。可以看电池服务信息,包含一系列wakelock信息。运行adb shell dumpsys power。
- 电池历史 – 解析Andriod bug report输出,将其中电源使用部分转化成直观的图标的形式的工具。
最好的练习
一般来说,应用应该尽可能避免使用partial wakelock,因为他太容易引起耗电。之前需要partial wakelock的大部分情形,Android提供了新的替换API。还有一个情况需要使用partial wakelock,即关屏后听音乐。如果需要运行任务,考虑下面的替换方案:
- 长时间运行的HTTP下载,考虑用DownloadManager。
- 应用需要从外部服务器同步数据,考虑用sync adapter。
- 如果应用定期执行后台服务,考虑用JobScheduler或者Firebase JobDispatcher或者WorkManager,目前还在阿尔法阶段。定期触发服务,请看Intelligent Job-Scheduling。
用这些API的好处就是系统会根据电池的状态批量或推迟这些操作。
如果必须使用部分wakelock,一定要注意一些建议:
- 确保你的应用的一部分保持在前台。例如,如果需要一个服务,那么启动一个前台服务。这样可以提醒用户你的应用一直在运行。
- 确保获得和释放wakelock的逻辑简单。当你的wakelock逻辑特别复杂的时候,比如跟状态机,计时器,执行池,回调,那么其中任何一点疏忽都可能导致wakelock持有时间过长。这些问题很难分析调试。