文章來源于我的CSDN 博客:http://blog.csdn.net/u011311586/article/details/79044176
概述
Android 中關于耗電的統計一般是關于功耗分析的重要信息,Bettery-historian工具也是依托于解析BatteryStats 的dump 信息來提供界面直觀分析,并且電池電量耗費的源頭實在太多,基本Android 設備上任何一個活動都會引起電池電量的消耗,Android 在統計電量上也在不斷完善,不斷的在更新,具體化耗電詳情。耗電名單在主要記錄在BatterySipper里面,雖然在源碼中他并沒有集成在service 端,實在frameworks/base/core 下,但是谷歌開放sdk 中并沒有公開電量統計的API 或者文檔,但是并不代表沒有,因為安全中心->省電優化→耗電排行 中就是通過app 能顯示出耗電詳情排行,所以我們將從這個入口開始分析Android 是如何記錄設備電池的耗電詳情信息的
BatteryStats服務架構設計
由于系統中形形色色,所有的活動都會耗電,所以BatteryStats服務也是相當的復雜,所以首先我們需要摸清楚該服務的架構設計,以此來切入分析,我們首先來看一下BatteryStats 電池電量統計服務的架構圖:
從圖中我們可以看出整個電池管理服務的大概架構是如何的。那么這里面的每個類所擔當的角色是怎樣的呢?
BatteryStats: 這是一個抽象類,在我看來也算是整個電池信息統計服務的架構核心類,這里面定義了很多內部類:
Timer (記錄時間信息狀態);
ControllerActivityCounter(統計無線電數據傳輸,接受,以及idle狀態);
Counter(記錄計數信息的狀態。如Alarm,Wakelock 等統計計數);
LongCounter(針對長期持續的活動統計,如屏幕亮滅,插拔充電等);
UID(針對App Uid 統計信息):
Uid由于是統計app 的耗電量,所以其還定義內部類:Wakelock (統計應用申請Wakelock 的情況),Sensor(統計應用使用sensor的情況),Proc(統計應用進程的信息),Pkg(統計應用包的信息,內部類Serv(統計該包名下服務的信息));
BatteryStatsImpl :為整個電池信息統計服務的計算核心類,雖然該類是在frameworks/base 端(并非放在services 端),但是從分析該服務源碼能看出來,BatteryStatsServices 雖然是system_server 中一個服務,但是實際上該服務只是一個空殼(后面即將講到),所有的電池耗電信息相關計算都是在BatteryStatsImpl 中實現的,該類繼承自BatteryStats,并且實現了BatteryStats 中定義的所有的抽象類以及計算方法。
BatteryStatsHelper : 是BatteryStatsImpl 計算的一個輔助類,主要是提供給應用(比如設置,安全中心,360等)來展示耗電信息,這里面的定義了軟件類和硬件耗電信息的計算類***PowerCalculator,并且提供獲取耗電信息列表方法getUsageList()
BatterySipper: 英文解釋為:電池吸管,這個類的對象才是每個耗電的實體項統計,在安全中心中耗電排行中,每一個耗電項都是一個BatterySipper對象。
以上對BatteryStats 服務中各個相關的類以及其作用做了一個大致的解釋,那么其服務是怎么統計的呢,我們繼續來一步一步剖析源碼
</br>
服務啟動
BatteryStats 服務是在AMS 的構造函數中啟動的
ActivityManagerService 構造函數中:
mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler);
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.scheduleWriteToDisk();
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
mBatteryStatsService.getActiveStatistics().setCallback(this);
在AMS 構造函數中創建BatteryStatsService 的對象,并且開始讀取統計文件里已經保存的統計信息。并且開始異步 的去記錄信息,設置Callback
BatteryStatsService初始化:
BatteryStatsService(Context context, File systemDir, Handler handler) {
// BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
mContext = context;
mUserManagerUserInfoProvider = new BatteryStatsImpl.UserInfoProvider() {
private UserManagerInternal umi;
@Override
public int[] getUserIds() {
if (umi == null) {
umi = LocalServices.getService(UserManagerInternal.class);
}
return (umi != null) ? umi.getUserIds() : null;
}
};
mStats = new BatteryStatsImpl(systemDir, handler, this, mUserManagerUserInfoProvider);
mWorker = new BatteryExternalStatsWorker(context, mStats);
mStats.setExternalStatsSyncLocked(mWorker);
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L); //設置RadioScanningTimeout 值(0 * 1000L)
mStats.setPowerProfileLocked(new PowerProfile(context)); //設置PowerProfile(電池基本參數信息)。
}
public void publish() {
ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
}
1.在構造函數中,使用BatteryExternalStatsWorker 內部統計集合來收集電池耗電信息了(8.1之前的是創建一個新的線程batterystats-sync用來記錄電池電量信息) ,從AMS中傳過來的mHandler(ActivityManager線程)給BatteryStatsImpl 用于記錄wakelock,PowerChange,charging 等信息。設置外部硬件統計對象mWorker
2.在AMS 中onStart()函數中調用BatteryStatsService.publish() ,將batterystats 服務注冊到system_server 進程中。可以看到在publish 中邏輯:3.將batterystats 服務添加到ServiceManager 中。
</br>
我們這里需要重點關注BatteryStatsImpl 的初始化,因為從以上分析來看雖然電量統計服務是system_server進程中的一個服務,但是其主要只是一個proxy 的作用,整體的計算工作還是交給BatteryStatsImpl 去做的,所以BatteryStatsImpl 才是整個耗電信息的計算核心類。
BatteryStatsImpl 構造函數
private BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
PlatformIdleStateCallback cb,
UserInfoProvider userInfoProvider) {
init(clocks);
if (systemDir != null) {
mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
new File(systemDir, "batterystats.bin.tmp"));
} else {
mFile = null;
}
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
mHandler = new MyHandler(handler.getLooper());
mStartCount++;
mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
........
initDischarge();
clearHistoryLocked();
updateDailyDeadlineLocked();
mPlatformIdleStateCallback = cb;
mUserInfoProvider = userInfoProvider;
}
構造函數大概干了幾件事:
1.傳入的mClocks 為AMS 啟動時候創建的SystemClock。
- 在/data/system/ 下創建 batterystats.bin 文件和其備份文件 batterystats.bin.tmp,創建電池信息校準文件 batterystats-checkin.bin ,電池每日使用信息 batterystats-daily.xml
3.創建mHandler,其looper 使用的是ActivityManager 的Looper。
- 創建各種耗電活動的timer 計時器,標識該活動使用的時長,每一個計時器,每個timer 對應一個唯一的type。
- 創建 網絡流量/Modem Radio 活動,非充電狀態次數,拔電狀態下滅屏,Doze活動 等的計數器LongSamplingCounter
- 創建wifi,藍牙,基帶數據活動最大級別對應的耗電統計,藍牙和wifi 均只有一個級別,modem有的級別為5(5個傳輸速率對應5個級別的耗電功率)
- 初始化各種充電,日期,電池歷史信息參數
</br>
再來說道說道的PowerProfile 文件,向BatteryStatsImpl中設置的PowerProfile 對象其實是兩方面構成:1.解析power_profile.xml ,將該配置文件中的各項耗電功率讀取出來,設置到電量統計計算類BatteryStatsImpl ;2. 原生上添加增加藍牙,wifi不同狀態下的耗電電流和電壓值
服務啟動并不復雜,只是做一些初始化的工作,大致簡圖如下:
耗電統計
當我們進入到原生手機:設置→ 電池→ 應用使用情況 (MIUI的安全中心→省電優化→耗電排行) 代碼基本都是一樣,可以看到電池各個模塊的耗電排行,那么他是怎么計算出來的呢,我們由此為入口,由點及面的來展開
mHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
List<com.android.internal.os.BatterySipper> usageList = mHelper.getUsageList();
for (com.android.internal.os.BatterySipper osSipper : usageList) {
if (osSipper.drainType == com.android.internal.os.BatterySipper.DrainType.APP) {
...... // APP 耗電
mTotalPower += sipper.value;
mAppUsageList.add(sipper);
}
...... //PHONE, SCREEN, WIFI, BLUETOOTH, BLUETOOTH, IDLE, CELL
else {
BatterySipperHelper.addBatterySipper(otherSipper, osSipper);
addEntry(otherSipper); //添加到硬件耗電
}
}
以上代碼,是粘貼的設置中關于電池耗電統計的一段代碼,我們可以看到安全中心中獲取耗電整體的信息是通過BatteryStatsHelper.getUsageList()方法獲取到所有耗電的list ,通過判斷DrainType 是app 還是其他硬件,來區分統計軟件以及硬件(PHONE, SCREEN, WIFI, BLUETOOTH, BLUETOOTH, IDLE, CELL, OTHER)的耗電。 那么getUsageList 中BatterySipper List 是如何統計出來的呢。我們來一層一層的抽絲剝繭的根據源碼來找尋其原理
</br>
從BatteryStatsHelper 中定義的相關usage list 能看出來,系統中將耗電總共分成了五大類:App,Wifi,Bluetooth ,User,Mobile。getUsageList中獲取到的list 就是這五類耗電信息的綜合。當我們每次進入到耗電詳情排名界面時(或者dump時),都會刷新一次當前實際耗電信息。而在刷新電池耗電信息,來執行一次聚合所有的耗電信息到usage 中。我們來看看其核心函數:
public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
long rawUptimeUs) {
// Initialize mStats if necessary.
getStats();
...... //初始化一些PowerCalculato 以及各類時間參數
processAppUsage(asUsers);
.... // 記錄移動數據流量到mMobilemsppList 中
processMiscUsage();
Collections.sort(mUsageList);
.... // 對統計數據做一些去雜和優化
}
該函數實際有兩百多行,但是其核心處理只有兩個函數:
processAppUsage 計算軟件app功耗
processMiscUsage 計算硬件功耗
那么他是怎么將各個app 和各個硬件上的耗電值綜合起來的呢,我們一條一條單個來分析:
</br>
軟件功耗計算
軟件功耗計算函數processAppUsage() : 在 sumPower()計算總和
final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
final int NU = uidStats.size();
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
//計算app 消耗的Cpu電量到cpuPowerMah 中
mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
//計算app 使用的Wakelock電量到wakeLockPowerMah 中
mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
// 計算app 使用radio 網絡消耗的電量到mobileRadioPowerMah 中
mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
// 計算app 使用的Wifi電量到wifiPowerMah 中
mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
// 計算app 使用藍牙的電量到bluetoothPowerMah 中
mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
// 計算app 使用的Sensor電量到sensorPowerMah 中
mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
// 計算app 使用camera的電量到cameraPowerMah 中
mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
// 計算app 使用閃光燈Flashlight 的電量到flashlightPowerMah
mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
final double totalPower = app.sumPower();
軟件功耗計算公式:
totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah + flashlightPowerMah + bluetoothPowerMah;
</br>
硬件功耗計算
硬件功耗計算函數在:processMiscUsage()
private void processMiscUsage() {
addUserUsage(); // 多用戶中每個用戶的耗電量
addPhoneUsage(); // modem通話耗電量
addScreenUsage(); // 屏幕耗電量
addWiFiUsage(); // wifi耗電量
addBluetoothUsage(); // 藍牙耗電量
addMemoryUsage(); // DDR內存耗電量
addIdleUsage(); // CPU suspend/idle狀態下的耗電量(不包括蜂窩數據空閑功耗)
if (!mWifiOnly) {//(當只有wifi上網功能的設備時不計算蜂窩數據功耗,如平板,電視等)
addRadioUsage(); //移動數據網絡的耗電量
}
}
</br>
</br>
Users
多用戶下各個用戶的耗電量
private void addUserUsage() {
for (int i = 0; i < mUserSippers.size(); i++) {
final int userId = mUserSippers.keyAt(i);
BatterySipper bs = new BatterySipper(DrainType.USER, null, 0);
bs.userId = userId;
aggregateSippers(bs, mUserSippers.valueAt(i), "User");
mUsageList.add(bs);
}
}
mUserSippers 為各個app 在非當前用戶下的耗電(每一個userid 對應一個BatterySipper List),其中Android 電量統計中將其他用戶使用的耗電量都統歸為mUserSippers 的硬件耗電。
公式:user_power = user_1_powerMah + user_2_powerMah + … + user_n_powerMah; (n為所有的user的總數)
</br>
</br>
Phone
private void addPhoneUsage() {
long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtimeUs, mStatsType) / 1000;
double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
* phoneOnTimeMs / (60 * 60 * 1000);
if (phoneOnPower != 0) {
addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
}
}
計算Phone 通話的耗電量,從PowerProfile 中讀取POWER_RADIO_ACTIVE 的功率,與Phone 信號的時間計算出其功耗值
公式:phonePower = (phoneOnPower * phoneOnTimeMs ) / (60 * 60 * 1000);
</br>
</br>
Screen
/**
* Screen power is the additional power the screen takes while the device is running.
*/
private void addScreenUsage() {
double power = 0;
long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtimeUs, mStatsType) / 1000;
power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON); //屏幕打開時的功耗,不包括背光功耗。
final double screenFullPower =
mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); // 最高背光亮度下的功耗。(如果背光亮度為50%,則應該乘以0.5)
for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
double screenBinPower = screenFullPower * (i + 0.5f)
/ BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtimeUs, mStatsType)
/ 1000;
double p = screenBinPower * brightnessTime;
power += p;
}
power /= (60 * 60 * 1000); // To hours
if (power != 0) {
addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
}
}
屏幕的功耗是排除在設備運行時屏幕的功耗(比如繪圖,動畫等),這里計算的屏幕功耗,主要是 屏幕保持活躍狀態時的功耗值 和 屏幕被點亮后不同背光強度下的功耗值
屏幕保持活躍時的功耗值: screenOnPower = screenOnTimeMs * POWER_SCREEN_ON (screenon功率)
屏幕不同背光下的功耗值:屏幕背光分為5個級別( BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS)
背光功率為:screenFullPower * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; //也就是最高級別功率為 1.1 ,最低級別為0.1
所以屏幕背光功耗值: brightnessPower = screenBinPower1 * brightnessTime + screenBinPower2 * brightnessTime + screenBinPower3 * brightnessTime + screenBinPower4 * brightnessTime + screenBinPower5 * brightnessTime
公式:screenPower = (screenOnPower + brightnessPower ) / (60 * 60 * 1000);
</br>
</br>
wifi
mWifiPowerCalculator = hasWifiPowerReporting ?
new WifiPowerCalculator(mPowerProfile) :
new WifiPowerEstimator(mPowerProfile);
private void addWiFiUsage() {
BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
aggregateSippers(bs, mWifiSippers, "WIFI");
if (bs.totalPowerMah > 0) {
mUsageList.add(bs);
}
}
// WifiPowerCalculator 計算
@Override
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
final BatteryStats.ControllerActivityCounter counter = stats.getWifiControllerActivity();
final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType);
final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType);
final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType);
app.wifiRunningTimeMs = Math.max(0,
(idleTimeMs + rxTimeMs + txTimeMs) - mTotalAppRunningTime);
double powerDrainMah = counter.getPowerCounter().getCountLocked(statsType)
/ (double)(1000*60*60);
if (powerDrainMah == 0) {
// 有些控制器不報告功耗,所以我們可以在這里計算。
powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa)
+ (rxTimeMs * mRxCurrentMa)) / (1000*60*60);
}
app.wifiPowerMah = Math.max(0, powerDrainMah - mTotalAppPowerDrain);
}
// WifiPowerEstimator
@Override
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType)
/ 1000;
final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn)
/ (1000*60*60);
app.wifiRunningTimeMs = totalRunningTimeMs;
app.wifiPowerMah = Math.max(0, powerDrain);
}
WifiPowerCalculator 計算wifi功耗:
mIdleCurrentMa: wifi controller 處于idle 狀態下的功率 (PowerProfile.POWER_WIFI_CONTROLLER_IDLE)
mTxCurrentMa: wifi controller 處于Tx 上行狀態下的功率 (PowerProfile.POWER_WIFI_CONTROLLER_TX)
mRxCurrentMa: wifi controller 處于rx 下行狀態下的功率 (PowerProfile.POWER_WIFI_CONTROLLER_RX)
公式:powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) + (rxTimeMs * mRxCurrentMa)) / (10006060);
WifiPowerEstimator 計算wifi功耗:
mWifiPowerOn:wifi驅動打開時的功耗 (PowerProfile.POWER_WIFI_ON)
mWifiPowerScan: WiFi驅動程序掃描網絡時的功耗。(PowerProfile.POWER_WIFI_SCAN)
mWifiPowerBatchScan: wif批量掃描消耗的功率。 按“每小時掃描的頻道”分解為分組。(PowerProfile.POWER_WIFI_BATCHED_SCAN)
公式:wifiPowerMah = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn) / (1000* 60* 60);
</br>
</br>
Bluetooth
private void addBluetoothUsage() {
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
if (bs.totalPowerMah > 0) {
mUsageList.add(bs);
}
}
@Override
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
final BatteryStats.ControllerActivityCounter counter =
stats.getBluetoothControllerActivity();
final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType);
final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType);
final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType);
final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
double powerMah = counter.getPowerCounter().getCountLocked(statsType)
/ (double)(1000*60*60);
if (powerMah == 0) {
// 有些設備不報告功率,所以在這里計算一下。
powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
/ (1000*60*60);
}
// 減去使用的應用程序,但不能小于0。
powerMah = Math.max(0, powerMah - mAppTotalPowerMah);
if (DEBUG && powerMah != 0) {
Log.d(TAG, "Bluetooth active: time=" + (totalTimeMs)
+ " power=" + BatteryStatsHelper.makemAh(powerMah));
}
app.bluetoothPowerMah = powerMah;
app.bluetoothRunningTimeMs = Math.max(0, totalTimeMs - mAppTotalTimeMs);
}
藍牙功耗與wifi 功耗計算相似:
mIdleMa: Bluetooth controller 處于idle 狀態下的功率 (PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE)
mRxMa:Bluetooth controller 處于Rx 下行狀態下的功率 (PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX)
mTxMa:Bluetooth controller 處于Tx 上行狀態下的功率 (PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX)
公式:bluetoohPower = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa)) / (10006060);
</br>
</br>
Memory
private void addMemoryUsage() {
BatterySipper memory = new BatterySipper(DrainType.MEMORY, null, 0);
mMemoryPowerCalculator.calculateRemaining(memory, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
memory.sumPower();
if (memory.totalPowerMah > 0) {
mUsageList.add(memory);
}
}
@Override
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
double totalMah = 0;
long totalTimeMs = 0;
LongSparseArray<? extends BatteryStats.Timer> timers = stats.getKernelMemoryStats();
for (int i = 0; i < timers.size() && i < powerAverages.length; i++) {
double mAatRail = powerAverages[(int) timers.keyAt(i)];
long timeMs = timers.valueAt(i).getTotalTimeLocked(rawRealtimeUs, statsType); //不同速率下運行時間
double mAm = (mAatRail * timeMs) / (1000*60);
totalMah += mAm/60;
totalTimeMs += timeMs;
}
app.usagePowerMah = totalMah;
app.usageTimeMs = totalTimeMs;
}
MemoryPowerCalculator 是8.0 上新加的,主要是統計DDR內存上的耗電量
powerAverages : 每個讀寫速率級別上的功率 (PowerProfile.POWER_MEMORY)
公式: memoryPower = (mAatRail_1 * timeMs_1 + mAatRail_2 * timeMs_2 + ... + mAatRail_n * timeMs_n) / (1000 * 60 * 60) (mAatRail_n :是該讀寫速率級別下的功率,timeMs_n:是在mAatRail_n 級別下的時間)
</br>
</br>
Idle
private void addIdleUsage() {
final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) *
mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE); //cpu 處于idle 的時間
final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) *
mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE); //cpu 處于awker的時間
final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);
if (totalPowerMah != 0) {
addEntry(BatterySipper.DrainType.IDLE, mTypeBatteryRealtimeUs / 1000, totalPowerMah);
}
}
這里是計算設備cpu處于idle 狀態和 suspend 狀態 的基準功耗值,其中包括:
設備在最低電量狀態下處于POWER_CPU_IDLE的功耗
設備持有wakelock 時候POWER_CPU_IDLE + POWER_CPU_AWAKE的功耗
公式:idlePower = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);
</br>
</br>
Radio
private void addRadioUsage() {
BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
radio.sumPower();
if (radio.totalPowerMah > 0) {
mUsageList.add(radio);
}
}
@Override
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
double power = 0;
long signalTimeMs = 0;
long noCoverageTimeMs = 0;
for (int i = 0; i < mPowerBins.length; i++) {
long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
/ 1000;
final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
power += p; // 計算信號強度的功耗
signalTimeMs += strengthTimeMs;
if (i == 0) {
noCoverageTimeMs = strengthTimeMs;
}
}
final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
/ 1000;
final double p = (scanningTimeMs * mPowerScan) / (60*60*1000); // 計算搜網的功耗
power += p;
long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
if (remainingActiveTimeMs > 0) {
power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60); //計算駐網的功耗
}
if (power != 0) {
if (signalTimeMs != 0) {
app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
}
app.mobileActive = remainingActiveTimeMs;
app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
app.mobileRadioPowerMah = power;
}
}
這里統計的是無限數據網絡的耗電。
此類功耗計算包括三個方面:信號強度(signalStrenth),搜索運營商網(scanning),駐網(remainingActive)。
信號強度(signalStrenth): 在android 設備中分了6(SignalStrength.NUM_SIGNAL_STRENGTH_BINS:"none", "poor", "moderate", "good", "great", "excellent")個等級,每個等級對應相應的功率(PowerProfile.POWER_RADIO_ON)。
子公式:strengthOnPower = none_strength_Ms * none_strength_Power + poor_strength_Ms * poor_strength_Power + moderate_strength_Ms * moderate_strength_Power + good_strength_Ms * good_strength_Power + great_strength_Ms * great_strength_Power;
搜索運營商網(scanning):搜網過程其實是一個耗電的過程,對應的功率(PowerProfile.POWER_RADIO_SCANNING)
子公式:scanningPower = scanningTimeMs * mPowerScan;
駐網(remainingActive):駐網的過程中是需要保持活動的,讓基站知道該設備是活躍的狀態。所以該活動的功率(PowerProfile.POWER_RADIO_ACTIVE)
子公式:remainingActivePower = (radioActiveTimeMs - mTotalAppMobileActiveMs)* mPowerRadioOn
總公式:mobileRadioPower = strengthOnPower + scanningPower + remainingActivePower
</br>
</br>
所以以上就是所有硬件耗電的計算公式方法。所以硬件的總公式則是
micPowerMah = user_power + phonePower + screenPower + wifiPowerMah + bluetoohPower + memoryPower + idlePower + mobileRadioPower
</br>
</br>
以上則是系統中獲取電量統計中,針對于硬件/軟件的功耗值統計排名的計算方法,在應用需要獲取電池電量統計時,會去主動調用BatteryStatsHelper 的refreshStats 的方法,將其電量刷新為最新的統計數據,繼而獲取mUsageList 統計列表來顯示系統電量消耗源
計算電量提供給前臺app 去顯示的流程圖大致如下:
</br>
總結
電量信息統計服務的統計方式可以簡單總結為:耗電量 = 模塊耗電功率 * 模塊耗電時間,其耗電功率中硬件耗電功率由硬件廠商提供過來的Power_profile.xml 中配置好了,模塊耗電時間為系統中各種Timer 計時器來統計的。
總結下來,電池電量統計服務為系統中基礎服務之一,其主要功能為系統中的各個模塊耗電情況進行統計匯總。為系統app 或者第三方app 提供耗電信息獲取的接口,也為Android 應用開發者和系統開發者提供分析功耗問題的入口。通過該服務,我們能迅速獲取到系統耗電排名情況,能迅速定位到問題app所在。至于系統如何統計各個各個模塊的耗電時間,以及耗電級別情況,下一篇博文會詳細分析系統電池信息的大會計BatteryStatsImpl 類