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

功耗异常管控中WakeLock机制的埋点和需求调研1.前言作为移动终端,电量是一种稀缺资源,需要尽可能的节省。于是,Android系统在空闲时,会主动进入到休眠状态。Android设备中运行的进程需要使用电量资源时,也需要向PMS申请一个WakeLock;当工作完成后,就释放掉申请的WakeLock。PMS通过判断当前是否还有进程持有WakeLock,就能得出系统是否空闲。经过调研PMS机制和HW的逆向源码,我们得到如下埋点函数。即HW也是根据电量服务的notifyWakeLockAcquiredLocked/notifyWakeLockRel

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/35018.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注