Android Fk: PowerManagerService重点整理

主要内容:
1.PowerManagerService的架构
2.Wakelock的知识
3.电源管理相关的知识
4.相关debug
5. 具体场景释疑

本文涉及到的图片由draw.io绘制,绘制的原xml文件
时序图由plantuml绘制,uml文件
几个用于学习该模块的资料
均分享在百度云中
https://pan.baidu.com/s/1lDxJ_Z-tmjqCB9hi_JFG8g

1. PowerManagerService的架构

1.1 PowerManagerService家族整体架构

在这里插入图片描述

1.2 PowerManagerService的binder架构

在这里插入图片描述

1.3 PowerManagerService开机初始化

在这里插入图片描述

1.4 PowerManagerService中的重要接口

Wakeup(): 强制系统从睡眠状态唤醒,此接口对应用是不开放的,应用想唤醒系统必须通过设置亮屏标志

gotoSleep(): 强制系统进入到睡眠状态,此接口也是应用不开放。

userActivity(): 向 PowerManagerService 报告影响系统休眠的用户活动,重计算灭屏时间,背光亮度等,例如触屏,划屏等用户活动;

Wakelock: wakelock 是 PowerManager 的一个内部类,提供了相关的接口来操作 wakelock 锁, 比如
newWakeLock()方法来创建 wakelock 锁,acquire()和 release()方法来申请和释放锁。

isDeviceIdleMode(): 返回设备当前是否处于idle状态

setBacklightBrightness() : 设置屏幕背光值

isScreenOn(): 返回屏幕当前是否处于亮屏(可交互状态),不推荐使用该接口,后面谷歌可能会删掉该
接口推荐使用isInteractive();

