Android 耗電信息統計服務——BatteryStats源碼分析(一)

文章來源于我的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架構圖

從圖中我們可以看出整個電池管理服務的大概架構是如何的。那么這里面的每個類所擔當的角色是怎樣的呢?
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。

  1. 在/data/system/ 下創建 batterystats.bin 文件和其備份文件 batterystats.bin.tmp,創建電池信息校準文件 batterystats-checkin.bin ,電池每日使用信息 batterystats-daily.xml

3.創建mHandler,其looper 使用的是ActivityManager 的Looper。

  1. 創建各種耗電活動的timer 計時器,標識該活動使用的時長,每一個計時器,每個timer 對應一個唯一的type。
  2. 創建 網絡流量/Modem Radio 活動,非充電狀態次數,拔電狀態下滅屏,Doze活動 等的計數器LongSamplingCounter
  3. 創建wifi,藍牙,基帶數據活動最大級別對應的耗電統計,藍牙和wifi 均只有一個級別,modem有的級別為5(5個傳輸速率對應5個級別的耗電功率)
  4. 初始化各種充電,日期,電池歷史信息參數
    </br>

再來說道說道的PowerProfile 文件,向BatteryStatsImpl中設置的PowerProfile 對象其實是兩方面構成:1.解析power_profile.xml ,將該配置文件中的各項耗電功率讀取出來,設置到電量統計計算類BatteryStatsImpl ;2. 原生上添加增加藍牙,wifi不同狀態下的耗電電流和電壓值
服務啟動并不復雜,只是做一些初始化的工作,大致簡圖如下:


BatteryStatsService服務啟動

耗電統計

當我們進入到原生手機:設置→ 電池→ 應用使用情況 (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 類

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容