Android Fk: PowerManagerService重点整理

Android Fk: PowerManagerService重点整理
主要内容:
1.PowerManagerService的架构
2.Wakelock的知识
3.电源管理相关的知识
4.相关debug

  1. 具体场景释疑

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

1. PowerManagerService的架构

1.1 PowerManagerService家族整体架构

image.png

1.2 PowerManagerService的binder架构

image.png

1.3 PowerManagerService开机初始化

image.png

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,必须满足--mCount==0才会释放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

image.png

2.3 wakelock的持有释放流程

image.png

详细流程:


image.png

2.4 updatePowerStateLocked()做了什么

image.png

2.5 屏幕亮屏流程
(待整理)

3. 电源管理

3.1 电源管理的整体框架

image.png

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

image.png

3.3 电源信息更新

image.png

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浏览器打开此文件即可

打开结果:


image.png

参数意义:


image.png

5. 疑问解答

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

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


image.png

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中实现;

  1. 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​记录电量信息的主要结构:


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文...
    程序员Android1阅读 1,782评论 0 2
  • 生活总是让我们遍体鳞伤,但到后来,那些受伤的地方一定会变成我们最强壮的地方。---海明威 WakeLock是什么 ...
    小草凡阅读 28,939评论 2 18
  • 一些手机app(如微信、QQ等)有新消息来到达,手机屏幕即使在锁屏状态下也会亮起,并提示用户有新消息。但是,一般情...
    磨砺营IT阅读 3,135评论 0 2
  • 前言 本文主要围绕如下问题进行知识收集整理: 待机、睡眠与休眠的区别? Android开发者官网当中提到“idle...
    GrayMonkey阅读 11,564评论 4 22
  • 今天我读了一本书,叫女生日记,我读到了143页,里面讲的是我们的数学老师,马上就要不教我们了,南科梦见他...
    琦芷凡荏阅读 190评论 0 1