Android電量優化全解析
電量優化一直是個老生常談的話題,關於這塊的文章已經有很多了,最近也在做這塊東西,所以結合自己的理解寫下這篇文章,如果有啥問題歡迎關注微信公眾號“程式設計師驛站”留言。好了,開始今天的正題,關於這塊的論述我按照按照下述結構進行。

在這裡插入圖片描述
電量消耗的全過程分析
手機裝置會執行各種任務和各種複雜計算,如秀自拍圖片上傳朋友圈、秀直播等等,為了完成這些裝置硬體會快速消耗手機電池電量。很明顯,任務處理的越複雜,電量就會消耗的越多和越快,一眨眼的功夫電量就消耗完了,這個時候使用者的手機頓時變成個累贅的磚頭了,使用者就會懷疑誰(哪個app)這麼耗電,然後把它卸了!
寫出耗電量低的應用的關鍵是要透徹理解它的全部過程。

在這裡插入圖片描述
在電子程式設計世界,這種硬體消耗電量 來執行任務的過程,叫做超時電流消耗,任何電子程式設計專業的人都會告訴你,你的裝置的各項活動在相同時間內,消耗的電量是不同的。
比如,很多手機號稱待機好幾天,這個確實是真的,不過就是使用飛航模式放在家裡什麼都不幹,確實可以甚至可以堅持10多天。但是我們一旦使用它,比如使用蜂窩式無線資料交換(3G4G)、螢幕保持喚醒狀態等,手機電量就會很快被消耗掉。
作為開發者,我們很想知道我的應用執行的哪些任務消耗的電量是最多的?這個問題確實會很棘手。因為電量消耗的計算與統計是一件麻煩而且矛盾的事情,記錄電量消耗本身也是一個費電量的事情(所以很多裝置都把這個監測電量的功能閹割掉了)。
唯一可行的方案是使用第三方監測電量的裝置,這樣才能夠獲取到真實的電量消耗(因為第三方硬體監測的時候是用的自己的供電而不是用的手機的電量)。
耗電情況,例如:開啟螢幕,所有要使用CPU/GPU工作的動作都會喚醒螢幕,都會消耗電量。這和應用程式喚醒裝置還不一樣。比如使用叫醒鬧鐘(wake clock)、AlarmManager、JobSchedulerAPI。

在這裡插入圖片描述
手機哪些地方最耗電?
喚醒螢幕
當用戶點亮螢幕的時候,意味著系統的各元件要開始進行工作,介面也需要開始執行渲染。
待機狀態的電量消耗:

在這裡插入圖片描述
使用和喚醒屏幕後:

在這裡插入圖片描述
當裝置從休眠狀態中,被應用程式喚醒時,可以看到在第一次喚醒時,出現一條電量使用高峰線。
CPU喚醒使用
CUP 喚醒時的高峰線:

在這裡插入圖片描述
接下來就是後續的一些執行的消耗了:

在這裡插入圖片描述
當工作完成後,裝置會主動進行休眠,這非常重要,在不使用或者很少使用的情況下,長時間 保持螢幕喚醒會迅速消耗電池的電量。
蜂窩式無線
當裝置通過無線網傳送資料的時候,為了使用硬體,這裡會出現一個喚醒耗電高峰。接下來還 有一個高數值,這是傳送資料包消耗的電量,然後接受資料包也會消耗大量電量 也看到一個峰值。

