1. 程式人生 > >Android Fk: PowerManagerService重點整理

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​記錄電量資訊的主要結構:
在這裡插入圖片描述