版權宣告:
本賬號釋出文章均來自公眾號,承香墨影(cxmyDev),版權歸承香墨影所有。
未經允許,不得轉載。
一、前言
Android 中提供了很多系統的管理類,WindowManager 、ActivityManager、PowerManager 等,這些都是為了對一些系統的元件和服務進行管理,提供給開發者使用的。而 PowerManager 就是 Android 系統中,對電源狀態進行控制的一把管理類。
本文就 PowerManager 的做一個詳盡的介紹。
二、什麼是PowerManager
從名稱上也可以看出來 PowerManager 主要就是為了做電源狀態的管理。在實際開發過程中,我們使用PowerManager 主要是為了通過它獲取一個 WakeLock 物件,用於控制當前裝置的一些耗電操作。
首先要了解到,Google 一直在為 Android 系統省電做了特別大的努力,從最新的 Android O 的更新上就可以看出來,為了省電,最開發者做了非常多的限制。而在 Android 系統的裝置上,當手機靜置一段時間不去操作它之後,通常為了省電提高續航能力,會關閉手機螢幕或者降低手機螢幕的亮度、降低 CPU 頻率等。這是系統執行自發的一些行為,其實也是通過 PowerManager 進行管理的。
而如果我們需要自行調整這種行為,同樣也需要借用 PowerManager 這個類來進行操作。PowerManager 主要對三種資源進行管理:CPU、螢幕、鍵盤背光。 CPU 和螢幕,很好理解,而鍵盤背光最開始其實是指物理輸入鍵盤,但是在現在這個觸屏橫行的時代,基本上已經不存在了,可很多手機會做一些物理的 home、back 鍵,上面會有一些喚醒燈,其實也是通過 PowerManager 進行點亮和熄滅的管理的。
我們使用 PowerManager 其實也就是為了管理 CPU和螢幕資源,例如需要讓 CPU 在休眠狀態下依然保持高速的運算,在長期不動螢幕的時候,依然保持螢幕的常亮等等,接下來就讓我們看看如何使用 PowerManager 來對這些進行管理。
三、使用 PowerManager
1、PowerManager 的基本使用
既然節電是一個系統推薦的行為,那麼一般情況下,我們最好還是不要對其進行設定去操作它。但是如果一定需要操作的話,也最好使用級別最低的 WakeLock 鎖,並且在使用完成之後,立即釋放它。使用級別最低的 WakeLock 是非常有必要的,雖然為了方便,直接使用高階別的 WakeLock 的話,也是可以達到目的的,但是它將會更耗電,現在一些裝置上都有電量監控的 App ,它們會記錄耗電情況,並告知使用者所知。
PowerManager 的使用非常的簡單,可以通過 getSystemService()
方法來獲取它。
context.getSystemService(Context.POWER_SERVICE)
它將獲得一個 PowerManager 物件,如果需要獲取 WakeLock 物件,還需要使用 newWakeLock()
方法。
WakeLock 這個類沒有提供公有的構造方法,所以只能通過 newWakeLock() 來獲取。可以看到 newWakeLock()
需要的引數,其中 levelAndFlags 就是 WakeLock 的級別,後面我們會講解到。
而在其中,還會呼叫 validateWakeLockParameters()
方法,它主要是對引數進行一些校驗。
下面看看 WakeLock 使用的一個 Demo:
可以看到,使用 acquire()
獲取 WakeLock ,在使用完成之後,必須呼叫 release()
對 WakeLock 進行釋放。
為了正確的使用 WakeLock ,還需要在 AndroidManifest.xml 檔案中宣告 WAKE_LOCK 許可權。
<uses-permission android:name="android.permission.WAKE_LOCK" />
2、WakeLock 的 level 和 flag
前面提到 newWakeLock() 方法需要傳遞一個 levelAndFlags 的引數,用於標記當前需要持有的 WakeLock 的 級別和標識,也就是說 WakeLock 是通過 level 和 Flag 來唯一確定一個行為的,它們可以通過或運算子『|』進行拼接。
下面我們先看看關於 Level 的設定,Level 在 Android 的版本中,經過了多次調整,很多 Level 其實已經被廢棄了,這裡只對一些常用的 Level 進行介紹。
未被廢棄的 Level 引數:
- PARTIAL_WAKE_LOCK : 使 CPU 保持高效能執行,而螢幕和鍵盤背光可以被關閉。通常推薦使用它。
- PROXIMITY_SCREEN_OFF_WAKE_LOCK:用於和接近感測器配合使用,來實現電話類 App 中,手機貼近耳朵的時候,將螢幕關閉的功能,而離開的時候,又使螢幕兩期的功能。這個 level 在 API 21 之後才被開放出來,之前都是處於 @hide 的狀態。
已經被廢棄的 Level 引數:
- SCREEN_DIM_WAKE_LOCK:只限制螢幕,保證亮起,但是允許它亮度變低。
- SCREEN_BRIGHT_WAKE_LOCK:保證螢幕最高亮,但是鍵盤背光燈允許熄滅。
- FULL_WAKE_LOCK:保證螢幕最高亮度,並且鍵盤背光燈不允許熄滅。
仔細找規律,可以發現這幾個被廢棄的 Level 引數,都是用於限制螢幕常亮的,而當使用者主動按下電源鍵進行鎖屏的時候,這些 WakeLock 都將被系統給隱式釋放,導致鎖定的螢幕和 CPU都被關閉。除了 SCREEN_BRIGHT_WAKE_LOCK 是在 API 13 被標記廢棄之外,其他兩個都是在 API 17 中被標記廢棄的。
既然這些 Level 引數都已經被標記為 @Deprecated 了,那麼我們最好還是不要使用它。而有時候我們又需要在無操作的時候,依然保持螢幕常亮,Google 官方既然有廢棄的 Level 引數,同時也提供瞭解決方案。
可以使用 FLAG_KEEP_SCREEN_ON 來標記視窗,它會比 FULL_WAKE_LOCK 這些與螢幕相關的 WakeLock 更好,因為它會對螢幕進行隱式的正確管理,就無需我們自己去管理螢幕的亮度狀態,同時它是不需要我們在 AndroidManifest.xml 檔案中,額外的申請 WAKE_LOCK 許可權。
FLAG_KEEP_SCREEN_ON 是被定義在 WindowManager.LayoutParams 中的,下面是使用示例。
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);複製程式碼
前面提到 newWakeLock()
可以配置 level 之外,還可以組合配置一個 Flag 引數,在 PowerManager 中,定義了兩個 Flag。
- ACQUIRE_CAUSES_WAKEUP:它與 level 配合,可以使螢幕亮起。level 只是用於保持一個鎖,例如保持螢幕常亮,但是它只是一個持續保持的過程,如果呼叫 acquire() 的時候,螢幕已經熄滅,就需要額外新增 ACQUIRE_CAUSES_WAKEUP 將其點亮。需要注意的是,它不能和 PARTIAL_WAKE_LOCK 同時使用。
- ON_AFTER_RELEASE:它與 level 配合,標記如果釋放 WakeLock 的時候,螢幕處於亮著的狀態,則在釋放 WakeLock 之後,再讓螢幕保持亮一小會而,而如果釋放 WakeLock 的時候,螢幕已經熄滅,則不會有任何動作。
3、WakeLock 的一些方法
使用 level | flag 的組合形式,就可以獲取到一個 WakeLock 物件,雖然前面介紹了 WakeLock 的簡單使用,但是它實際上還有一些跟複雜的 API ,供我們使用。
- void acquire() : 獲取 WakeLock。
- void acquire(long timeOut) :獲取 WakeLock ,同時設定一個超時時間,當超過 timeOut 設定的時長之後,系統會自動釋放 WakeLock,不需要額外呼叫 release()。
- release() :釋放獲取的 WakeLock。
- release(int flag):釋放獲取的 WakeLock,並設定一個 Flag 標識。其實 Flag 的取值,暫時只有 RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY ,它表示在獲取 PROXIMITY_SCREEN_OFF_WAKE_LOCK 喚醒說的時候,如果當前感測器標記仍然有物品在靠近,則延遲到物品裡去的時候再釋放這個 WakeLock。
- boolean isHeld():判斷當前 WakeLock 是否已經被獲取。
- void setReferenceCounted(boolean value):它標記是否對 WakeLock 資源使用引用計數,它的預設值是 true。
PowerManager 對 WakeLock 資源設定為一個引用計數的方式進行標記管理,也就是說,呼叫一次 acquire()
獲取 WakeLock 就必須呼叫一次 release() 釋放 WakeLock 才能真正的將其釋放掉。而如果 release()
一個沒有 acquire()
的 WakeLock 物件,則會丟擲一個 RuntimeException 異常,表示 WakeLock under-locked。
這是一個預設行為,可以通過 setReferenceCounted()
方法對 mRefCounted 其進行改變,mRefCounted 預設為 true。從原始碼上可以看到,當 mRefCounted 為 true 的時候,只有在第一次 acquire()
的時候才會真實的呼叫 IPowerManager.acquireWakeLock()
獲取 WakeLock,並增加引用計數,在最後一次 release()
的時候,才會真實呼叫 IPowerManager.releaseWakeLock()
進行釋放 WakeLock,也就是說,是否真的獲取和釋放 WakeLock 取決於是否呼叫到 IPowerManager 的方法了。而 setReferenceCouned()
方法可以改變這種預設的設定,讓每次的 acquire()
和 release()
都去呼叫 IPowerManager 的對應方法。
下面看看相關原始碼。
可以看到,在 acquireLocked() 方法中,mCount++ == 0
表示 mCount 會在呼叫之後自增 ,這樣的語法表示只有在第一次 mCount 等於 0 的時候,才會命中 if 條件,執行 IPowerManager.acquireLocked() 方法。
而在 release() 方法中,同樣在 --mCount ==0
在判斷 mCount 之前做了一個自減的操作,表示只有在最後一次 release() 才會真實的呼叫 IPowerManager.releaseWakeLock() 方法。
四、PowerManager 不止有 WakeLock
雖然我們大部分時間,使用 PowerManager 就是為了獲取一個 WakeLock 物件進行操作,但是實際上 PowerManager 也提供了一些方法,用於我們判斷當前的電池管理狀態。
但是 PowerManager 中的方法,不是被標記為 @hide 就是需要額外的許可權,可供我們直接使用的方法非常少,@hide 的方法我們可以使用反射的方式呼叫,但是需要額外許可權的方法,就沒辦法去繞過它了。
這裡只是介紹幾個常用的比較有特點的 API:
- boolean isScreenOn() :判斷當前螢幕是否亮著,只要不是鎖屏狀態,都算亮著。而同時在 API 20 中被廢棄,推薦使用 isInteractive().
- boolean isInteractive() : 判斷當前螢幕是否亮著,在 API 20 中被新增。
- boolean isWakeLockLevelSupported(int level):因為有一些 Level 需要許可權才能獲取,所以這個方法用於檢查當前是否有 level 許可權。
- void reboot():重啟裝置,需要 REBOOT 許可權,這是一個系統許可權。
- boolean isPowerSaveMode() :判斷當前裝置是否處於一個省電模式下,在省電模式下,我們應該儘量減少耗電操作。
- boolean isDeviceIdleMode():判斷當前裝置是否處於空閒狀態。在空閒狀態下,我們可以做一些後臺操作。
- void shutdown() : 這是一個 @hide 的方法,可以使用它將裝置關機。
- void goToSleep() :讓裝置去休眠。需要 DEVICE_POWER 許可權,這是一個系統許可權。
- void wakeUp():強行將裝置從休眠中喚醒,需要 DEVICE_POWER 許可權,這是一個系統許可權。