2、PowerManager类解析:核心API介绍、WakeLock机制、PowerManager的获取方式
好,咱们开始聊PowerManager。这个类说白了就是Android电源管理的"总开关"。你写的每个App,只要想控制屏幕亮灭、CPU休眠,都得跟它打交道。
我记得刚做Android开发那会儿,有个需求是让App在后台保持运行。我直接new了一个PowerManager对象,结果编译报错。后来才知道,这玩意儿不能自己new,得通过系统服务拿。嗯,这里要注意——PowerManager是个系统级服务,它的实例由SystemServer创建,App只能通过Context获取引用。
2.1 PowerManager的获取方式
获取PowerManager的方式很简单,就一行代码:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
但这里有个坑。你想想看,getSystemService()返回的是Object,需要强转。我见过不少新手直接写:
PowerManager pm = getSystemService(POWER_SERVICE); // 编译不过!
为啥?因为POWER_SERVICE是Context的静态常量,但getSystemService()方法签名是Object getSystemService(String name),返回类型是Object。所以必须强转。
另外,获取PowerManager需要权限吗?不需要。获取实例本身不需要任何权限,但后续操作WakeLock就需要了。这个咱们后面细说。
public class BaseActivity extends AppCompatActivity {
protected PowerManager mPowerManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
}
}
2.2 核心API介绍
PowerManager的API不算多,但每个都很关键。我按使用频率排个序:
| 方法 | 说明 | 使用场景 |
|---|---|---|
newWakeLock() |
创建WakeLock实例 | 需要保持屏幕/CPU唤醒时 |
isScreenOn() |
判断屏幕是否亮着 | 检查当前屏幕状态 |
isInteractive() |
判断设备是否处于交互状态 | 替代isScreenOn()(API 20+) |
isPowerSaveMode() |
判断是否开启了省电模式 | 根据省电模式调整App行为 |
reboot() |
重启设备 | 系统级App使用,普通App无权限 |
这里面最常用的就是newWakeLock()。其他的像reboot(),说实话,普通App根本调不动——需要REBOOT权限,那是系统级App才有的。
我遇到过有人问:isScreenOn()和isInteractive()有啥区别?
简单说,isScreenOn()在API 20被标记为deprecated,官方推荐用isInteractive()。两者的区别在于:isScreenOn()只判断屏幕物理亮灭,而isInteractive()判断设备是否处于可交互状态(比如锁屏但屏幕亮着,也算interactive)。
2.3 WakeLock机制详解
WakeLock,直译就是"唤醒锁"。它的作用说白了就是:告诉系统"我现在要用CPU/屏幕,你别睡"。
Android系统为了省电,会在没有用户操作时让CPU休眠、屏幕熄灭。但有些场景需要保持唤醒,比如:
- 播放视频时屏幕不能灭
- 下载大文件时CPU不能休眠
- 导航App需要屏幕常亮
这时候就需要WakeLock出场了。
2.3.1 WakeLock的类型
PowerManager定义了以下几种WakeLock类型:
| 类型 | CPU | 屏幕 | 键盘灯 | 说明 |
|---|---|---|---|---|
PARTIAL_WAKE_LOCK |
保持 | 可灭 | 可灭 | 最常用,只保CPU |
SCREEN_DIM_WAKE_LOCK |
保持 | 保持(变暗) | 可灭 | 屏幕变暗但不灭 |
SCREEN_BRIGHT_WAKE_LOCK |
保持 | 保持(亮) | 可灭 | 屏幕保持亮度 |
FULL_WAKE_LOCK |
保持 | 保持(亮) | 保持 | 全开,最耗电 |
PROXIMITY_SCREEN_OFF_WAKE_LOCK |
保持 | 可灭 | 可灭 | 接近传感器触发时灭屏 |
这里有个重点:从API 17开始,SCREEN_DIM、SCREEN_BRIGHT、FULL这三种WakeLock被废弃了。为啥?因为Google觉得App不应该直接控制屏幕亮度,这应该由系统统一管理。所以现在推荐的做法是:用PARTIAL_WAKE_LOCK保CPU,再用WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON保屏幕。
FULL_WAKE_LOCK做导航App的屏幕常亮,结果被Google Play拒了。因为从API 17开始,非系统App使用废弃的WakeLock类型会被视为兼容性问题。正确的做法是用FLAG_KEEP_SCREEN_ON。
2.3.2 WakeLock的使用流程
使用WakeLock的套路很固定,就三步:
- 创建:调用
newWakeLock()获取WakeLock实例 - 获取:调用
acquire()获取锁 - 释放:调用
release()释放锁
看个标准示例:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp:MyTag");
// 获取锁
wl.acquire();
// 做需要保持CPU的事情
doSomething();
// 释放锁
wl.release();
这段代码看着简单,但实际项目中我见过太多翻车案例了。
2.3.3 避坑指南
坑一:忘记释放WakeLock
这是最常见的。如果你acquire了但没release,系统会一直保持唤醒状态,电池哗哗地掉。我见过一个音乐App,播放完歌曲后WakeLock没释放,用户一晚上手机掉电50%。
解决方案:一定要在finally块里释放,或者用try-with-resources(如果API够新)。
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp:MyTag");
try {
wl.acquire();
doSomething();
} finally {
if (wl.isHeld()) {
wl.release();
}
}
坑二:acquire()不带超时参数
acquire()有两个重载:
acquire():一直持有,直到手动releaseacquire(long timeout):超时后自动释放
我建议:能用带超时的就别用无参的。比如你只需要保CPU 30秒,就写wl.acquire(30000)。这样即使代码异常没走到release,系统也会帮你释放。
坑三:WakeLock的标签要规范
创建WakeLock时第二个参数是tag,这个tag会出现在dumpsys power的输出里。我习惯用"包名:具体用途"的格式,比如"com.example.app:download_service"。这样排查问题时一眼就能看出是哪个模块持有了WakeLock。
- PowerManager通过
getSystemService()获取,不能new - WakeLock类型推荐用
PARTIAL_WAKE_LOCK,屏幕常亮用FLAG_KEEP_SCREEN_ON - acquire和release必须成对出现,最好用try-finally保证释放
- 带超时的acquire更安全
- tag命名要规范,方便调试
好了,PowerManager的核心内容就这些。下一章咱们深入源码,看看newWakeLock()到底干了什么,WakeLock在系统层面是怎么管理的。说实话,源码里有些设计挺巧妙的,看完你会对Android电源管理有更深的理解。