功耗管理与唤醒锁 (WakeLock) 架构文档
description: “Android 16 Telephony 功耗管理与唤醒锁架构文档,涵盖 RIL WakeLock 引用计数 + 序列号防重入机制、ClientWakelockTracker 调用方级电量追踪、WakeLockStateMachine 定向广播模式、Multi-Source Lock 多原因关机计数、Thermal 热缓解关机等完整功耗管理设计。”
功耗管理与唤醒锁 (WakeLock) 架构文档
1. 概述
Telephony 子系统是 Android 设备功耗的主要消耗源之一。Modem 与 AP 之间的通信存在本质的异步性:AP 发送命令后,Modem 可能需要几毫秒到几十秒才能响应。在此期间,如果 CPU 进入深度休眠,RIL 层将无法接收 Modem 的响应,导致命令超时、重试,甚至 Radio 挂死。
因此,Telephony 系统在所有关键路径上使用PowerManager.PARTIAL_WAKE_LOCK确保 CPU 不会在等待 Modem 响应期间休眠。Android 16 的 WakeLock 管理体现了引用计数 + 超时保护 + 精细追踪的三层设计。
1.1 核心组件
| 组件 | 职责 | 文件 |
|---|---|---|
| RIL WakeLock | 每发送一个 RIL 请求就 acquire,每收一个响应就 release | RIL.java |
| ClientWakelockTracker | 追踪每个调用方持有的 WakeLock,按 UID/PID 统计唤醒时长 | ClientWakelockTracker |
| WakeLockStateMachine | 小区广播/短信处理期间的 WakeLock 管理(状态机驱动) | WakeLockStateMachine.java |
| ProxyController WakeLock | Radio Capability 变更期间的 WakeLock 保护 | ProxyController.java |
| Multi-Reason Radio Power Off | 多原因关机集合,确保所有原因消除后才开机 | ServiceStateTracker.java |
2. RIL WakeLock — 引用计数 + 序列号防重入
2.1 双 WakeLock 设计
RIL 有两个WakeLock,分别服务于不同用途:
// 主 WakeLock — 为每个 RIL 请求/响应周期持有// Tag: "*telephony-radio*"publicfinalWakeLockmWakeLock;// ACK WakeLock — 为 Modem 的 ACK 响应持有// Tag: "RILJ_ACK_WL"publicfinalWakeLockmAckWakeLock;// 超时配置privatestaticfinalintDEFAULT_WAKE_LOCK_TIMEOUT_MS=60000;// 主 WakeLock: 60 秒privatestaticfinalintDEFAULT_ACK_WAKE_LOCK_TIMEOUT_MS=200;// ACK WakeLock: 200 毫秒// 引用计数(不是简单的 acquire/release,而是计数管理)intmWakeLockCount;Google设计技巧 #1 — 双 WakeLock 分级超时设计(Dual WakeLock with Tiered Timeout):
主请求的 WakeLock 超时 = 60 秒,足够大多数 Modem 操作完成。ACK 的 WakeLock 超时只有 200 毫秒,因为 ACK 只是 Modem 说"我收到了",应该在极短时间内完成。这种分级设计避免了"一锅端"的超时:如果 ACK 超时,只释放 ACK WakeLock;主请求仍持有自己的 WakeLock 等待真实的响应数据。
2.2 WakeLock 序列号防重入(Sequence Number Anti-Reentrancy)
HAL 通信有两个并发通道:请求通道和 ACK 通道。两个通道各自使用独立的 WakeLock 和序列号,互不干扰:
// FOR_WAKELOCK = 0 — 请求/响应通道// FOR_ACK_WAKELOCK = 1 — ACK 通道publicstaticfinalintFOR_WAKELOCK=0;publicstaticfinalintFOR_ACK_WAKELOCK=1;volatileintmWlSequenceNum=0;// 请求通道序列号volatileintmAckWlSequenceNum=0;// ACK 通道序列号序列号用于防止定时器误触发:
// RilHandler.handleMessage()caseEVENT_WAKE_LOCK_TIMEOUT:synchronized(mRequestList){// 关键检查:只有当时钟到期且序列号匹配时才执行if(msg.arg1==mWlSequenceNum&&clearWakeLock(FOR_WAKELOCK)){if(mRadioBugDetector!=null){mRadioBugDetector.processWakelockTimeout();}// 记录当前所有未完成请求(用于诊断)intcount=mRequestList.size();riljLog("WAKE_LOCK_TIMEOUT mRequestList="+count);for(inti=0;i<count;i++){rr=mRequestList.valueAt(i);riljLog(i+": ["+rr.mSerial+"] "+RILUtils.requestToString(rr.mRequest));}}}break;当 Modem 正常响应后,mWlSequenceNum++,之前的超时定时器因为msg.arg1 != mWlSequenceNum被自动忽略。
Google设计技巧 #2 — 序列号防重入(Sequence Number Guard):
在有大量并发请求的场景中,如果没有序列号机制,一个旧定时器可能在 WakeLock 已经被新的请求重新 acquire 后仍然触发,导致正常运行中的 WakeLock 被错误释放。解决方法:每次 acquire 时递增
mWlSequenceNum++,超时事件携带当时的序列号。只有当定时器的序列号与当前序列号匹配时,才执行超时清理。
2.3 acquireWakeLock — 引用计数 + WorkSource 追踪
privatevoidacquireWakeLock(RILRequestrr,intwakeLockType){synchronized(rr){if(rr.mWakeLockType!=INVALID_WAKELOCK){riljLog("Failed to acquire wakelock for "+rr.serialString());return;}switch(wakeLockType){caseFOR_WAKELOCK:synchronized(mWakeLock){mWakeLock.acquire();// 增加内核 WakeLock 引用计数mWakeLockCount++;mWlSequenceNum++;StringclientId=rr.getWorkSourceClientId();if(!mClientWakelockTracker.isClientActive(clientId)){// 新调用方 → 加入 WorkSourcemActiveWakelockWorkSource.add(rr.mWorkSource);mWakeLock.setWorkSource(mActiveWakelockWorkSource);}mClientWakelockTracker.startTracking(clientId,...);// 消息的超时时间携带当前序列号Messagemsg=mRilHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);msg.arg1=mWlSequenceNum;mRilHandler.sendMessageDelayed(msg,mWakeLockTimeout);}rr.mWakeLockType=FOR_WAKELOCK;break;// ...}}}2.4 decrementWakeLock / clearWakeLock
// 正常递减:每个 RIL 请求完成时调用privatevoiddecrementWakeLock(RILRequestrr){synchronized(rr){switch(rr.mWakeLockType){caseFOR_WAKELOCK:synchronized(mWakeLock){// 从 ClientWakelockTracker 中移除该请求mClientWakelockTracker.stopTracking(rr.mClientId,...);if(mWakeLockCount>1){mWakeLockCount--;// 如果还有请求待处理,只递减计数,不真正 release}}break;}rr.mWakeLockType=INVALID_WAKELOCK;}}// 强制清理:WakeLock 超时时调用privatebooleanclearWakeLock(intwakeLockType){if(wakeLockType==FOR_WAKELOCK){synchronized(mWakeLock){if(mWakeLockCount==0&&!mWakeLock