[Android Fk] PowerManagerService簡單整理 Based on Android O

主要內容:
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​記錄電量信息的主要結構:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章