优化电池续航时间  |  Android 开发者  |  Android Developers

一、原理

PowerManager 用来控制设备的电源状态. 而 PowerManager.WakeLock 也称作唤醒锁, 是一种保持 CPU 运转防止设备休眠的方式.

  • WakeLock是什么

    WakeLock是Android框架层提供的一套机制,应用使用该机制可以达到控制Android设备状态的目的。这里的设备状态主要指屏幕的打开关闭,cpu的保持运行。简单的理解WakeLock是让系统保持”清醒”的一种手段.

  • WakeLock作用

    当手机灭屏状态下保持一段时间后,系统会进入休眠,一些后台运行的任务就可能得不到正常执行,比如网络下载中断,后台播放音乐暂停等。WakeLock正是为了解决这类问题,应用只要申请了WakeLock,那么在释放WakeLock之前,系统不会进入休眠,即使在灭屏的状态下,应用要执行的任务依旧不会被系统打断。

  • WakeLock有那些分类

    WakeLock是PowerManager的内部类,其代码路径位于:

    frameworks/base/core/java/android/os/PowerManager.java
  • WakeLock 分类如下:

    • PARTIAL_WAKE_LOCK: 灭屏,关闭键盘背光的情况下,CPU依然保持运行。
    • PROXIMITY_SCREEN_OFF_WAKE_LOCK: 基于距离感应器熄灭屏幕。最典型的运用场景是我们贴近耳朵打电话时,屏幕会自动熄灭。
    • SCREEN_DIM_WAKE_LOCK/SCREEN_BRIGHT_WAKE_LOCK/FULL_WAKE_LOCK:这三种WakeLock都已经过时了,它们的目的是为了保持屏幕长亮,Android官方建议用getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);方式替换。因为比起申请WakeLock,这种方式更简单,还不需要特别申请android.permission.WAKE_LOCK权限。
    • DOZE_WAKE_LOCK/DRAW_WAKE_LOCK: 隐藏的分类,系统级别才会用到。

如果是PARTIAL_WAKE_LOCK, 无论屏幕的状态甚至是用户按了电源钮, CPU 都会继续工作. 如果是其它的唤醒锁, 设备会在用户按下电源钮后停止工作进入休眠状态.

  • WakeLock的flag如下:
    • ACQUIRE_CAUSES_WAKEUP: 点亮屏幕,比如应用接收到通知后,屏幕亮起。
    • ON_AFTER_RELEASE: 释放WakeLock后,屏幕不马上熄灭。
    • UNIMPORTANT_FOR_LOGGING: 隐藏的flag,系统级别才会用到。

二、查找耗电问题

adb shell dumpsys power|grep PARTIAL

2.1 cocos持有的锁

image

在这看到了系统持有了两个PARTIAL_WAKE_LOCK

  • '*job*/com.android.vending/com.google.android.finsky.scheduler.process.mainimpl.PhoneskyJobServiceMain'
    android JobSchedule,可以不管

    Android 5.0 系统以后,Google 为了优化 Android 系统,提高使用流畅度以及延长电池续航,加入了在应用后台 / 锁屏时,系统会回收应用,同时自动销毁应用拉起的 Service 的机制。同时为了满足在特定条件下需要执行某些任务的需求,google 在全新一代操作系统上,采取了 Job _(jobservice & JobInfo)_的方式,即每个需要后台的业务处理为一个 job,通过系统管理 job,来提高资源的利用率,从而提高性能,节省电源。这样又能满足 APP 开发商的要求,又能满足系统性能的要求。Jobscheduler 由此应运而生。

  • AudioMix 音频锁

    audioMix 是 安卓系统默认的音频锁,只要在cocos2d中播放音频,cocos都会持有该锁,即使手机锁屏,cocos也不会释放该锁,要解决这个问题,需要修改cocos的源码。

    https://forum.cocos.org/t/creator2-2-0-android-audiomix-partial-wake-locks/87985

    临时解决方案:

    free AudioMix partial wake locks, and android device can enter power save by xianyinchen · Pull Request #2137 · cocos-creator/engine-native (github.com)

2.2 自定义锁

'MyApp::MyWakelockTag' 是我们自己创建的锁

image
解决:
熄屏后手动释放锁,或则增加策略,比如音频停止,并且熄屏两分钟后释放锁

/**
* 播放过程中申请唤醒锁,尽量延长熄屏的时间
*/
private fun acquireWakeLock() {
if (wakeLock != null && wakeLock?.isHeld == true) {
Timber.d("已经持有锁")
return
}
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Anibook::AudioServiceWakeLock").apply {
acquire()
}
}
}
private fun releaseWakeLock() {
wakeLock?.let {
if (it.isHeld) {
it.release()
}
}
}

监听app后台状态

private val appStatusListener = object : Utils.OnAppStatusChangedListener {
override fun onForeground(activity: Activity?) {
// if (AudioPlayer.isPlaying()) {
Timber.d("处于前台,获取锁")
acquireWakeLock()
// }
}
override fun onBackground(activity: Activity?) {
if (AudioPlayer.isPlaying()) {
Timber.d("处于后台,正在播放,持有锁")
acquireWakeLock()
return
}
releaseWakeLock()
Timber.d("处于后台,释放锁")
}
}

在暂停/恢复处也处理下,因为在通知中也可以暂停和播放,这种情况下,是不会回调上面的回调的

private fun pause() {
if (!AppUtils.isAppForeground()){
releaseWakeLock()
}
}

private fun resume() {
acquireWakeLock()
}