主要內容:
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記錄電量信息的主要結構: