IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 功耗异常管控中WakeLock机制的埋点和需求调研 -> 正文阅读

[移动开发]功耗异常管控中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状态的最小模型,方便获取各种定制化接口。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-22 18:47:45  更:2022-04-22 18:50:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 22:29:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码