PowerManager 不是隻有 WakeLock | 掘金技術徵文

承香墨影發表於2017-05-21

版權宣告:

本賬號釋出文章均來自公眾號,承香墨影(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() 方法。

PowerManager 不是隻有 WakeLock | 掘金技術徵文

WakeLock 這個類沒有提供公有的構造方法,所以只能通過 newWakeLock() 來獲取。可以看到 newWakeLock() 需要的引數,其中 levelAndFlags 就是 WakeLock 的級別,後面我們會講解到。

而在其中,還會呼叫 validateWakeLockParameters() 方法,它主要是對引數進行一些校驗。

下面看看 WakeLock 使用的一個 Demo:

PowerManager 不是隻有 WakeLock | 掘金技術徵文

可以看到,使用 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 的對應方法。

下面看看相關原始碼。

PowerManager 不是隻有 WakeLock | 掘金技術徵文

可以看到,在 acquireLocked() 方法中,mCount++ == 0

表示 mCount 會在呼叫之後自增 ,這樣的語法表示只有在第一次 mCount 等於 0 的時候,才會命中 if 條件,執行 IPowerManager.acquireLocked() 方法。

PowerManager 不是隻有 WakeLock | 掘金技術徵文

而在 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 許可權,這是一個系統許可權。

PowerManager 不是隻有 WakeLock | 掘金技術徵文
公眾號二維碼.jpg

相關文章