PowerManager 不是只有 WakeLock
版权声明:
本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有。
未经允许,不得转载。
一、前言
Android 中提供了很多系统的管理类,WindowManager 、ActivityManager、PowerManager 等,这些都是为了对一些系统的组件和服务进行管理,提供给开发者使用的。而 PowerManager 就是 Android 系统中,对电源状态进行控制的一把管理类。
本文就 PowerManager 的做一个详尽的介绍。
二、什么是PowerManager
从名称上也可以看出来 PowerManager 主要就是为了做电源状态的管理。在实际开发过程中,我们使用PowerManager 主要是为了通过它获取一个 WakeLock 对象,用于控制当前设备的一些耗电操作。
首先要了解到,Google 一直在为 Android 系统省电做了特别大的努力,从最新的 Android O 的更新上就可以看出来,为了省电,最开发者做了非常多的限制。而在 Android 系统的设备上,当手机静置一段时间不去操作它之后,通常为了省电提高续航能力,会关闭手机屏幕或者降低手机屏幕的亮度、降低 CPU 频率等。这是系统运行自发的一些行为,其实也是通过 PowerManager 进行管理的。
而如果我们需要自行调整这种行为,同样也需要借用 PowerManager 这个类来进行操作。PowerManager 主要对三种资源进行管理:CPU、屏幕、键盘背光。 CPU 和屏幕,很好理解,而键盘背光最开始其实是指物理输入键盘,但是在现在这个触屏横行的时代,基本上已经不存在了,可很多手机会做一些物理的 home、back 键,上面会有一些唤醒灯,其实也是通过 PowerManager 进行点亮和熄灭的管理的。
我们使用 PowerManager 其实也就是为了管理 CPU和屏幕资源,例如需要让 CPU 在休眠状态下依然保持高速的运算,在长期不动屏幕的时候,依然保持屏幕的常亮等等,接下来就让我们看看如何使用 PowerManager 来对这些进行管理。
三、使用 PowerManager
1、PowerManager 的基本使用
既然节电是一个系统推荐的行为,那么一般情况下,我们最好还是不要对其进行设定去操作它。但是如果一定需要操作的话,也最好使用级别最低的 WakeLock 锁,并且在使用完成之后,立即释放它。使用级别最低的 WakeLock 是非常有必要的,虽然为了方便,直接使用高级别的 WakeLock 的话,也是可以达到目的的,但是它将会更耗电,现在一些设备上都有电量监控的 App ,它们会记录耗电情况,并告知用户所知。
PowerManager 的使用非常的简单,可以通过 getSystemService()
方法来获取它。
context.getSystemService(Context.POWER_SERVICE)
它将获得一个 PowerManager 对象,如果需要获取 WakeLock 对象,还需要使用 newWakeLock()
方法。
WakeLock 这个类没有提供公有的构造方法,所以只能通过 newWakeLock() 来获取。可以看到 newWakeLock()
需要的参数,其中 levelAndFlags 就是 WakeLock 的级别,后面我们会讲解到。
而在其中,还会调用 validateWakeLockParameters()
方法,它主要是对参数进行一些校验。
下面看看 WakeLock 使用的一个 Demo:
可以看到,使用 acquire()
获取 WakeLock ,在使用完成之后,必须调用 release()
对 WakeLock 进行释放。
为了正确的使用 WakeLock ,还需要在 AndroidManifest.xml 文件中声明 WAKE_LOCK 权限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
2、WakeLock 的 level 和 flag
前面提到 newWakeLock() 方法需要传递一个 levelAndFlags 的参数,用于标记当前需要持有的 WakeLock 的 级别和标识,也就是说 WakeLock 是通过 level 和 Flag 来唯一确定一个行为的,它们可以通过或运算符『|』进行拼接。
下面我们先看看关于 Level 的设定,Level 在 Android 的版本中,经过了多次调整,很多 Level 其实已经被废弃了,这里只对一些常用的 Level 进行介绍。
未被废弃的 Level 参数:
- PARTIAL_WAKE_LOCK : 使 CPU 保持高性能运行,而屏幕和键盘背光可以被关闭。通常推荐使用它。
- PROXIMITY_SCREEN_OFF_WAKE_LOCK:用于和接近传感器配合使用,来实现电话类 App 中,手机贴近耳朵的时候,将屏幕关闭的功能,而离开的时候,又使屏幕两期的功能。这个 level 在 API 21 之后才被开放出来,之前都是处于 @hide 的状态。
已经被废弃的 Level 参数:
- SCREEN_DIM_WAKE_LOCK:只限制屏幕,保证亮起,但是允许它亮度变低。
- SCREEN_BRIGHT_WAKE_LOCK:保证屏幕最高亮,但是键盘背光灯允许熄灭。
- FULL_WAKE_LOCK:保证屏幕最高亮度,并且键盘背光灯不允许熄灭。
仔细找规律,可以发现这几个被废弃的 Level 参数,都是用于限制屏幕常亮的,而当用户主动按下电源键进行锁屏的时候,这些 WakeLock 都将被系统给隐式释放,导致锁定的屏幕和 CPU都被关闭。除了 SCREEN_BRIGHT_WAKE_LOCK 是在 API 13 被标记废弃之外,其他两个都是在 API 17 中被标记废弃的。
既然这些 Level 参数都已经被标记为 @Deprecated 了,那么我们最好还是不要使用它。而有时候我们又需要在无操作的时候,依然保持屏幕常亮,Google 官方既然有废弃的 Level 参数,同时也提供了解决方案。
可以使用 FLAG_KEEP_SCREEN_ON 来标记窗口,它会比 FULL_WAKE_LOCK 这些与屏幕相关的 WakeLock 更好,因为它会对屏幕进行隐式的正确管理,就无需我们自己去管理屏幕的亮度状态,同时它是不需要我们在 AndroidManifest.xml 文件中,额外的申请 WAKE_LOCK 权限。
FLAG_KEEP_SCREEN_ON 是被定义在 WindowManager.LayoutParams 中的,下面是使用示例。
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
前面提到 newWakeLock()
可以配置 level 之外,还可以组合配置一个 Flag 参数,在 PowerManager 中,定义了两个 Flag。
- ACQUIRE_CAUSES_WAKEUP:它与 level 配合,可以使屏幕亮起。level 只是用于保持一个锁,例如保持屏幕常亮,但是它只是一个持续保持的过程,如果调用 acquire() 的时候,屏幕已经熄灭,就需要额外添加 ACQUIRE_CAUSES_WAKEUP 将其点亮。需要注意的是,它不能和 PARTIAL_WAKE_LOCK 同时使用。
- ON_AFTER_RELEASE:它与 level 配合,标记如果释放 WakeLock 的时候,屏幕处于亮着的状态,则在释放 WakeLock 之后,再让屏幕保持亮一小会而,而如果释放 WakeLock 的时候,屏幕已经熄灭,则不会有任何动作。
3、WakeLock 的一些方法
使用 level | flag 的组合形式,就可以获取到一个 WakeLock 对象,虽然前面介绍了 WakeLock 的简单使用,但是它实际上还有一些跟复杂的 API ,供我们使用。
- void acquire() : 获取 WakeLock。
- void acquire(long timeOut) :获取 WakeLock ,同时设定一个超时时间,当超过 timeOut 设定的时长之后,系统会自动释放 WakeLock,不需要额外调用 release()。
- release() :释放获取的 WakeLock。
- release(int flag):释放获取的 WakeLock,并设定一个 Flag 标识。其实 Flag 的取值,暂时只有 RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY ,它表示在获取 PROXIMITY_SCREEN_OFF_WAKE_LOCK 唤醒说的时候,如果当前传感器标记仍然有物品在靠近,则延迟到物品里去的时候再释放这个 WakeLock。
- boolean isHeld():判断当前 WakeLock 是否已经被获取。
- void setReferenceCounted(boolean value):它标记是否对 WakeLock 资源使用引用计数,它的默认值是 true。
PowerManager 对 WakeLock 资源设定为一个引用计数的方式进行标记管理,也就是说,调用一次 acquire()
获取 WakeLock 就必须调用一次 release() 释放 WakeLock 才能真正的将其释放掉。而如果 release()
一个没有 acquire()
的 WakeLock 对象,则会抛出一个 RuntimeException 异常,表示 WakeLock under-locked。
这是一个默认行为,可以通过 setReferenceCounted()
方法对 mRefCounted 其进行改变,mRefCounted 默认为 true。从源码上可以看到,当 mRefCounted 为 true 的时候,只有在第一次 acquire()
的时候才会真实的调用 IPowerManager.acquireWakeLock()
获取 WakeLock,并增加引用计数,在最后一次 release()
的时候,才会真实调用 IPowerManager.releaseWakeLock()
进行释放 WakeLock,也就是说,是否真的获取和释放 WakeLock 取决于是否调用到 IPowerManager 的方法了。而 setReferenceCouned()
方法可以改变这种默认的设置,让每次的 acquire()
和 release()
都去调用 IPowerManager 的对应方法。
下面看看相关源码。
可以看到,在 acquireLocked() 方法中,mCount++ == 0
表示 mCount 会在调用之后自增 ,这样的语法表示只有在第一次 mCount 等于 0 的时候,才会命中 if 条件,执行 IPowerManager.acquireLocked() 方法。
而在 release() 方法中,同样在 --mCount ==0
在判断 mCount 之前做了一个自减的操作,表示只有在最后一次 release() 才会真实的调用 IPowerManager.releaseWakeLock() 方法。
四、PowerManager 不止有 WakeLock
虽然我们大部分时间,使用 PowerManager 就是为了获取一个 WakeLock 对象进行操作,但是实际上 PowerManager 也提供了一些方法,用于我们判断当前的电池管理状态。
但是 PowerManager 中的方法,不是被标记为 @hide 就是需要额外的权限,可供我们直接使用的方法非常少,@hide 的方法我们可以使用反射的方式调用,但是需要额外权限的方法,就没办法去绕过它了。
这里只是介绍几个常用的比较有特点的 API:
- boolean isScreenOn() :判断当前屏幕是否亮着,只要不是锁屏状态,都算亮着。而同时在 API 20 中被废弃,推荐使用 isInteractive().
- boolean isInteractive() : 判断当前屏幕是否亮着,在 API 20 中被添加。
- boolean isWakeLockLevelSupported(int level):因为有一些 Level 需要权限才能获取,所以这个方法用于检查当前是否有 level 权限。
- void reboot():重启设备,需要 REBOOT 权限,这是一个系统权限。
- boolean isPowerSaveMode() :判断当前设备是否处于一个省电模式下,在省电模式下,我们应该尽量减少耗电操作。
- boolean isDeviceIdleMode():判断当前设备是否处于空闲状态。在空闲状态下,我们可以做一些后台操作。
- void shutdown() : 这是一个 @hide 的方法,可以使用它将设备关机。
- void goToSleep() :让设备去休眠。需要 DEVICE_POWER 权限,这是一个系统权限。
- void wakeUp():强行将设备从休眠中唤醒,需要 DEVICE_POWER 权限,这是一个系统权限。