reboot(): 重启手机( Requires the {@link android.Manifest.permission#REBOOT} permission.)
shutdown():关机( Requires the {@link android.Manifest.permission#REBOOT} permission.)

2 Wakelock相关知识

2.1 wakelock的种类

PowerManager.WakeLock 有加锁和解锁两种状态,加锁的方式有两种:
• 永久锁:,这样的锁除非显式的放开,否则是不会解锁的,所以这种锁用起来要非常的小心(默认)。

xref: /v8-n-mido-dev/frameworks/base/core/java/android/os/PowerManager.java
1204        /**
1205         * Acquires the wake lock with a timeout.
1206         * <p>
1207         * Ensures that the device is on at the level requested when
1208         * the wake lock was created.  The lock will be released after the given timeout
1209         * expires.
1210         * </p>
1211         *
1212         * @param timeout The timeout after which to release the wake lock, in milliseconds.
1213         */
1214        public void acquire(long timeout) {
1215            synchronized (mToken) {
1216                acquireLocked();
1217                mHandler.postDelayed(mReleaser, timeout);
1218            }
1219        }

• 超时锁:这种锁会在锁住后一段时间解锁。

xref: /v8-n-mido-dev/frameworks/base/core/java/android/os/PowerManager.java
1204        /**
1205         * Acquires the wake lock with a timeout.
1206         * <p>
1207         * Ensures that the device is on at the level requested when
1208         * the wake lock was created.  The lock will be released after the given timeout
1209         * expires.
1210         * </p>
1211         *
1212         * @param timeout The timeout after which to release the wake lock, in milliseconds.
1213         */
1214        public void acquire(long timeout) {
1215            synchronized (mToken) {
1216                acquireLocked();
1217                mHandler.postDelayed(mReleaser, timeout);
1218            }
1219        }
1220

以锁的类型来划分也是可分为两种:
• 计数锁:应用调用一次 acquire 申请必定会对应一个 release 来释放;mRefCounted 为true,那么必须满足条件
mCount++0才会去执行acquire,必须满足–mCount0才会释放WakeLock,这也就意味着,计数锁只会真正执行
第一次申请acquireWakeLock,以及最后一次释放,releaseWakeLock;再次申请,以及再次释放,只是对申请次数
以及释放次数的统计。所以每一次acquire 都必须一一对应一个release 操作

• 非计数锁:非计数锁应用调用多次acquire, 调用一次 release 就可释放前面 acquire 的锁。
setReferenceCounted(boolean)设置计数锁和非计数锁

xref: /v8-n-mido-dev/frameworks/base/core/java/android/os/PowerManager.java
acquire:
1221        private void acquireLocked() {
1222            if (!mRefCounted || mCount++ == 0) {
1223                // Do this even if the wake lock is already thought to be held (mHeld == true)
1224                // because non-reference counted wake locks are not always properly released.
1225                // For example, the keyguard's wake lock might be forcibly released by the
1226                // power manager without the keyguard knowing.  A subsequent call to acquire
1227                // should immediately acquire the wake lock once again despite never having
1228                // been explicitly released by the keyguard.
1229                mHandler.removeCallbacks(mReleaser);
1230                Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
1231                try {
1232                    mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
1233                            mHistoryTag);
1234                } catch (RemoteException e) {
1235                    throw e.rethrowFromSystemServer();
1236                }
1237                mHeld = true;
1238            }
1239        }

realese:
1265        public void release(int flags) {
1266            synchronized (mToken) {
1267                if (!mRefCounted || --mCount == 0) {
1268                    mHandler.removeCallbacks(mReleaser);
1269                    if (mHeld) {
1270                        Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
1271                        try {
1272                            mService.releaseWakeLock(mToken, flags);
1273                        } catch (RemoteException e) {
1274                            throw e.rethrowFromSystemServer();
1275                        }
1276                        mHeld = false;
1277                    }
1278                }
1279                if (mCount < 0) {
1280                    throw new RuntimeException("WakeLock under-locked " + mTag);
1281                }
1282            }
1283        }

2.2 wakelock的flag

在这里插入图片描述

2.3 wakelock的持有释放流程

在这里插入图片描述
详细流程:
在这里插入图片描述

2.4 updatePowerStateLocked()做了什么
在这里插入图片描述
2.5 屏幕亮屏流程
(待整理)
电源键点亮屏幕
在这里插入图片描述

3. 电源管理

3.1 电源管理的整体框架

在这里插入图片描述

3.2 Framework注册natvie层电源信息监听器

在这里插入图片描述

3.3 电源信息更新

在这里插入图片描述

4. 电源debug

1. adb shell dumpsys battery

AC powered :false  表示是否连接电源供电,false无供电
USB powered :true 表示是否USB使用供电,true供电
status :5 表示电池充电状态 5表示电量是满的
health :2 表示电池健康状况 2表示良好
present: true 表示手机上是否有电池 ,true表示有电池
level :100 表示当前剩余电量信息 100表示100% 
scale:100 表示电池电量最大值
voltage:4332 表示当前电池电压 单位mv
temperature: 314 表示当前电池温度 314表示31.4度
technology:Li-ion 表示电池使用技术

adb shell dumpsys power //
adb shell dumpsys battery unplug   //相当于不插电
adb shell dumpsys deviceidle step    //让状态转换Doze模式
adb shell dumpsys battery reset //重置电源状态
adb shell dumpsys battery set level 10 //将电池电量改为10
adb shell dumpsys batterystatus 

2. 电量信息测试方法(adb shell dumpsys batterystats)

1.首先需下载historian.py脚本,下载地址:https://github.com/google/battery-historian
2.下载后解压,battery-historian-master\battery-historian-master\scripts目录下,historian.py脚本在该目录下
3.在此目录下执行操作
4.执行步骤
1)首先要初始化batterystats数据
adb shell dumpsys batterystats --enable full-wake-history
shell dumpsys batterystats --reset
2)上面的操作执行完毕后,拔掉手机,操作你的App,操作完成后,重新连接手机,执行下面的命令,收集Battery数据:
adb shell dumpsys batterystats > batterystats.txt
3)得到这些数据后,这个时候使用我们的battery-historian来生成我们可见HTML报告:
python historian.py batterystats.txt > batterystats.html
4)用google浏览器打开此文件即可

