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就需要了。这个咱们后面细说。

小技巧: 我习惯在Application或BaseActivity里统一获取PowerManager实例,避免到处写重复代码。比如:
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的套路很固定,就三步:

  1. 创建:调用newWakeLock()获取WakeLock实例
  2. 获取:调用acquire()获取锁
  3. 释放:调用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():一直持有,直到手动release
  • acquire(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电源管理有更深的理解。