功耗异常管控中WakeLock机制的埋点和需求调研

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 值CPUScreenKeyboard备注
PARTIAL_WAKE_LOCKOnOffOff不受Power键影响
SCREEN_DIM_WAKE_LOCKOnDimOff按下电源键,仍然可进入休眠
SCREEN_BRIGHT_WAKE_LOCKOnBrightoff按下电源键,仍然可进入休眠
FULL_WAKE_LOCKOnBrightOn按下电源键,仍然可进入休眠

上述看,如果滥用下,很容易导致耗电异常。

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状态的最小模型,方便获取各种定制化接口。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
PowerManager.WakeLock 是一个 Android 的类,用于管理设备电源的锁定状态。它允许应用程序在一些特殊的情况下保持设备处于唤醒状态,以确保应用程序可以继续运行。 在 Android ,设备通常会在一段时间后进入休眠状态,以节省电量。在这种情况下,CPU 和其他一些硬件组件会进入低功耗模式,此时应用程序的运行也会受到限制。 PowerManager.WakeLock 的作用就是可以防止设备进入休眠状态,以保持设备的唤醒状态。这样,应用程序可以继续执行一些需要持续运行的操作,如后台下载、音乐播放、定时任务等。 使用 PowerManager.WakeLock 需要以下几个步骤: 1. 获取 PowerManager 的实例: ```java PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); ``` 2. 创建 WakeLock 对象: ```java PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLockTag"); ``` 这里的第一个参数表示锁定级别,常见的有: - PARTIAL_WAKE_LOCK:部分唤醒锁定,保持 CPU 运行,但允许屏幕关闭。 - SCREEN_DIM_WAKE_LOCK:屏幕亮度唤醒锁定,保持 CPU 运行和屏幕亮度降低。 - SCREEN_BRIGHT_WAKE_LOCK:屏幕高亮度唤醒锁定,保持 CPU 运行和屏幕高亮度。 - FULL_WAKE_LOCK:全唤醒锁定,保持 CPU 运行和屏幕高亮度。 第二个参数是一个标签,用于标识唤醒锁定的用途。 3. 获取唤醒锁: ```java wakeLock.acquire(); ``` 这个方法会请求获取唤醒锁定,使设备保持唤醒状态。 4. 释放唤醒锁: ```java wakeLock.release(); ``` 当不再需要保持设备唤醒状态时,需要调用 release 方法释放唤醒锁定。 需要注意的是,使用 PowerManager.WakeLock 要谨慎,确保在不需要时及时释放锁定,以免造成电池消耗过大。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

法迪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值