WakeLock在Framework中的实现:PowerManagerService中的管理机制
WakeLock这东西,说白了就是应用跟系统说「嘿,我现在要干活,别让我睡」。但系统怎么知道谁拿了锁、锁有多重要、什么时候该释放?这些逻辑全在PowerManagerService(简称PMS)里。
我个人习惯把PMS看作一个「锁管家」。它不生产锁,它只是锁的搬运工——嗯,开个玩笑。实际上PMS负责所有WakeLock的注册、计数、优先级排序,以及最终决定系统能不能进入休眠。
PMS中的WakeLock数据结构
先看看PMS里怎么存这些锁。核心数据结构是这么个东西:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
final class WakeLock {
int mUid; // 哪个进程拿的锁
int mPid; // 哪个线程拿的锁
String mPackageName;
String mTag; // 锁的名字,调试用
int mFlags; // 锁的类型,比如PARTIAL_WAKE_LOCK
boolean mActive; // 是否还持有
long mAcquireTime; // 获取时间戳
WorkSource mWorkSource; // 谁在干活
int mDisplayId; // 关联的屏幕
IBinder mToken; // Binder令牌,后面细说
}
每个WakeLock对象都对应一个Binder令牌。这个设计很巧妙——当应用进程挂了,Binder会死亡通知,PMS就能自动清理锁。我在项目中遇到过好几次因为应用崩溃导致锁没释放的问题,全靠这个机制兜底。
锁的注册与释放流程
应用调用PowerManager.WakeLock.acquire()时,底层会通过Binder走到PMS的acquireWakeLock()方法。流程大概是:
- 检查调用者权限(没有WAKE_LOCK权限?直接扔异常)
- 解析flags和tag,创建WakeLock对象
- 把锁加入
mWakeLocks列表 - 调用
applyWakeLockFlags()处理屏幕点亮等副作用 - 更新电源状态
释放时走releaseWakeLock(),把锁从列表移除,然后重新计算系统能否休眠。
核心逻辑:PMS维护一个全局的ArrayList<WakeLock> mWakeLocks,所有活跃的锁都在里面。每次锁变化时,遍历这个列表判断是否还有未释放的锁。
WakeLock的Binder通信机制
这里有个关键问题:应用进程和系统服务(PMS)不在同一个进程,怎么传递锁的状态?答案就是Binder。
每个WakeLock在创建时,会生成一个Binder对象作为令牌:
// PowerManagerService.java 内部
private final class WakeLock {
public final Binder mToken = new Binder();
// ...
}
这个Binder对象有两个作用:
- 身份标识:释放锁时必须传入同一个Binder引用,防止别的进程乱释放
- 死亡监听:如果持有锁的进程挂了,Binder驱动会回调
binderDied(),PMS自动清理该进程的所有锁
我记得有一次排查一个「手机睡死」的问题,发现是某个三方应用在后台持有了PARTIAL_WAKE_LOCK,然后进程被LMK杀了,但锁没释放——等等,按道理Binder死亡通知会清理啊?后来发现是应用用了WorkSource把锁转移给了另一个进程,导致死亡通知没触发。这个坑我后面专门讲。
避坑指南:我曾经遇到一个案例,应用在native层通过JNI拿了WakeLock,但Binder令牌没传到Java层。结果应用Java进程挂了,native层的锁还活着,系统一晚上没睡。解决方案是在native层也注册Binder死亡通知。
WakeLock的优先级与排序
系统里同时可能有几十个WakeLock,谁更重要?PMS用一套优先级规则来决定:
优先级判定规则
| 优先级 | 条件 | 典型场景 |
|---|---|---|
| 最高 | 屏幕相关的锁(SCREEN_BRIGHT_WAKE_LOCK等) | 用户正在看视频 |
| 高 | PARTIAL_WAKE_LOCK + 前台进程 | 前台音乐播放 |
| 中 | PARTIAL_WAKE_LOCK + 后台进程 | 后台下载 |
| 低 | PROXIMITY_SCREEN_OFF_WAKE_LOCK | 通话中靠近耳朵 |
排序逻辑在applyWakeLockFlags()里:
private void applyWakeLockFlags(WakeLock wakeLock, int opUid, long opUidSeq) {
// 如果锁带有ACQUIRE_CAUSES_WAKEUP标志,强制亮屏
if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) {
wakeUpNoUpdateLocked(wakeLock.mAcquireTime, wakeLock.mTag, opUid,
opUidSeq, wakeLock.mPackageName);
}
// 如果锁带有ON_AFTER_RELEASE标志,释放后屏幕多亮一会儿
if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0) {
// 设置屏幕超时延长
}
}
你想想看,为什么要有优先级?因为系统进入休眠前,会遍历mWakeLocks列表。只要有一个锁是mActive == true,系统就不能睡。但不同锁对系统功耗的影响天差地别——屏幕锁亮着屏,一小时耗电10%;PARTIAL_WAKE_LOCK只保持CPU醒着,一小时可能只耗1%。
注意:WakeLock的优先级并不直接影响系统是否休眠,它影响的是「当锁释放时,系统要不要立即尝试休眠」。高优先级的锁释放后,系统会立即检查能否休眠;低优先级的锁释放后,系统可能等一个超时周期再检查。
排序的实际应用
PMS内部维护一个mWakeLocks列表,每次新增或释放锁时,会调用sortWakeLocksLocked()重新排序:
private void sortWakeLocksLocked() {
// 按优先级降序排列
Collections.sort(mWakeLocks, (a, b) -> {
// 屏幕锁优先
if (a.mFlags != b.mFlags) {
return getWakeLockPriority(a) - getWakeLockPriority(b);
}
// 同优先级按获取时间排序
return Long.compare(a.mAcquireTime, b.mAcquireTime);
});
}
这个排序有什么用?当系统决定休眠时,会从列表尾部(优先级最低的锁)开始逐个释放。低优先级的锁会被先强制释放,高优先级的锁能撑更久。说白了,系统在功耗和用户体验之间做了个权衡——你正在看视频,系统不会因为一个后台下载锁就让你黑屏。
嗯,这里要注意:排序只是内部管理手段,真正决定系统能否休眠的,还是mWakeLocks里是否还有活跃锁。排序影响的是「谁先被释放」而不是「能不能释放」。
整体架构图
下面这张图展示了WakeLock从应用到Framework的完整链路:
从这张图能看明白:应用拿锁走Binder到PMS,PMS维护锁列表,最后通过/sys/power/wake_lock告诉内核别睡。反过来,释放锁时PMS检查列表,如果空了就写wake_unlock让内核可以休眠。
我个人觉得,理解WakeLock的关键就三点:Binder令牌保证安全、列表管理保证有序、优先级排序保证体验。把这三点吃透了,遇到电源管理相关的问题,你至少知道该从哪里下手查。
调试技巧:想看当前系统有哪些WakeLock?adb shell dumpsys power 里有个 "Wake Locks:" 章节,会列出所有活跃锁的uid、tag、flags和获取时间。我排查耗电问题时,第一步就是看这个输出。
公众号:蓝海资料掘金营,微信deep3321