打开结果:
在这里插入图片描述
参数意义:
在这里插入图片描述

5. 疑问解答

1.UserActivity最常调用的地方,按音量键是否调用?

如果我们在Settings中设置sleep时间为15s,那么15秒内如果没有任何操作,屏幕就会熄灭(当然,没有WakeLock未被释放是前提)。
如果在这个时间内用户有操作:touch屏幕或者按下菜单键、返回键等,那么这时就会调用PowerManagerService的UserActivity方法,
重新计算亮屏至灭屏的timeout时间。
屏幕及按键事件的调用主要在InputDispatcher中通过JNI调用PMS的userActivityFromNative()方法,在KeyGuard中也有调用userActivity()方法的地方,
也是为了更新屏幕是否需要更新屏幕灭屏的timeout时间,由keyGuard自己的逻辑调用。
在这里插入图片描述

2. 打电话亮灭屏时锁是怎么申请的(靠近远离时对于是申请还是释放)?

PROXIMITY_SCREEN_OFF_WAKE_LOCK的使用方法
​在新建call连接时incallui里 会新建一个ProximitySensor的对象,在ProximitySensor的
构造函数中持有一个PROXIMITY_SCREEN_OFF_WAKE_LOCK
类型的wakelock,mProximityWakeLock;

在判断需要距离传感器工作的场景下执行mProximityWakeLock.acquire()
不需要距离传感器工作的情况下执行mProximityWakeLock.release()
即申请该wakelock会将距离传感器唤醒;释放会关闭距离传感器。屏幕的亮灭由传感器模块控制
具体的场景:
a. 打通电话,InCallUI全屏显示即申请该wakelock,打开距离传感器,(屏幕亮灭由传感器控制,如果是传感器导致的灭屏
该锁不会释放,系统也不会进入睡眠状态;)
b. 这时如果按home键,IncallUI退至后台此时释放该wakelock,关闭距离传感器;
c. 打通电话,InCallUI全屏显示即申请该wakelock,打开距离传感器,如果不做操作即没有userActivity屏幕自动灭屏,将释放该wakelock,关闭传感器;只能由power键唤醒屏幕;
d. power键影响:如果已申请该wakelock,距离传感器已开,通过power键使灭屏会释放该wakelock,距离传感器关闭,再点击power键点亮屏幕后会再次申请该wakelock,唤醒距离传感器;

如上d场景有歧义,这里详细描述下:
d. power键影响:如果已申请该wakelock,距离传感器已开,通过power键使灭屏会释放该wakelock,距离传感器关闭,再点击power键点亮屏幕后会再次申请该wakelock,唤醒距离传感器;

1.通过power键使灭屏会释放该wakelock 有歧义: 通过power键灭屏,InCallUI退至后台,释放距离传感器的wakelock是在InCallUI退至后台主动释放的, 不是power键事件去释放的;
2.释放该wakelock,距离传感器关闭,有歧义: 如果释放时有这个RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY​ flag,会推迟释放该wakelock,直到物体离开传感器才会去释放wakelock,然后关闭距离传感器;

flag定义:
frameworks/base/core/java/android/os/PowerManager.java
260    /**
261     * Flag for {@link WakeLock#release WakeLock.release(int)}: Defer releasing a
262     * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor
263     * indicates that an object is not in close proximity.
264     */
265    public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1;
InCallUI中使用:
packages/apps/InCallUI/src/com/android/incallui/ProximitySensor.java
86    private void turnOffProximitySensor(boolean screenOnImmediately) {
87        if (mProximityWakeLock != null) {
88            if (mProximityWakeLock.isHeld()) {
89                Log.i(this, "Releasing proximity wake lock");
90                // Because of ultrasonic sensor not work well sometimes, turn on screen immediately
91                int flags = screenOnImmediately || Utils.isUltrasonicSensorDevice()
92                        ? 0 : PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY;
93                mProximityWakeLock.release(flags);
94            } else {
95                Log.i(this, "Proximity wake lock already released");
96            }
97        }
98    }

