Telephony 中的 WakeLock 使用全面分析

7 阅读6分钟

一、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 A5% (包括 RIL 唤醒消耗)
  • App B3% (包括 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 WakeLockSMS WakeLockPost-Dial WakeLock
获取时机RIL 请求发送SMS 收到拨号后等待
释放时机Modem 响应广播处理完成拨号完成或超时
超时时间25s10 分钟3 分钟
计数支持是(多请求并发)是(重入)否(单个连接)
WorkSource 追踪是(用于 BatteryStats)
关键用途防止 Modem 通信中断防止短信处理中断防止用户拨号中断