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()方法。流程大概是:

  1. 检查调用者权限(没有WAKE_LOCK权限?直接扔异常)
  2. 解析flags和tag,创建WakeLock对象
  3. 把锁加入mWakeLocks列表
  4. 调用applyWakeLockFlags()处理屏幕点亮等副作用
  5. 更新电源状态

释放时走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的完整链路:

WakeLock Framework 架构图 应用进程 PowerManager.WakeLock Binder IPC acquire/release 调用 PowerManagerService WakeLock管理核心 mWakeLocks 列表 ArrayList<WakeLock> Binder死亡通知 binderDied() 自动清理 优先级排序 sortWakeLocksLocked() 内核层 (Kernel) /sys/power/wake_lock /sys/power/wake_unlock 应用通过Binder与PMS通信,PMS管理锁列表并最终写入内核节点

从这张图能看明白:应用拿锁走Binder到PMS,PMS维护锁列表,最后通过/sys/power/wake_lock告诉内核别睡。反过来,释放锁时PMS检查列表,如果空了就写wake_unlock让内核可以休眠。

我个人觉得,理解WakeLock的关键就三点:Binder令牌保证安全、列表管理保证有序、优先级排序保证体验。把这三点吃透了,遇到电源管理相关的问题,你至少知道该从哪里下手查。

调试技巧:想看当前系统有哪些WakeLock?adb shell dumpsys power 里有个 "Wake Locks:" 章节,会列出所有活跃锁的uid、tag、flags和获取时间。我排查耗电问题时,第一步就是看这个输出。


公众号:蓝海资料掘金营,微信deep3321