一、WakeLock 的类型和配置
RIL 中的 WakeLock:
// RIL.java 定义的两种类型
public static final int FOR_WAKELOCK = 0; // RIL 命令执行
public static final int FOR_ACK_WAKELOCK = 1; // ACK 应答发送
// 初始化配置
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
// FOR_WAKELOCK:RIL 请求/响应期间持有
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, RILJ_WAKELOCK_TAG);
mWakeLock.setReferenceCounted(false); // 不支持嵌套计数
// FOR_ACK_WAKELOCK:ACK 发送期间持有
mAckWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, RILJ_ACK_WAKELOCK_NAME);
mAckWakeLock.setReferenceCounted(false);
// 超时时间配置
mWakeLockTimeout = TelephonyProperties.wake_lock_timeout()
.orElse(DEFAULT_WAKE_LOCK_TIMEOUT_MS); // 默认 25s
mAckWakeLockTimeout = TelephonyProperties.wake_lock_timeout()
.orElse(DEFAULT_ACK_WAKE_LOCK_TIMEOUT_MS); // 默认 200ms
// WakeLock 计数器
int mWakeLockCount = 0; // 支持多个并发请求
int mWlSequenceNum = 0; // 序列号用于超时检查
int mAckWlSequenceNum = 0;
二、RIL WakeLock 的生命周期
流程 1:正常的 RIL 请求流程
1. 创建 RIL 请求
↓
RILRequest rr = obtainRequest(RIL_REQUEST_SIGNAL_STRENGTH, result, workSource);
↓
2. addRequest() 中自动获取 WakeLock
↓
acquireWakeLock(rr, FOR_WAKELOCK);
↓
synchronized (mWakeLock) {
mWakeLock.acquire(); // ← 实际获取
mWakeLockCount++; // 计数增加
mWlSequenceNum++; // 序列号增加
// 关键:设置 WorkSource 用于 BatteryStats 追踪
String clientId = rr.getWorkSourceClientId();
if (!mClientWakelockTracker.isClientActive(clientId)) {
mActiveWakelockWorkSource.add(rr.mWorkSource);
mWakeLock.setWorkSource(mActiveWakelockWorkSource);
}
// 关键:记录追踪信息
mClientWakelockTracker.startTracking(rr.mClientId,
rr.mRequest, rr.mSerial, mWakeLockCount);
// 关键:设置超时保护(25s)
Message msg = mRilHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
msg.arg1 = mWlSequenceNum;
mRilHandler.sendMessageDelayed(msg, mWakeLockTimeout);
}
↓
3. 发送请求给 Modem
↓
radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getSignalStrength", () -> {
networkProxy.getSignalStrength(rr.mSerial);
});
↓
4. 收到 Modem 响应
↓
onSignalStrengthResponse(RadioResponseInfo responseInfo, SignalStrength signalStrength)
↓
5. decrementWakeLock() 释放
↓
decrementWakeLock(rr);
↓
synchronized (mWakeLock) {
mClientWakelockTracker.stopTracking(rr.mClientId,
rr.mRequest, rr.mSerial,
(mWakeLockCount > 1) ? mWakeLockCount - 1 : 0);
String clientId = rr.getWorkSourceClientId();
if (!mClientWakelockTracker.isClientActive(clientId)) {
// 该客户端的所有请求都完成,移除 WorkSource
mActiveWakelockWorkSource.remove(rr.mWorkSource);
mWakeLock.setWorkSource(mActiveWakelockWorkSource);
}
if (mWakeLockCount > 1) {
mWakeLockCount--; // 只减计数,不释放
} else {
mWakeLockCount = 0;
mWakeLock.release(); // ← 最后一个请求完成时释放
}
}
↓
6. rr.release() 清理请求
流程 2:超时处理
EVENT_WAKE_LOCK_TIMEOUT 触发
↓
if (msg.arg1 == mWlSequenceNum && clearWakeLock(FOR_WAKELOCK)) {
if (mRadioBugDetector != null) {
mRadioBugDetector.processWakelockTimeout(); // 上报异常
}
if (RILJ_LOGD) {
int count = mRequestList.size();
riljLog("WAKE_LOCK_TIMEOUT mRequestList=" + count);
for (int i = 0; i < count; i++) {
rr = mRequestList.valueAt(i);
riljLog(i + ": [" + rr.mSerial + "] "
+ RILUtils.requestToString(rr.mRequest));
}
}
}
↓
clearWakeLock(FOR_WAKELOCK) // 强制释放
↓
synchronized (mWakeLock) {
if (mWakeLockCount == 0 && !mWakeLock.isHeld()) return false;
riljLog("NOTE: mWakeLockCount is " + mWakeLockCount
+ " at time of clearing");
mWakeLockCount = 0;
mWakeLock.release(); // 强制释放
mClientWakelockTracker.stopTrackingAll(); // 清空所有追踪
mActiveWakelockWorkSource = new WorkSource();
return true;
}
三、计数机制详解
多个并发 RIL 请求:
时刻 1:Request A(getSignalStrength)
mWakeLockCount = 0
acquireWakeLock(A) → mWakeLock.acquire()
mWakeLockCount = 1
时刻 2:Request B(getDataCallList)到达,A 还未完成
acquireWakeLock(B) → mWakeLock 已持有,不再 acquire()
mWakeLockCount = 2
时刻 3:Request C(queryNetworkSelectionMode)到达
acquireWakeLock(C)
mWakeLockCount = 3
时刻 4:Request A 完成
decrementWakeLock(A)
mWakeLockCount = 3 - 1 = 2
// 不调用 mWakeLock.release(),因为还有其他请求
时刻 5:Request B 完成
decrementWakeLock(B)
mWakeLockCount = 2 - 1 = 1
// 仍不释放
时刻 6:Request C 完成
decrementWakeLock(C)
mWakeLockCount = 1 - 1 = 0
mWakeLock.release() // ← 最后一个完成时释放
优势:
- 只在所有 RIL 请求都完成后才释放 WakeLock
- CPU 持续活跃,避免频繁唤醒
- 减少电池消耗
四、WorkSource 和 BatteryStats 追踪
核心机制:
// 每个 RIL 请求都关联一个 WorkSource(表示哪个应用触发的)
String clientId = rr.getWorkSourceClientId();
// 如果该客户端是第一次,添加其 WorkSource
if (!mClientWakelockTracker.isClientActive(clientId)) {
mActiveWakelockWorkSource.add(rr.mWorkSource);
mWakeLock.setWorkSource(mActiveWakelockWorkSource);
}
// 示例:
App A 调用 TelephonyManager.getSignalStrength()
↓ WorkSource = "App A"
↓ setWorkSource("App A")
↓ BatteryStats 记录:App A 导致 CPU 唤醒
App B 调用 TelephonyManager.getDataCallList()
↓ 同时到达(App A 还未完成)
↓ WorkSource = "App A, App B"
↓ setWorkSource("App A, App B")
↓ BatteryStats 记录:App A 和 App B 共同导致 CPU 唤醒
BatteryStats 最终显示:
Settings → Battery
• App A:5% (包括 RIL 唤醒消耗)
• App B:3% (包括 RIL 唤醒消耗)
• System (Telephony):2%
五、ACK WakeLock
目的:确保向 Modem 发送 ACK 响应不被中断
// 发送 ACK 时获取
private void sendAck(int service) {
RILRequest rr = RILRequest.obtain(RIL_RESPONSE_ACKNOWLEDGEMENT, null,
mRILDefaultWorkSource);
acquireWakeLock(rr, FOR_ACK_WAKELOCK);
↓
synchronized (mAckWakeLock) {
mAckWakeLock.acquire(); // 独立的 WakeLock
mAckWlSequenceNum++;
// 超时保护:200ms(比 FOR_WAKELOCK 的 25s 短很多)
Message msg = mRilHandler.obtainMessage(EVENT_ACK_WAKE_LOCK_TIMEOUT);
msg.arg1 = mAckWlSequenceNum;
mRilHandler.sendMessageDelayed(msg, mAckWakeLockTimeout);
}
}
// 区别:
// FOR_WAKELOCK:
// • 等待 Modem 响应(可能很长)
// • 支持计数
// • 超时 25s
//
// FOR_ACK_WAKELOCK:
// • 仅用于发送 ACK
// • 不计数(每次独立)
// • 超时 200ms
六、其他 WakeLock 使用场景
场景 1:短信处理(InboundSmsHandler)
// 初始化时获取并持有
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
mWakeLock.acquire(); // 初始化时获取
mWakeLock.setReferenceCounted(true); // 支持嵌套
// 进入 WaitingState 时的超时
private class WaitingState extends State {
@Override
public void enter() {
mWakeLock.acquire(); // 若未获取,则获取
// 10 分钟超时保护
sendMessageDelayed(obtainMessage(EVENT_RECEIVER_TIMEOUT),
SMS_WAKELOCK_TIMEOUT_MS); // 600,000ms
}
}
// 流程:
收到短信
↓ 获取 WakeLock(或增加计数)
↓ 进入 WaitingState
↓ 广播 SMS_RECEIVED intent
↓ 应用处理短信(可能很慢)
↓ 应用调用 setResultCode(RESULT_OK)
↓ 进入下一状态
↓ 释放 WakeLock(或减少计数)
超时(10 分钟):
↓ EVENT_RECEIVER_TIMEOUT 触发
↓ 强制释放 WakeLock
↓ 继续处理短信,不会永久阻塞
场景 2:Post-Dial 等待(GsmCdmaConnection)
// 用户拨号 *123# 等待结果
private void setPostDialState(PostDialState s) {
if (s == PostDialState.STARTED || s == PostDialState.PAUSE) {
synchronized (mPartialWakeLock) {
if (mPartialWakeLock.isHeld()) {
mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
} else {
acquireWakeLock(); // 获取
}
// 3 分钟超时
Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); // 60,000ms
}
} else {
mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
releaseWakeLock(); // 释放
}
}
// 超时保护:防止用户按错按钮但不继续,导致 WakeLock 永久持有
场景 3:IMS 电话(ImsPhone)
PowerManager pm = (PowerManager) context.getServicesSy stem(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
mWakeLock.setReferenceCounted(false);
// 用于 IMS 相关操作保持设备唤醒
场景 4:紧急通话(EmergencyStateTracker)
PowerManager pm = context.getSystemService(PowerManager.class);
mWakeLock = (pm != null) ? pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"telephony:" + TAG) : null;
// 紧急通话期间维持 WakeLock,确保不进入睡眠
场景 5:NITZ 时间验证
// 快速读取 elapsedRealtime 时需要 WakeLock
WakeLock wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
try {
wakeLock.acquire(); // 获取
long elapsedRealtime = deviceState.elapsedRealtimeMillis();
// ... 验证逻辑
} finally {
wakeLock.release(); // 释放
}
场景 6:SmsStorageMonitor
PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SmsStorageMonitor");
mWakeLock.setReferenceCounted(true); // 支持嵌套计数
// 发送 SMS 存储状态报告时保持唤醒
场景 7:多卡切换(ProxyController)
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
mWakeLock.setReferenceCounted(false);
// 设置无线功能配置时保持唤醒
场景 8:WakeLockStateMachine
// 状态机基础类的 WakeLock 管理
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, debugTag);
mWakeLock.acquire(); // 初始化时获取
// 进入 IdleState 时释放
private void releaseWakeLock() {
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
}
// 用途:用于接收广播的模块(如 SMS),需要从 Idle 状态快速唤醒
七、WakeLock 的性能影响
CPU 功耗对比:
CPU 状态 功耗 说明
─────────────────────────────
正常运行 1000 mW 屏幕亮、应用活跃
PARTIAL_WAKE_LOCK 20 mW 屏幕灭、CPU 活跃(仍在处理)
完全睡眠 5 mW 屏幕灭、CPU 睡眠
RIL WakeLock 持有时间典型值:
• getSignalStrength:100-500ms
• setupDataCall:500ms-5s
• 超时释放:25s(异常情况)
电池消耗估算:
• 1000 个 RIL 请求/天,平均 200ms = 200s × 20mW = 4000mWs = 1.1mAh
• 相对总容量 4000mAh:0.028% 电池消耗
八、监控和调试
日志信息:
// RIL.java 中的日志
"WAKE_LOCK_TIMEOUT mRequestList=" + count
// 表示有 count 个 RIL 请求未完成
"ACK_WAKE_LOCK_TIMEOUT"
// ACK 发送超时(200ms)
// 每个请求的追踪
mClientWakelockTracker.startTracking(rr.mClientId, rr.mRequest, rr.mSerial, mWakeLockCount);
// 记录:哪个客户端、什么请求、序列号、当前计数
BatteryStats 查询:
adb shell dumpsys battery unreachable
adb shell dumpsys power_profile
// 查看 WakeLock 持有情况
adb shell dumpsys power_manager | grep WakeLock
九、最佳实践
1. 始终使用 PARTIAL_WAKE_LOCK
• 屏幕可关闭,节省电池
• 对用户无感知
2. 设置超时保护
• 防止死锁导致永久持有
• Telephony 中统一 25s 超时
3. 正确使用 WorkSource
• 用于 BatteryStats 追踪
• 帮助诊断问题应用
4. 释放前检查 isHeld()
• 防止重复释放异常
5. 使用 synchronized 保护
• WakeLock 非线程安全
• 所有获取/释放都要同步
6. 监控计数一致性
• acquire() 和 release() 要配对
• 计数不匹配表示有泄漏
总结
| 方面 | RIL WakeLock | SMS WakeLock | Post-Dial WakeLock |
|---|---|---|---|
| 获取时机 | RIL 请求发送 | SMS 收到 | 拨号后等待 |
| 释放时机 | Modem 响应 | 广播处理完成 | 拨号完成或超时 |
| 超时时间 | 25s | 10 分钟 | 3 分钟 |
| 计数支持 | 是(多请求并发) | 是(重入) | 否(单个连接) |
| WorkSource 追踪 | 是(用于 BatteryStats) | 否 | 否 |
| 关键用途 | 防止 Modem 通信中断 | 防止短信处理中断 | 防止用户拨号中断 |