3.ACQUIRE_CAUSES_WAKEUP​不能和PARTIAL_WAKE_LOCK一起使用的原因?

PARTIAL_WAKE_LOCK只持有CPU锁,如后台下载,听音乐,虽然屏幕灭了但是CPU不休眠,
三种ScreenLock:
PowerManagerService.java

907    @SuppressWarnings("deprecation")
908    private static boolean isScreenLock(final WakeLock wakeLock) {
909        switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
910            case PowerManager.FULL_WAKE_LOCK:
911            case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
912            case PowerManager.SCREEN_DIM_WAKE_LOCK:
913                return true;
914        }
915        return false;
916    }

以上三种是屏幕相关的wakelock,如果flag中含有ACQUIRE_CAUSES_WAKEUP可以将屏幕wakeup,但是其和PARTIAL_WAKE_LOCK同用会没有持有屏幕锁导致屏幕不亮,或者亮屏下持有该锁也无法保持屏幕一直亮;
因此ACQUIRE_CAUSES_WAKEUP应和ScreenLock类型的flag一起使用才能达到想要的效果(灭屏时申请,点亮对应屏幕或键盘,屏幕为操作超时后保持屏幕或键盘对应亮度);

4.申请锁后再释放锁,这时是什么状态(原来的状态么)?

ACQUIRE_CAUSES_WAKEUP​与三种ScreenLock配合使用效果:
​亮屏状态申请:
FULL_WAKE_LOCK:申请后 屏幕超时该灭屏的时候可以保持屏幕正常亮度,键盘灯保持亮起状态,之后释放该wakelock,屏幕和键盘灯立刻灭;
SCREEN_BRIGHT_WAKE_LOCK:申请后 屏幕超时该灭屏的时候可以保持屏幕正常亮度,键盘不亮,之后释放该wakelock,屏幕立刻灭;​
SCREEN_DIM_WAKE_LOCK:申请后 屏幕超时该灭屏的时候可以保持屏幕处于dim亮度,键盘灯不亮,之后释放该wakelock,屏幕立刻灭;​
灭屏状态申请:屏幕正常亮起,无用户操作超时后同上;
屏幕或键盘灯未到超时时间释放,将继续保持未超时状态,走超时后该灭屏灭键盘等行为;

5.wakelock 申请和释放kenerl是怎么处理write进来的数据的?

kernel对wake_lock的sysfs接口文件的处理在kernel\power\wakelock.c中定义
这部分需要大量学习暂未完成,日后有需要时再深入学习,先梳理大概流程:
1.上层接口通过JNI调用hal层的power.c将wakelock信息写入/sys/power/wake_lock​ sysfs文件,这部分在hardware/libhardware_legacy/power/power.c中实现;
2.之后将调用kernel中PM core的wakelock模块的pm_wake_lock方法,处理和管理wakelock,这部分在kernel/kernel/power/wakelock.c中实现;
3. PM core的wakeup模块向device driver上报一个wakeup event,用来阻止系统suspend,这部分在drivers/base/power/wakeup.c中实现;
4.device driver将设备的wakeup信息,以sysfs的形式提供到用户空间供查询配置,这部分在在drivers/base/power/sysfs.c中实现。

6.Battery的数据保存在哪里,什么时机保存?

BatteryStats的数据保存在/data/system/batterystats.bin中,在对应模块需要更新电量统计时调用BatteryStatsImpl接口口来向该文件写入,分门别类的统计每个部分的耗电情况,如说的healthd更新电池电量至BatteryService的回调中就在必走的processValuesLocked​方法中调用了BatteryStatService的记录电池信息的方法:

424    private void processValuesLocked(boolean force) {
439        ...
457
458        // Let the battery stats keep track of the current level.
459        try {
460            mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth,
461                    mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature,
462                    mBatteryProps.batteryVoltage, mBatteryProps.batteryChargeCounter);
463        } catch (RemoteException e) {
464            // Should never happen.
465        }
466​        ...
626    }

BatteryStats​记录电量信息的主要结构:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/TaylorPotter/article/details/83824647