1. 前言
作为移动终端,电量是一种稀缺资源,需要尽可能的节省。于是,Android系统在空闲时,会主动进入到休眠状态。
Android设备中运行的进程需要使用电量资源时,也需要向PMS申请一个WakeLock;当工作完成后,就释放掉申请的WakeLock。PMS通过判断当前是否还有进程持有WakeLock,就能得出系统是否空闲。
经过调研PMS机制和HW的逆向源码,我们得到如下埋点函数。
即HW也是根据电量服务的notifyWakeLockAcquiredLocked/notifyWakeLockReleasedLocked/notifyWakeLockChangingLocked 进行wakeLock的埋点,从而建立wakeLock状态的最小模型,方便获取各种定制化接口。
埋点函数 | 作用 |
---|
PowerManagerService.notifyWakeLockAcquiredLocked | 应用持锁埋点 | PowerManagerService.notifyWakeLockReleasedLocked | 应用释放锁埋点 | PowerManagerService.notifyWakeLockChangingLocked | 锁配置更新埋点 |
通过上述函数我们需要得到的函数
API接口 | 作用 |
---|
getWkTimeByUidPid | 根据UID\PID获取持锁时间 | getWkTimeByUid | 根据UID获取持锁时间 | getWkHoldingTime | 获取阻止休眠时长 | getWkTimeByUidPidTAG | 根据UID\PID\TAG获取持锁TAG | getWkTagByUidPid | 根据UID\PID获取持锁TAG | getWkTimeByTag | 根据TAG获取持锁时长 | getWkUidsByTag | 根据TAG获取持锁UID | getWkPidsByTag | 根据TAG获取持锁PID | getHoldingWkPidsByUid | 根据UID获取阻止休眠PID | isHoldWkByTag | 根据TAG获取持锁TAG | getLastReleaseAudioMixUid | 获取最近音频输出的释放锁 | getLastReleaseAudioInUid | 获取最近音频输入的释放锁 | getHoldingJobWkTime | 获取持锁Job时长 | | |
2. PowerManagerService SDK
2.1 DEMO
使用 PowerManager.newWakeLock API 进行持锁管理,WakeLock Flag一般与WakeLock Level组合使用
/*
Flag Value CPU Screen Keyboard
PARTIAL_WAKE_LOCK On Off Off 0x00000001 1
SCREEN_DIM_WAKE_LOCK On Dim Off 0x00000006 6
SCREEN_BRIGHT_WAKE_LOCK On Bright Off 0x0000000a 10
FULL_WAKE_LOCK On Bright Bright 0x0000001a 26
*/
object AlertWakeLock {
private val TAG = "AlertWakeLock"
private var sCpuWakeLock: PowerManager.WakeLock? = null
@SuppressLint("InvalidWakeLockTag")
internal fun createPartialWakeLock(context: Context): PowerManager.WakeLock? {
// 第一步:获取PowerManager的实例
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager ?: return null
// 第二步:调用PowerManager中的newWakeLock方法创建一个WakeLock对象
return pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, TAG)
//return pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, TAG);
}
fun acquireCpuWakeLock(context: Context) {
if (sCpuWakeLock != null) {
return
}
sCpuWakeLock = createPartialWakeLock(context)
// 第三步:acquire()获取相应的锁
sCpuWakeLock!!.acquire()
}
fun releaseCpuLock() {
if (sCpuWakeLock != null) {
// 最后:release释放
sCpuWakeLock!!.release()
sCpuWakeLock = null
}
}
}
2.2 levelAndFlags
WakeLock主要用于控制CPU、屏幕、键盘三部分
- PARTIAL_WAKE_LOCK = 0x00000001
- SCREEN_DIM_WAKE_LOCK = 0x00000006
- SCREEN_BRIGHT_WAKE_LOCK = 0x0000000a
- FULL_WAKE_LOCK = 0x0000001a
- PROXIMITY_SCREEN_OFF_WAKE_LOCK = 0x00000020
- DOZE_WAKE_LOCK = 0x00000040
- DRAW_WAKE_LOCK = 0x00000080
- ACQUIRE_CAUSES_WAKEUP = 0x10000000
- ON_AFTER_RELEASE = 0x20000000
2.2.1 四大天王-level
对于PARTIAL_WAKE_LOCK、SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK和FULL_WAKE_LOCK而言,不考虑Power键的话,随着等级的提高,权限也相应增大,即持有高等级的锁,能够激活的部分越多;如果考虑Power键的话,PARTIAL_WAKE_LOCK可以保证CPU不休眠,反而是权限最大的。
level 值 | CPU | Screen | Keyboard | 备注 |
---|
PARTIAL_WAKE_LOCK | On | Off | Off | 不受Power键影响 | SCREEN_DIM_WAKE_LOCK | On | Dim | Off | 按下电源键,仍然可进入休眠 | SCREEN_BRIGHT_WAKE_LOCK | On | Bright | off | 按下电源键,仍然可进入休眠 | FULL_WAKE_LOCK | On | Bright | On | 按下电源键,仍然可进入休眠 |
上述看,如果滥用下,很容易导致耗电异常。
2.2.1 levelAndFlags
levelAndFlags | 作用 |
---|
PROXIMITY_SCREEN_OFF_WAKE_LOCK | 无法阻止系统休眠。当系统处于唤醒态时,传感器发觉终端某个物体比较近时,关闭屏幕。重新拉个某个物体距离后,点亮屏幕。例如通话时,贴耳通话灭屏,远离耳朵亮屏 | DOZE_WAKE_LOCK | 终端处于Dozing state时,使能CPU挂起,屏幕处于低电量模式。 | DRAW_WAKE_LOCK | 终端处于Dozeing state 时,使应用获取足够的时间完成绘制。 | ACQUIRE_CAUSES_WAKEUP | 正常情况下,获取WakeLock并不会点亮屏幕(即acquire之前机器处于息屏状态,无法点亮屏幕)。加上这个Flag后,acquire Wakelock同时能够点亮屏幕 | ON_AFTER_RELEASE | 和用户体验有关。正常情况下当wakelock释放后,如果没有该标志位,那么系统会立即息屏。如果有该标志位,系统可以延长一段时间再息屏。 |
3. WakeLock
frameworks/base/core/java/android/os/PowerManager.java
WakeLock是PowerManager中的内部类
public final class WakeLock {
...
@UnsupportedAppUsage
private int mFlags;
@UnsupportedAppUsage
private String mTag;
private final String mPackageName;
private final IBinder mToken;
private int mInternalCount;
...
}
3.1 PowerManager.WakeLock.acquire()
我们知道一个进程创建的WakeLock,实际上表明了该进程执行某个工作时对电量的需求,例如声明该工作需要保持屏幕处于点亮状态,或该工作需要CPU处于唤醒态等。 因此,进程创建了WakeLock后,需要将WakeLock发送到PMS中,让PMS明白该进程的需求。 这种将WakeLock通知到PMS的过程,就被称为acquire WakeLock。
frameworks/base/core/java/android/os/PowerManager.java
public void acquire() {
synchronized (mToken) {
acquireLocked();
}
}
private void acquireLocked() {
...
// 工作流程将通过Binder通信进入到PMS中
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,mHistoryTag, mDisplayId);
...
}
PowerManager.WakeLock.acquire() 通过Binder调用到PowerManagerService
@Override // Binder call
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
...
acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag,uid, pid);
...
}
private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag,
String packageName, WorkSource ws, String historyTag, int uid, int pid) {
...
//PMS中维持了一个ArrayList,记录当前已申请的WakeLock
int index = findWakeLockIndexLocked(lock);
...
//如果index大于0,说明此时Acquire的是一个旧的WakeLock
if (index >= 0) {
wakeLock = mWakeLocks.get(index);
//这是判断WakeLock对应的成员变量是否发生改变
if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
// 改变则更新
wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);
}
} else {
...
// 监控申请WakeLock的进程是否死亡
lock.linkToDeath(wakeLock, 0);
...
//创建一个新的WakeLock,例如RIL第一次调用send就会进入该分支
wakeLock = new WakeLock(lock, displayId, flags, tag, packageName, ws, historyTag,
uid, pid, state);
//添加到wakelock列表
mWakeLocks.add(wakeLock);
// 特殊处理PARTIAL_WAKE_LOCK,根据Doze模式的白名单更新wakelock的disabled变量,可以定制:即使该应用申请了PARTIAL_WAKE_LOCK,也不能阻止系统进入休眠状态。
setWakeLockDisabledStateLocked(wakeLock);
}
// 处理WakeLock对应的Flag
// 判断WakeLock是否有ACQUIRE_CAUSES_WAKEUP,在必要时唤醒屏幕
applyWakeLockFlagsOnAcquireLocked(wakeLock, uid);
mDirty |= DIRTY_WAKE_LOCKS;
//更新电源状态
updatePowerStateLocked();
// 通知wakeLock发生变化,电量统计服务做相关统计
// 同时适合功耗异常埋点
notifyWakeLockAcquiredLocked(wakeLock);
}
3.2 PowerManager.WakeLock.release()
frameworks/base/core/java/android/os/PowerManager.java
public void release() {
release(0);
}
public void release(int flags) {
...
// 工作流程将通过Binder通信进入到PMS中
mService.releaseWakeLock(mToken, flags);
...
}
PowerManager.WakeLock.release() 通过Binder调用到PowerManagerService
private void releaseWakeLockInternal(IBinder lock, int flags) {
...
releaseWakeLockInternal(lock, flags);
...
}
private void releaseWakeLockInternal(IBinder lock, int flags) {
...
//根据Binder代理,从存储的ArrayList中找到对应WakeLock的序号
int index = findWakeLockIndexLocked(lock);
//RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY,表示当sensor判断终端离物体较远时,才真正释放PROXIMITY_SCREEN_OFF_WAKE_LOCK等级的WakeLock
if ((flags & PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) != 0) {
mRequestWaitForNegativeProximity = true;
}
//PMS不再关注客户端进程是否死亡
wakeLock.mLock.unlinkToDeath(wakeLock, 0);
removeWakeLockLocked(wakeLock, index);
}
private void removeWakeLockLocked(WakeLock wakeLock, int index) {
mWakeLocks.remove(index);
...
//通知BatteryStatsService,可作为功耗异常埋点
notifyWakeLockReleasedLocked(wakeLock);
applyWakeLockFlagsOnReleaseLocked(wakeLock);
mDirty |= DIRTY_WAKE_LOCKS;
updatePowerStateLocked();
}
3. 华为的wakeLock功耗异常埋点调研
Hw的WakeLock埋点
PowerManagerService
Notifier
Utils
LogPower
notifyWakeLockAcquiredLocked
notifyWakeLockReleasedLocked
notifyWakeLockChangingLocked
onWakeLockAcquired(160)
onWakeLockReleased(161)
onWakeLockChanging
noteWakelock
push(160/161)
PowerManagerService
Notifier
Utils
LogPower
Hw的埋点数据处理
DeviceMonitor
WakelockStats
createAllStats
handleScrState
handleStatsEvent(160/161)
1. getWkTimeByUidPid
2. getWkTimeByUid
3. getWkHoldingTime
4. getWkTimeByUidPidTAG
5. getWkTagByUidPid
6. getWkTimeByTag
7. getWkUidsByTag
8. getWkPidsByTag
9. getHoldingWkPidsByUid
10. isHoldWkByTag
11. getLastReleaseAudioMixUid
12. getLastReleaseAudioInUid
13. getHoldingJobWkTime
DeviceMonitor
WakelockStats
即HW也是根据电量服务的notifyWakeLockAcquiredLocked/notifyWakeLockReleasedLocked/notifyWakeLockChangingLocked 进行wakeLock的埋点,从而建立wakeLock状态的最小模型,方便获取各种定制化接口。
|