在這裡插入圖片描述
通常情況下,使用3G行動網路傳輸資料,電量的消耗有三種狀態:
- Full power: 能量最高的狀態,行動網路連線被啟用,允許裝置以最大的傳輸速率進
行操作。 - Low power: 一種中間狀態,對電量的消耗差不多是 Full power 狀態下的 50%。
- Standby: 最低的狀態,沒有資料連線需要傳輸,電量消耗最少。
Battery-Historian 電量分析工具的使用
要進行電量優化,我們首先得知道電都消耗到哪裡去了,我們可以通過 google 開源的 Battery-Historian 來進行分析。
工具開源地址: https://github.com/google/battery-historian
Battery History 工具安裝
根據 gitbub 上面介紹,Battery History
工具的安裝有兩種方式:
方式1:通過安裝 Docker 環境來安裝。(這種方式很簡單,Docker 真心好用)
- 按照 Docker 網站上的說明安裝 Docker Community Edition。
- 使用以下命令執行 Battery Historian 映象:
docker --run -p port_number:9999 gcr.io/android-battery-historian:2.1 --port 9999
方式2通過編譯 gitbub 上面的原始碼來安裝。
- GO 環境安裝:具體可以參考 Mac os 安裝 golang 開發環境 ( https://www.jianshu.com/p/79bdd20c46cf )
- 安裝 git.
- 安裝 Python。僅支援 python2.7 ( https://www.python.org/ )
- 安裝Java環境
下載 Battery Historian 原始碼並且執行
輸入如下命令列 下載到GOPATH 配置目錄下。
go get -d -u github.com/google/battery-historian/...

在這裡插入圖片描述
進入到$GOPATH/src/github.com/google/battery-historian目錄下方
cd $GOPATH/src/github.com/google/battery-historian

在這裡插入圖片描述
執行 Battery Historian
1.執行命令:
go run setup.go
Compile Javascript files using the Closure compiler

在這裡插入圖片描述
2.接著在執行命令:
go run cmd/battery-historian/battery-historian.go [--port <default:9999>]
GOBIN)
3.登入網址 http://localhost:9999 檢視battery-historian是否執行。
到此Battery-historian的環境就整好了。
電量資料收集
Android 5.0 及以上的裝置, 允許我們通過 adb 命令 dump 出電量使用統計資訊。
1.因為電量統計資料是持續的, 會非常大, 統計待測試的 App 之前需要連上裝置,因此需要reset(重置)電池資料收集。命令列執行:
$ adb shell dumpsys batterystats --resetBattery stats reset
2.斷開usb連線的測試裝置, 操作要測試的App。
3.重新連線裝置, 使用 adb 命令匯出相關統計資料:
- Android 7.0 及以上執行如下命令:
adb bugreport > [path/]bugreport.zip
- Android 5.0/ 6.0執行如下命令:
adb bugreport > [path/]bugreport.txt
匯出的統計資料儲存到 bugreport.zip(bugreport.txt), 藉助 battery-historian 工具來圖形化 展示電池的消耗情況.
上傳 bugreport.zip(bugreport.txt)檔案至 http://localhost:9999 :

在這裡插入圖片描述
battery-historian電量分析結果:

在這裡插入圖片描述
分析指標
下圖是使用 adb 命令將採集的電量資料上傳至 Battery Historian 而得到電量的分析情況。(我們可以通過包名過濾具體應用的耗電情況)

在這裡插入圖片描述
各指標的含義
- 橫座標: 橫座標就是一個時間範圍,咱們的例子中統計的資料是以重置為起點,獲取 bugreport 內容時 刻為終點。我們一共採集了多長時間的資料;
- 縱座標: 關鍵資料點說明如下。
資料項 | 說明 |
---|---|
battery_level | 電量,可以看出電量的變化 |
plugged | 充電狀態,這一欄顯示是否進行了充電,以及充電的時間範圍 |
screen | 螢幕是否點亮,這一點可以考慮到睡眠狀態和點亮狀態下電量的使用資訊 |
top | 該欄顯示當前時刻哪個 app 處於最上層,就是當前手機執行的 app,用來判斷某個 app 對手機電量的影響,這樣也能判斷出該 app 的耗電量資訊。該欄記錄了應用在某 一個時刻啟動,以及執行的時間,這對我們比對不同應用對效能的影響有很大的幫助 |
wake_lock | wake_lock 該屬性是記錄 wake_lock 模組的工作時間。是否有停止的時候等 |
running | 介面的狀態,主要判斷是否處於 idle 的狀態。用來判斷無操作狀態下電量的消耗 |
Job | 後臺的工作,比如服務 service 的執行 |
data_conn | 資料連線方式的改變,上面的 edge 是說明採用的 gprs 的方式連線網路的。此資料可 以看出手機是使用 2g,3g,4g 還是 wifi 進行資料交換的。這一欄可以看出不同的連 接方式對電量使用的影響 |
status | 電池狀態資訊,有充電,放電,未充電,已充滿,未知等不同狀態 |
phone_signal_strength | 手機訊號狀態的改變。 這一欄記錄手機訊號的強弱變化圖,依次來判斷手機訊號對電 量的影響 |
health | 電池健康狀態的資訊,這個資訊一定程度上反映了這塊電池使用了多長時間 |
plug | 充電方式,usb 或者插座,以及顯示連線的時間 |
Sync | 是否跟後臺同步 |
phone_in_call | 是否進行通話 |
gps | gps 是否開啟 |
如何進行電量優化?
瞭解手機關鍵耗電的地方及分析耗電的工具後。接下來就是我們的核心,如何來進行電量的優 化?首先我們先簡單總結匯總一下耗電的相關因素
- 螢幕亮暗相關
- 裝置 awake,sleep 的切換,尤其是喚醒.
- CPU 執行相關
- 網路
- 感測器
我們都知道螢幕的渲染及 CPU 的執行是耗電的主要因素之一。所以當我們在做記憶體優化、渲染優化、計算優化的時候,就已然在做電量優化。所以在平時的開發中,我們要注意點滴效能 的優化積累,實際上當我們來做電量分析的時候,也是在找自己挖的坑。所以儘量有意識在項 目的開發過程中儘量少挖坑,這一點是我們在分析其他優化項首先要提到的一個點。
監聽手機充電狀態
我們可以通過下面的程式碼來獲取手機的當前充電狀態:

在這裡插入圖片描述
得到充電狀態資訊之後,我們可以有針對性的對部分程式碼做優化。比如我們可以判斷只有當前 手機為 AC 充電狀態時 才去執行一些非常耗電的操作。可以通過下面的方法判斷手機當前的充 電狀態。

在這裡插入圖片描述
這裡我們就需要思考,根據具體的業務,考慮將一些不需要及時地和使用者互動的操作放到充電 的時候去做。比如:360 手機助手,當充上電的時候,才會自動清理手機垃圾,自動備份上傳圖片、聯絡人 等到雲端,從而避免當用戶手機低電量時,任然繼續進行耗電操作。
螢幕喚醒
當 Android 裝置空閒時,螢幕會變暗,然後關閉螢幕,最後會停止 CPU 的執行,這樣可以防 止電池電量掉的快。但有些時候我們需要改變 Android 系統預設的這種狀態:比如玩遊戲時我 們需要保持螢幕常亮,比如一些下載操作不需要螢幕常亮但需要 CPU 一直執行直到任務完成。
保持螢幕常亮比較好的方式是在 Activity 中使用 FLAG_KEEP_SCREEN_ON 的 Flag。

在這裡插入圖片描述
這個方法的好處是不像喚醒鎖(wake locks),需要一些特定的許可權(permission)。並且能 正確管理不同 app 之間的切換,不用擔心無用資源的釋放問題。
另一個方式是在佈局檔案中使用 android:keepScreenOn 屬性:

在這裡插入圖片描述
android:keepScreenOn = “true”的作用和 FLAG_KEEP_SCREEN_ON 一樣,使用程式碼的好 處是你允許你在需要的地方關閉螢幕。
注意:一般不需要人為的去掉 FLAG_KEEP_SCREEN_ON 的 flag,windowManager 會管理好程式進入 後臺回到前臺的的操作。如果確實需要手動清掉常亮的 flag,使用

在這裡插入圖片描述
所以這裡我們需要根據自己的 APP 實際情況,根據業務來控制好是否保持螢幕常量。比如 APP 需要支援視訊播放。那麼在播放的介面需要控制好不熄屏,當退出播放時,當然就沒有了 這個設定。
WakeLock
wake_lock 鎖主要是相對系統的休眠而言的,意思就是程式給 CPU 加了這個鎖那系統就不會 休眠了,這樣做的目的是為了全力配合我們程式的執行。有的情況如果不這麼做就會出現一些 問題。
需要使用 PowerManager 這個系統服務的喚醒鎖(wake locks)特徵來保持 CPU 處於喚醒狀 態。喚醒鎖允許程式控制宿主裝置的電量狀態,建立和持有喚醒鎖對電池的續航有較大的影 響,所以,除非是真的需要喚醒鎖完成儘可能短的時間在後臺完成的任務時才使用它。比如在 Acitivity 中就沒必要用了。如果需要關閉螢幕,使用上述的 FLAG_KEEP_SCREEN_ON。
只有一種合理的使用場景,使用後臺服務在螢幕關閉情況下 hold 住 CPU 完成一些工作,需要 使用喚醒鎖,如果不使用喚醒鎖來執行後臺服務,不能保證因 CPU 休眠未來的某個時刻任務 會停止,這不是我們想要的。
喚醒鎖可劃分並識別為四種使用者喚醒鎖:
標記值 | CPU | 螢幕 | 鍵盤 |
---|---|---|---|
PARTIAL_WAKE_LOCK | 開啟 | 關閉 | 關閉 |
SCREEN_DIM_WAKE_LOCK | 開啟 | 變暗 | 關閉 |
SCREEN_BRIGHT_WAKE_LOCK | 開啟 | 變亮 | 關閉 |
FULL_WAKE_LOCK | 開啟 | 變亮 | 變亮 |
注意:自 API 等級 17 開始,FULL_WAKE_LOCK 將被棄用。 應用應使用 FLAG_KEEP_SCREEN_ON。
1.新增喚醒鎖許可權:

在這裡插入圖片描述
2.直接使用喚醒鎖:

在這裡插入圖片描述
注意:在使用該類的時候,必須保證 acquire 和 release 是成對出現的。不然當我們業務已經不需要時, 當 CPU 處於喚醒狀態,這個時候就會損耗多餘的電量。
JobScheduler
自 Android 5.0 釋出以來,JobScheduler 已成為執行後臺工作的很好的方式,其工作方式有 利於使用者在適當的時機執行正確的事情。應用可以在安排作業的同時允許系統基於記憶體、電源 和連線情況進行優化。JobSchedule 的宗旨就是把一些不是特別緊急的任務放到更合適的時機 批量處理。這樣做有兩個好處:
- 避免頻繁的喚醒硬體模組,造成不必要的電量消耗。
- 避免在不合適的時間(例如低電量情況下、弱網路或者行動網路情況下的)執行過多的
任務消耗電量。
GPS
選擇合適的 Location Provider
Android 系統支援多個 Location Provider:
- GPS_PROVIDER: GPS 定位,利用 GPS 晶片通過衛星獲得自己的位置資訊。定位精準度高,一般在 10 米左右, 耗電量大;但是在室內,GPS 定位基本沒用。
- NETWORK_PROVIDER: 網路定位,利用手機基站和 WIFI 節點的地址來大致定位位置,這種定位方式取決於伺服器,
即取決於將基站或 WIF 節點資訊翻譯成位置資訊的伺服器的能力。 - PASSIVE_PROVIDER: 被動定位,就是用現成的,當其他應用使用定位更新了定位資訊,系統會儲存下來,該應用接 收到訊息後直接讀取就可以了。
如果 App 只是需要一個粗略的定位那麼就不需要使用 GPS 進行定位,既耗費電量,定位的耗 時也久。
及時登出定位監聽
在獲取到定位之後或者程式處於後臺時,登出定位監聽,此時監聽 GPS 感測器相當於執行 no- op(無操作指令),使用者不會有感知但是卻耗電。
多模組使用定位儘量復
多個模組使用定位,儘量複用上一次的結果,而不是都重新走定位的過程,節省電量損耗;例 如:在應用啟動的時候獲取一次定位,儲存結果,之後再用到定位的地方都直接去取。
感測器
使用感測器,選擇合適的取樣率,越高的取樣率型別則越費電。
- SENSOR_DELAY_NOMAL (200000 微秒)
- SENSOR_DELAY_UI (60000 微秒)
- SENSOR_DELAY_GAME (20000 微秒)
- SENSOR_DELAY_FASTEST (0 微秒)
在後臺時注意及時登出感測器監聽
Doze and App Standby
最後提這一點,理論上不是電量優化,而是做電量優化要注意的一個坑。Doze and App Standby 是 Android 6.0 以後,提供了兩種省電延長電池壽命的功能。
具體可參考 google 官方介紹文件。
如果大家有什麼好的意見或建議,歡迎關注我的公眾號“程式設計師驛站”進行留言,謝謝!

在這裡插入圖片描述
掃一掃 關注我的公眾號
如果你有好的文章需要和廣大網友分享,歡迎投稿,謝謝!
參考資料: https://github.com/google/battery-historian#wakelock-analysis