4、PowerManagerService深度解析:PMS启动流程、电源状态机、屏幕超时与休眠策略、Doze模式
PowerManagerService,圈内人都叫它PMS。这玩意儿是Android电源管理的总指挥。说白了,系统要不要睡、什么时候睡、怎么睡,全归它管。我当年刚接触这部分代码时,第一反应是——这代码量也太大了吧?但啃下来之后发现,核心逻辑其实就几个模块。
今天咱们就把PMS的底裤扒干净。从它怎么启动的,到状态机怎么转的,再到屏幕超时和Doze模式,一条线讲透。
4.1 PMS启动流程:系统还没醒,它先醒了
PMS是在SystemServer里启动的。你想想看,系统刚开机,其他服务可能还在初始化,PMS就得先跑起来。为什么?因为电源管理是基础中的基础。
启动流程大致分三步:
- 创建PMS实例——在SystemServer的startOtherServices()里调用PowerManagerService的构造函数。
- 注册系统服务——通过ServiceManager将PMS添加到服务列表中,这样其他模块才能通过Binder调用它。
- 初始化核心组件——包括电源状态机、WakeLock管理、显示电源控制器等。
这里有个细节我提一下。PMS的构造函数里会调用nativeInit(),这个native方法会初始化底层的电源管理hal层。嗯,这里要注意,如果hal层初始化失败,PMS会直接抛异常。我在项目中遇到过某款低端平板,就是因为hal层驱动没适配好,导致PMS启动时崩溃,整机卡在开机动画上。
核心要点:PMS的启动顺序在SystemServer中非常靠前。它必须在WindowManagerService和ActivityManagerService之前初始化,因为这两个服务都需要依赖PMS来控制屏幕状态。
4.2 电源状态机:系统休眠的交通指挥
PMS内部维护了一个状态机。这个状态机决定了系统当前处于什么电源状态。我习惯把它理解成一个交通指挥——红灯停、绿灯行,状态不对就切换。
Android的电源状态主要有以下几种:
| 状态 | 含义 | 典型场景 |
|---|---|---|
| AWAKE | 屏幕亮着,系统正常工作 | 用户正在操作手机 |
| DREAMING | 屏保模式,屏幕微亮 | 充电时进入屏保 |
| DOZE | 深度休眠,但保持网络连接 | 手机放口袋里一段时间后 |
| SLEEP | 完全休眠,CPU可能挂起 | 长时间不操作 |
状态切换的触发条件是什么?说白了就是两件事:用户活动和超时。用户按电源键、触摸屏幕,系统就切到AWAKE。如果一段时间没人碰,系统就沿着AWAKE → DREAMING → DOZE → SLEEP这条路径往下走。
我曾经调试过一个bug:手机明明设置了30秒休眠,结果每次都是2分钟才黑屏。查了半天发现是某个第三方应用持有一个PARTIAL_WAKE_LOCK,阻止了状态机往下走。嗯,这就是典型的WakeLock滥用问题。
避坑指南:状态机的切换逻辑在updatePowerStateLocked()方法里。如果你要修改休眠策略,这个方法是关键入口。我曾经在这里加过日志,打印每次状态切换的原因,调试效率直接翻倍。
4.3 屏幕超时与休眠策略:用户设的30秒,真的就是30秒吗?
用户可以在设置里选择屏幕超时时间,比如15秒、30秒、1分钟。但实际执行时,PMS并不会傻傻地等满30秒就关屏。它会考虑很多因素:
- 是否有WakeLock持有——如果有应用持有了SCREEN_BRIGHT_WAKE_LOCK,屏幕就不会灭。
- 是否在通话中——通话时屏幕超时会被延长,防止通话中黑屏。
- 是否连接了外设——比如插着HDMI,屏幕超时可能被忽略。
- 用户是否在观看视频——某些播放器会通过setKeepScreenWhileScreenOn()来阻止休眠。
你想想看,如果用户设了30秒,但正在看视频,30秒一到直接黑屏,那体验得多糟糕?所以PMS的休眠策略是:超时时间只是一个参考,最终是否休眠由多个条件共同决定。
具体的超时计算在getScreenOffTimeoutLocked()方法里。它会读取用户设置的时间,然后根据当前场景做调整。比如检测到正在充电,超时时间可能会翻倍。
个人经验:我在做车载系统时,客户要求屏幕在车辆行驶中永不熄灭。我直接在PMS里加了一个判断:如果当前是车载模式且车速大于0,就把屏幕超时设为-1(永不超时)。效果立竿见影,但后来发现这样会导致功耗飙升。嗯,最后改成了降低屏幕亮度而不是完全禁止休眠。
4.4 Doze模式:Android 6.0引入的省电黑科技
Doze模式,说白了就是手机在静止状态下进入的一种深度省电模式。它从Android 6.0开始引入,后续版本不断优化。我刚开始研究Doze时,觉得这东西挺玄乎的,后来看了源码才发现,核心逻辑其实不复杂。
Doze模式的触发条件有两个:
- 设备处于静止状态(通过加速度传感器判断)
- 屏幕关闭超过一定时间
进入Doze后,系统会做以下几件事:
- 限制网络访问——只有高优先级应用才能联网
- 延迟闹钟和任务——非紧急的Alarm会被推迟到下一个维护窗口
- 禁止WakeLock——应用申请的WakeLock不再生效
- 减少GPS和WiFi扫描——省电嘛
但Doze不是一直睡死过去的。它会周期性地进入一个叫维护窗口的阶段。在这个窗口里,系统会短暂唤醒,让应用处理一些积压的任务。维护窗口的长度和间隔由系统动态调整——如果用户长时间不动,间隔会越来越长。
我记得有一次,某款App在Doze模式下收不到推送消息。开发人员跑来问我是不是系统bug。我查了一下,发现是App没有使用高优先级FCM消息,而是用了普通的AlarmManager。在Doze模式下,普通Alarm会被延迟到维护窗口,推送自然就不及时了。解决方案很简单:改用Firebase Cloud Messaging的高优先级消息,或者引导用户将App加入电池白名单。
警告:Doze模式对后台应用的限制非常严格。如果你的应用需要实时通信(比如即时通讯、导航),一定要适配Doze模式。否则用户会发现:手机一锁屏,App就掉线了。我曾经见过一个即时通讯App,在Doze模式下延迟高达15分钟,用户差点把手机砸了。
4.5 总结一下
PMS的核心就三件事:管状态、管超时、管休眠。状态机决定了系统当前处于什么电源级别,超时机制决定了什么时候切换状态,Doze模式则是在休眠基础上进一步省电。
如果你要优化系统休眠,我建议从这三个方向入手:
- 检查WakeLock的使用情况——很多功耗问题都是WakeLock滥用导致的
- 调整超时策略——根据产品形态定制屏幕超时逻辑
- 适配Doze模式——确保关键应用在Doze下能正常工作
嗯,PMS这部分内容就讲到这里。下一章咱们聊聊更底层的Linux电源管理机制,包括suspend和wakeup source。到时候你会发现,Android的电源管理其实是在Linux内核的基础上又包了一层壳。