一、電量消耗理論與分析
寫出耗電量低的應用的關鍵是要透徹理解它的理論以及全部過程。下面將對電量消耗的相關理論知識進行介紹。
1、電量消耗的概念
首先要知道,電量的消耗,主要是指硬件的電量消耗(廢話),在電子世界,這種硬件消耗電量來執行任務的過程,叫做超時電流消耗。
同情況下,相同時間內,消耗的電量是不同的。比如使用飛行模式待機,確實可以堅持10多天。但是我們一旦使用手機,比如使用蜂窩式無線數據交換(3G4G)、屏幕保持喚醒狀態等,電量就會消耗得很快:
作為開發者,我們很想知道我的應用執行的哪些任務消耗的電量是最多的?這個問題確實會很棘手。
電量優化是方方面面的,比如說減少內存的開銷,減少界面的過度繪制,本身就是一種電量優化。
2、電量消耗計算
電量消耗的計算與統計是一件麻煩而且矛盾的事情,記錄電量消耗本身也是一個費電量的事情(所以很多設備都把這個監測電量的功能閹割掉了。)。
唯一可行的方案是使用第三方監測電量的設備,這樣才能夠獲取到真實的電量消耗(因為第三方硬件監測的時候是用的自己的供電而不是用的手機的電量)。
耗電情況,例如:打開屏幕,所有要使用CPU/GPU工作的動作都會喚醒屏幕,都會消耗電量。這和應用程序喚醒設備還不一樣。
比如使用叫醒鬧鐘(wake clock)、AlarmManager、JobSchedulerAPI。因此很難知道自己的應用程序的真實耗電情況。
3、設備待機與喚醒電量消耗分析
為什么要單獨拿這個出來講呢,就是因為,喚醒這個瞬間是非常耗電的,下面允許我慢慢介紹。
先來看看待機狀態的電量消耗:
待機狀態下,電量的消耗是非常少的,這是毋庸置疑的。
使用和喚醒屏幕后:
可以看到,屏幕喚醒的一瞬間是非常耗電的,這里有一條電量使用高峰線。
下面來看看CPU喚醒的曲線(CPU喚醒,屏幕不一定會喚醒):
同樣的,CPU喚醒的時候也會有一條電量使用高峰線。
CPU喚醒之后:
CPU喚醒之后,設備的耗電不會出現喚醒的時候的高峰線。
值得注意的是當工作完成后,設備會主動進行休眠,這非常重要,在不使用或者很少使用的情況下,長時間保持屏幕喚醒會迅速消耗電池的電量。
結論
設備喚醒的瞬間是有消耗高峰的,因此,當你的工作需要持續的時候,可以考慮保持喚醒狀態。
4、無線蜂窩耗電分析
蜂窩式無線也是耗電量非常可怕的,甚至比WIFI更加耗電,因此這里單獨拿出來進行分析。
Tips:不使用流量的時候,最好把數據關閉,這樣又省電又省流量。
下面開始分析無線蜂窩耗電的過程:
如上圖所示:
1、當設備通過無線網發送數據的時候,為了使用硬件,這里會出現一個喚醒高峰。
2、接下來還有一個高數值,這是發送數據包消耗的電量。
3、然后接受數據包也會消耗大量電量,也看到一個峰值。
4、保持喚醒狀態,耗電比較均衡,很少出現高峰點。
所以我們開啟無線模式這個過程非常耗電,那么硬件這塊為了防止頻繁開啟關閉耗電,采取了一個無奈的辦法,會在一個小段時間內保持開啟模式,防止短時間內還有數據包需要接收。這些數據非常有用,可是不是所有開發者都有這個第三方設備跟蹤。但是使用Android L版本就可以利用到新的一系列的工具來優化應用程序的耗電。(這里顯然不要考慮兼容性問題,我只是想測電量消耗問題,同一款APP在不同版本的Android上耗電情況應該不會有太大影響,雖然不同Android版本對電量的優化不同,但是我們的分析對象是我們自己的APP本身)
二、電量分析工具Battery Historian的環境搭建與使用
Battery Historian 是一個這樣的的工具:可以在 Android 5.0 Lollipop(API 級別21)及更高版本的 Android 設備上檢測與電池相關的信息和事件,而在此期間,該設備沒有插上電源。它允許應用程序開發人員在時間軸上可視化系統和應用級別的事件,并使用平移和縮放功能,在設備最后一次完全充電之后,可以輕松地查看各種聚合統計信息,可以選擇一個應用程序,檢查所選擇的應用程序對電池指標的影響。此外,它還允許對兩個錯誤報告進行 A/B 比較,突出顯示了關鍵電池相關指標的差異。
環境配置
(1)安裝Go編程語言
點擊下載go語言。
配置GOROOT、GOPATH、PATH環境變量。
檢查是否安裝成功:cmd 執行 “go version”
(2)安裝 Python
下載:https://www.python.org/ 【注意僅支持 python 2.7,python3.0改變很大】
安裝
配置環境變量
檢查是否安裝成功:cmd 執行 “python –V”【注意是大寫V】
(3)安裝Git
安裝
檢查是否安裝成功:cmd 執行 “git version”
(4)下載 Battery Historian 源碼并且運行
- cmd 執行“go get -d -u github.com/google/battery-historian/...”【注意最后有三個點】
下載成功后會在GOPATH(我自己建的工作空間文件目錄)下生成src文件夾
-
進入到$GOPATH/src/github.com/google/battery-historian目錄下方
運行Battery Historian
cmd 執行“go run setup.go”【第一次執行要下載,時間會久一些,以后就快些】
ps:http://blog.csdn.net/feitian_666/article/details/52756522 提供了一個在沒有下載成功情況下補充“go run setup.go”的方法:手動下載【closure-library】和【closure-compiler】和【flot-axislabels】,解壓放到 GOROOT 目錄下 third_party 文件夾下方的的 closure-compiler、closure-library 和flot-axislabels 文件夾,如果沒有均手動創建。
cmd 執行 go run cmd/battery-historian/battery-historian.go
- 檢查/battery-historian是否運行
登錄網址 http://localhost:9999查看是否加載運行battery historian。
G@~)M7WYR)R@T_(V9P%@5JV.png
使用
(1).初始化
battery-historian工具需要使用bugreport中的Battery History,因此需要如下的操作。
重啟adb服務:
adb kill-server
adb start-server
這一步很重要,因為當我們開發時做電量記錄時會打開很多可能造成沖突的東西。為了保險起見我們重啟adb。
通過以下命令來打開電池數據的獲取以及重置:
adb shell dumpsys batterystats --enable full-wake-history
adb shell dumpsys batterystats --reset
上面的操作很重要,因為可以過濾掉不需要的數據。然后斷開數據線(防止數據線造成充放電數據干擾),運行自己的APP進行測試。
(2)導出手機的 Bugreport 文件
重新連接USB調試,通過下面的命令獲取數據:
adb bugreport bugreport.zip(6.0以及以下的,使用adb bugreport > bugreport.txt導出)
你在哪個目錄下執行adb bugreport bugreport.zip 就會在哪個目錄下生成 bugreport.zip文件
(3)上傳bugreport.zip文件至 http://localhost:9999
最后
注意:7.0以下的,需要使用舊版本的adb工具,不然沒法采集,參考文章http://blog.csdn.net/mwq30123/article/details/53888449
注意:官方SDK文檔導出文件方式為:adb shell dumpsys batterystats > batterystats.txt。使用python historian.py batterystats.txt > batterystats.html查看數據。這是battery-historian老版本的使用方式. 目前Battery Historian已更新2.0版本, 推薦使用bugreport方式導出數據分析, 可以看到更多信息。
注意:模擬器可能獲取不到有用的電量數據,網頁沒有顯示電量信息,如下圖所示:
android電量統計的原理可以參看這篇文章:http://duanqz.github.io/2015-07-21-batterystats-part1
大致原理摘錄如下:
一、電量記錄
1. Android在進行電量統計時,并不是采用直接記錄電流消耗量的方式,而是跟蹤硬件模塊在不同狀態下的使用時間,收集一些可用信息,用來近似的計算出電池消耗量。
舉一個例子,假定某個APK的使用了GPS,使用時間用 t 表示。GPS模塊單位時間的耗電量用 w 表示,那么,這個APK使用GPS的耗電量就可以按照如下方式計算:
耗電量 = 單位時間耗電量(w) × 使用時間(t)
frameworks.jar里的frameworks/base/core/res/res/xml/power_profile.xml這個文件,記錄著各個模塊單位時間的耗電量, 由廠商定義。
以下是Nexus 5(hammerhead)耗電參數配置的代碼片段:
<device name="Android">
<!-- All values are in mAh except as noted -->
<item name="none">0</item>
...
<item name="wifi.on">3.5</item>
<item name="wifi.active">73.24</item>
<item name="wifi.scan">75.48</item>
...
<item name="battery.capacity">2300</item>
</device>
2. Android框架層通過一個名為batterystats的系統服務,實現了電量統計的功能。
收集信息被組織起來,在內存中的數據結構是由BatteryStats類描述的。 為了能夠從不同維度統計耗電量,這個數據結構設計得比較復雜,我們不在這里展開討論,僅通過一個收集應用程序前臺運行時間的例子,來說明信息收集過程。
記錄應用程序中所有Activity從顯示狀態(Resumed)到消失狀態(Paused)的時間,就能夠統計應用程序的前臺運行時間。Activity狀態的切換是由AMS掌控的,因此AMS需要將Activity的狀態信息通知給batterystats服務。
當Activity要切換到顯示狀態(Resumed)時,
會調用ActivityStackSupervisor.resumeTopActivitiesLocked()方法,
接下來會調用ActivityStack.resumeTopActivityInnerLocked()方法來完成Activity的狀態切換,在完成狀態切換后, 會調用
ActivityStackSupervisor.reportResumedActivityLocked()方法,從這里開始,就開始通報了:“本Activity已經進入了顯示狀態”。
在ActivityStackSupervisor.reportResumedActivityLocked()中得到BatteryStatsImpl對象,
并啟動一個計時器(StopwatchTimer),
記錄下了啟動時間.在Activity pause時, 再得到結束時間, 這樣就得到了應用程序的acitiviy在前臺的運行時間了。
除了應用程序前臺運行時間,還有很多信息是batterystats服務關注的,包括WakeLock、Sendor、Wifi、Audio、Video等,這些信息的采集方式與上述過程雷同,都會經過以下步驟:
由相應的模塊發起狀態變更的通知
BatteryStats使用定時器記錄起止時間
二、電量信息的儲存
Android支持歷史電量信息的顯示的,如果重新啟動Android,那內存中的數據就丟失了, 所以需要把這些信息存儲到磁盤上,磁盤上的 /data/system/batterystats.bin 文件中就是電量信息的序列化數據。
batterystats服務啟動時,會從 batterystats.bin 這個文件中讀取數據,來初始化BatteryStats這個數據結構。
三、電量計算
BatteryStatsHelper.refreshStats()承載了電量計算的全部過程,在需要顯示電量統計信息的地方,就可以通過BatteryStatsHelper這個類,來獲取統計完成的電量信息。 Setting.apk就引用了這個類。電量計算大體可以分為兩塊:
1. AppUsage:應用程序耗電量計算,是指每一個應用程序使用硬件模塊所產生的耗電量
在BatteryStatsHelper.processAppUsage()這個方法中,實現了應用程序的電量計算(實際上統計的粒度是uid,不同的apk可以運行在同一個uid)。
2. MiscUsage:其他雜項耗電量計算
所謂雜項,其實就是用戶比較關心的一大類,包括:待機的耗電量、亮屏的耗電量、通話的耗電量、Wifi的耗電量等,這個統計是系統層面的, 作為app的開發人員可以忽略掉這部分內容。
我們來總結一下應用程序的電量計算過程。Android通過一個名為BatteryStats.Uid的數據結構來維護一個應用程序的電量統計信息。 這個數據結構中,又包含很多子結構:
Proc:表示屬于Uid的進程,一個Uid中可能會有多個進程,每個進程都有CPU占用時間
WakeLock:表示Uid持有的WakeLock鎖的電量統計,一個Uid也可能會持有多個鎖
Mobile Radio:表示Uid使用數據流量的電量統計,譬如3G流量、4G流量
Wifi:表示Uid使用wifi的電量統計
Sendor:表示Uid使用傳感器的電量統計
Android提供的dumpsys命令用于查看系統服務的信息, 將batterystats作為參數,就能輸出完整的電量統計信息。
adb shell dumpsys batterystats