Android 耗電信息統(tǒng)計(jì)服務(wù)——BatteryStats源碼分析(一)

文章來源于我的CSDN 博客:http://blog.csdn.net/u011311586/article/details/79044176

概述

Android 中關(guān)于耗電的統(tǒng)計(jì)一般是關(guān)于功耗分析的重要信息,Bettery-historian工具也是依托于解析BatteryStats 的dump 信息來提供界面直觀分析遂跟,并且電池電量耗費(fèi)的源頭實(shí)在太多,基本Android 設(shè)備上任何一個(gè)活動(dòng)都會(huì)引起電池電量的消耗而线,Android 在統(tǒng)計(jì)電量上也在不斷完善汽畴,不斷的在更新桃漾,具體化耗電詳情祥山。耗電名單在主要記錄在BatterySipper里面,雖然在源碼中他并沒有集成在service 端穴张,實(shí)在frameworks/base/core 下细燎,但是谷歌開放sdk 中并沒有公開電量統(tǒng)計(jì)的API 或者文檔,但是并不代表沒有陆馁,因?yàn)榘踩行?>省電優(yōu)化→耗電排行 中就是通過app 能顯示出耗電詳情排行,所以我們將從這個(gè)入口開始分析Android 是如何記錄設(shè)備電池的耗電詳情信息的

BatteryStats服務(wù)架構(gòu)設(shè)計(jì)

由于系統(tǒng)中形形色色合愈,所有的活動(dòng)都會(huì)耗電叮贩,所以BatteryStats服務(wù)也是相當(dāng)?shù)膹?fù)雜,所以首先我們需要摸清楚該服務(wù)的架構(gòu)設(shè)計(jì)佛析,以此來切入分析益老,我們首先來看一下BatteryStats 電池電量統(tǒng)計(jì)服務(wù)的架構(gòu)圖:

BatteryStats架構(gòu)圖

從圖中我們可以看出整個(gè)電池管理服務(wù)的大概架構(gòu)是如何的。那么這里面的每個(gè)類所擔(dān)當(dāng)?shù)慕巧窃鯓拥哪兀?br> BatteryStats: 這是一個(gè)抽象類寸莫,在我看來也算是整個(gè)電池信息統(tǒng)計(jì)服務(wù)的架構(gòu)核心類捺萌,這里面定義了很多內(nèi)部類:
Timer (記錄時(shí)間信息狀態(tài));
ControllerActivityCounter(統(tǒng)計(jì)無(wú)線電數(shù)據(jù)傳輸膘茎,接受桃纯,以及idle狀態(tài));
Counter(記錄計(jì)數(shù)信息的狀態(tài)披坏。如Alarm,Wakelock 等統(tǒng)計(jì)計(jì)數(shù))态坦;
LongCounter(針對(duì)長(zhǎng)期持續(xù)的活動(dòng)統(tǒng)計(jì),如屏幕亮滅棒拂,插拔充電等)伞梯;
UID(針對(duì)App Uid 統(tǒng)計(jì)信息):
Uid由于是統(tǒng)計(jì)app 的耗電量,所以其還定義內(nèi)部類:Wakelock (統(tǒng)計(jì)應(yīng)用申請(qǐng)Wakelock 的情況)帚屉,Sensor(統(tǒng)計(jì)應(yīng)用使用sensor的情況)谜诫,Proc(統(tǒng)計(jì)應(yīng)用進(jìn)程的信息),Pkg(統(tǒng)計(jì)應(yīng)用包的信息攻旦,內(nèi)部類Serv(統(tǒng)計(jì)該包名下服務(wù)的信息))喻旷;

BatteryStatsImpl :為整個(gè)電池信息統(tǒng)計(jì)服務(wù)的計(jì)算核心類,雖然該類是在frameworks/base 端(并非放在services 端)牢屋,但是從分析該服務(wù)源碼能看出來掰邢,BatteryStatsServices 雖然是system_server 中一個(gè)服務(wù),但是實(shí)際上該服務(wù)只是一個(gè)空殼(后面即將講到)伟阔,所有的電池耗電信息相關(guān)計(jì)算都是在BatteryStatsImpl 中實(shí)現(xiàn)的辣之,該類繼承自BatteryStats,并且實(shí)現(xiàn)了BatteryStats 中定義的所有的抽象類以及計(jì)算方法皱炉。

BatteryStatsHelper : 是BatteryStatsImpl 計(jì)算的一個(gè)輔助類怀估,主要是提供給應(yīng)用(比如設(shè)置,安全中心,360等)來展示耗電信息多搀,這里面的定義了軟件類和硬件耗電信息的計(jì)算類***PowerCalculator歧蕉,并且提供獲取耗電信息列表方法getUsageList()

BatterySipper: 英文解釋為:電池吸管,這個(gè)類的對(duì)象才是每個(gè)耗電的實(shí)體項(xiàng)統(tǒng)計(jì)康铭,在安全中心中耗電排行中惯退,每一個(gè)耗電項(xiàng)都是一個(gè)BatterySipper對(duì)象。

以上對(duì)BatteryStats 服務(wù)中各個(gè)相關(guān)的類以及其作用做了一個(gè)大致的解釋从藤,那么其服務(wù)是怎么統(tǒng)計(jì)的呢催跪,我們繼續(xù)來一步一步剖析源碼

</br>

服務(wù)啟動(dòng)

BatteryStats 服務(wù)是在AMS 的構(gòu)造函數(shù)中啟動(dòng)的

ActivityManagerService 構(gòu)造函數(shù)中:

mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler);
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.scheduleWriteToDisk();
mOnBattery = DEBUG_POWER ? true
        : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
mBatteryStatsService.getActiveStatistics().setCallback(this);

在AMS 構(gòu)造函數(shù)中創(chuàng)建BatteryStatsService 的對(duì)象,并且開始讀取統(tǒng)計(jì)文件里已經(jīng)保存的統(tǒng)計(jì)信息夷野。并且開始異步 的去記錄信息懊蒸,設(shè)置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);  //設(shè)置RadioScanningTimeout 值(0 * 1000L)
    mStats.setPowerProfileLocked(new PowerProfile(context)); //設(shè)置PowerProfile(電池基本參數(shù)信息)。
}
 
    public void publish() {
        ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
    }

1.在構(gòu)造函數(shù)中悯搔,使用BatteryExternalStatsWorker 內(nèi)部統(tǒng)計(jì)集合來收集電池耗電信息了(8.1之前的是創(chuàng)建一個(gè)新的線程batterystats-sync用來記錄電池電量信息) 骑丸,從AMS中傳過來的mHandler(ActivityManager線程)給BatteryStatsImpl 用于記錄wakelock,PowerChange妒貌,charging 等信息通危。設(shè)置外部硬件統(tǒng)計(jì)對(duì)象mWorker
2.在AMS 中onStart()函數(shù)中調(diào)用BatteryStatsService.publish() ,將batterystats 服務(wù)注冊(cè)到system_server 進(jìn)程中灌曙』器ⅲ可以看到在publish 中邏輯:3.將batterystats 服務(wù)添加到ServiceManager 中。

</br>
我們這里需要重點(diǎn)關(guān)注BatteryStatsImpl 的初始化平匈,因?yàn)閺囊陨戏治鰜砜措m然電量統(tǒng)計(jì)服務(wù)是system_server進(jìn)程中的一個(gè)服務(wù)轻抱,但是其主要只是一個(gè)proxy 的作用蝇恶,整體的計(jì)算工作還是交給BatteryStatsImpl 去做的,所以BatteryStatsImpl 才是整個(gè)耗電信息的計(jì)算核心類。

BatteryStatsImpl 構(gòu)造函數(shù)

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;
}

構(gòu)造函數(shù)大概干了幾件事:
1.傳入的mClocks 為AMS 啟動(dòng)時(shí)候創(chuàng)建的SystemClock智蝠。

  1. 在/data/system/ 下創(chuàng)建 batterystats.bin 文件和其備份文件 batterystats.bin.tmp枚粘,創(chuàng)建電池信息校準(zhǔn)文件 batterystats-checkin.bin 户敬,電池每日使用信息 batterystats-daily.xml

3.創(chuàng)建mHandler唉擂,其looper 使用的是ActivityManager 的Looper。

  1. 創(chuàng)建各種耗電活動(dòng)的timer 計(jì)時(shí)器输玷,標(biāo)識(shí)該活動(dòng)使用的時(shí)長(zhǎng)队丝,每一個(gè)計(jì)時(shí)器,每個(gè)timer 對(duì)應(yīng)一個(gè)唯一的type欲鹏。
  2. 創(chuàng)建 網(wǎng)絡(luò)流量/Modem Radio 活動(dòng)机久,非充電狀態(tài)次數(shù),拔電狀態(tài)下滅屏赔嚎,Doze活動(dòng) 等的計(jì)數(shù)器LongSamplingCounter
  3. 創(chuàng)建wifi膘盖,藍(lán)牙胧弛,基帶數(shù)據(jù)活動(dòng)最大級(jí)別對(duì)應(yīng)的耗電統(tǒng)計(jì),藍(lán)牙和wifi 均只有一個(gè)級(jí)別侠畔,modem有的級(jí)別為5(5個(gè)傳輸速率對(duì)應(yīng)5個(gè)級(jí)別的耗電功率)
  4. 初始化各種充電结缚,日期,電池歷史信息參數(shù)
    </br>

再來說道說道的PowerProfile 文件软棺,向BatteryStatsImpl中設(shè)置的PowerProfile 對(duì)象其實(shí)是兩方面構(gòu)成:1.解析power_profile.xml 红竭,將該配置文件中的各項(xiàng)耗電功率讀取出來,設(shè)置到電量統(tǒng)計(jì)計(jì)算類BatteryStatsImpl 喘落;2. 原生上添加增加藍(lán)牙茵宪,wifi不同狀態(tài)下的耗電電流和電壓值
服務(wù)啟動(dòng)并不復(fù)雜,只是做一些初始化的工作揖盘,大致簡(jiǎn)圖如下:


BatteryStatsService服務(wù)啟動(dòng)

耗電統(tǒng)計(jì)

當(dāng)我們進(jìn)入到原生手機(jī):設(shè)置→ 電池→ 應(yīng)用使用情況 (MIUI的安全中心→省電優(yōu)化→耗電排行) 代碼基本都是一樣眉厨,可以看到電池各個(gè)模塊的耗電排行锌奴,那么他是怎么計(jì)算出來的呢兽狭,我們由此為入口,由點(diǎn)及面的來展開

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);   //添加到硬件耗電
    }
}

以上代碼鹿蜀,是粘貼的設(shè)置中關(guān)于電池耗電統(tǒng)計(jì)的一段代碼箕慧,我們可以看到安全中心中獲取耗電整體的信息是通過BatteryStatsHelper.getUsageList()方法獲取到所有耗電的list ,通過判斷DrainType 是app 還是其他硬件茴恰,來區(qū)分統(tǒng)計(jì)軟件以及硬件(PHONE, SCREEN, WIFI, BLUETOOTH, BLUETOOTH, IDLE, CELL, OTHER)的耗電颠焦。 那么getUsageList 中BatterySipper List 是如何統(tǒng)計(jì)出來的呢。我們來一層一層的抽絲剝繭的根據(jù)源碼來找尋其原理
</br>
從BatteryStatsHelper 中定義的相關(guān)usage list 能看出來往枣,系統(tǒng)中將耗電總共分成了五大類:App伐庭,Wifi,Bluetooth 分冈,User圾另,Mobile。getUsageList中獲取到的list 就是這五類耗電信息的綜合雕沉。當(dāng)我們每次進(jìn)入到耗電詳情排名界面時(shí)(或者dump時(shí))集乔,都會(huì)刷新一次當(dāng)前實(shí)際耗電信息。而在刷新電池耗電信息坡椒,來執(zhí)行一次聚合所有的耗電信息到usage 中扰路。我們來看看其核心函數(shù):

public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
        long rawUptimeUs) {
    // Initialize mStats if necessary.
    getStats();
...... //初始化一些PowerCalculato 以及各類時(shí)間參數(shù)
    processAppUsage(asUsers);
 
.... // 記錄移動(dòng)數(shù)據(jù)流量到mMobilemsppList 中
    processMiscUsage();
 
    Collections.sort(mUsageList);
.... // 對(duì)統(tǒng)計(jì)數(shù)據(jù)做一些去雜和優(yōu)化
}

該函數(shù)實(shí)際有兩百多行,但是其核心處理只有兩個(gè)函數(shù):
processAppUsage 計(jì)算軟件app功耗
processMiscUsage 計(jì)算硬件功耗
那么他是怎么將各個(gè)app 和各個(gè)硬件上的耗電值綜合起來的呢倔叼,我們一條一條單個(gè)來分析:
</br>

軟件功耗計(jì)算

軟件功耗計(jì)算函數(shù)processAppUsage() : 在 sumPower()計(jì)算總和

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);
    //計(jì)算app 消耗的Cpu電量到cpuPowerMah 中
    mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); 
    //計(jì)算app 使用的Wakelock電量到wakeLockPowerMah 中 
    mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); 
    // 計(jì)算app 使用radio 網(wǎng)絡(luò)消耗的電量到mobileRadioPowerMah 中 
    mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,            
            mStatsType);       
     // 計(jì)算app 使用的Wifi電量到wifiPowerMah 中                                                
    mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);   
    // 計(jì)算app 使用藍(lán)牙的電量到bluetoothPowerMah 中   
    mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,              
            mStatsType);
    // 計(jì)算app 使用的Sensor電量到sensorPowerMah 中
    mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);   
    // 計(jì)算app 使用camera的電量到cameraPowerMah 中
    mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);   
     // 計(jì)算app 使用閃光燈Flashlight 的電量到flashlightPowerMah
    mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,           
            mStatsType);
 
    final double totalPower = app.sumPower();

軟件功耗計(jì)算公式:
totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah + flashlightPowerMah + bluetoothPowerMah;

</br>

硬件功耗計(jì)算

硬件功耗計(jì)算函數(shù)在:processMiscUsage()

private void processMiscUsage() {
    addUserUsage();   // 多用戶中每個(gè)用戶的耗電量
    addPhoneUsage();  // modem通話耗電量
    addScreenUsage(); // 屏幕耗電量
    addWiFiUsage();   // wifi耗電量
    addBluetoothUsage(); // 藍(lán)牙耗電量
    addMemoryUsage();    // DDR內(nèi)存耗電量
    addIdleUsage(); // CPU suspend/idle狀態(tài)下的耗電量(不包括蜂窩數(shù)據(jù)空閑功耗)
    
    if (!mWifiOnly) {//(當(dāng)只有wifi上網(wǎng)功能的設(shè)備時(shí)不計(jì)算蜂窩數(shù)據(jù)功耗汗唱,如平板,電視等)
        addRadioUsage();  //移動(dòng)數(shù)據(jù)網(wǎng)絡(luò)的耗電量
    }
}

</br>
</br>

Users

多用戶下各個(gè)用戶的耗電量

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 為各個(gè)app 在非當(dāng)前用戶下的耗電(每一個(gè)userid 對(duì)應(yīng)一個(gè)BatterySipper List)丈攒,其中Android 電量統(tǒng)計(jì)中將其他用戶使用的耗電量都統(tǒng)歸為mUserSippers 的硬件耗電渡嚣。
公式:user_power = user_1_powerMah + user_2_powerMah + … + user_n_powerMah; (n為所有的user的總數(shù))

</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);
    }
}

計(jì)算Phone 通話的耗電量,從PowerProfile 中讀取POWER_RADIO_ACTIVE 的功率,與Phone 信號(hào)的時(shí)間計(jì)算出其功耗值
公式: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);  //屏幕打開時(shí)的功耗识椰,不包括背光功耗绝葡。
    final double screenFullPower =
            mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);  // 最高背光亮度下的功耗。(如果背光亮度為50%腹鹉,則應(yīng)該乘以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);
    }
}

屏幕的功耗是排除在設(shè)備運(yùn)行時(shí)屏幕的功耗(比如繪圖藏畅,動(dòng)畫等),這里計(jì)算的屏幕功耗功咒,主要是 屏幕保持活躍狀態(tài)時(shí)的功耗值屏幕被點(diǎn)亮后不同背光強(qiáng)度下的功耗值
屏幕保持活躍時(shí)的功耗值: screenOnPower = screenOnTimeMs * POWER_SCREEN_ON (screenon功率)
屏幕不同背光下的功耗值:屏幕背光分為5個(gè)級(jí)別( BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS)
背光功率為:screenFullPower * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; //也就是最高級(jí)別功率為 1.1 ,最低級(jí)別為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 計(jì)算
@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) {
        // 有些控制器不報(bào)告功耗愉阎,所以我們可以在這里計(jì)算。
        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 計(jì)算wifi功耗:
mIdleCurrentMa: wifi controller 處于idle 狀態(tài)下的功率 (PowerProfile.POWER_WIFI_CONTROLLER_IDLE)
mTxCurrentMa: wifi controller 處于Tx 上行狀態(tài)下的功率 (PowerProfile.POWER_WIFI_CONTROLLER_TX)
mRxCurrentMa: wifi controller 處于rx 下行狀態(tài)下的功率 (PowerProfile.POWER_WIFI_CONTROLLER_RX)
公式:powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) + (rxTimeMs * mRxCurrentMa)) / (10006060);

WifiPowerEstimator 計(jì)算wifi功耗:
mWifiPowerOn:wifi驅(qū)動(dòng)打開時(shí)的功耗 (PowerProfile.POWER_WIFI_ON)
mWifiPowerScan: WiFi驅(qū)動(dòng)程序掃描網(wǎng)絡(luò)時(shí)的功耗力奋。(PowerProfile.POWER_WIFI_SCAN)
mWifiPowerBatchScan: wif批量掃描消耗的功率榜旦。 按“每小時(shí)掃描的頻道”分解為分組。(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) {
        // 有些設(shè)備不報(bào)告功率溅呢,所以在這里計(jì)算一下。
        powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
                / (1000*60*60);
    }
 
    // 減去使用的應(yīng)用程序猿挚,但不能小于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);
}

藍(lán)牙功耗與wifi 功耗計(jì)算相似:
mIdleMa: Bluetooth controller 處于idle 狀態(tài)下的功率 (PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE)
mRxMa:Bluetooth controller 處于Rx 下行狀態(tài)下的功率 (PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX)
mTxMa:Bluetooth controller 處于Tx 上行狀態(tài)下的功率 (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);  //不同速率下運(yùn)行時(shí)間
        double mAm = (mAatRail * timeMs) / (1000*60);
        totalMah += mAm/60;
        totalTimeMs += timeMs;
    }
    app.usagePowerMah = totalMah;
    app.usageTimeMs = totalTimeMs;
}

MemoryPowerCalculator 是8.0 上新加的,主要是統(tǒng)計(jì)DDR內(nèi)存上的耗電量
powerAverages : 每個(gè)讀寫速率級(jí)別上的功率 (PowerProfile.POWER_MEMORY)
公式: memoryPower = (mAatRail_1 * timeMs_1 + mAatRail_2 * timeMs_2 + ... + mAatRail_n * timeMs_n) / (1000 * 60 * 60) (mAatRail_n :是該讀寫速率級(jí)別下的功率绩蜻,timeMs_n:是在mAatRail_n 級(jí)別下的時(shí)間)

</br>
</br>

Idle

private void addIdleUsage() {
    final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) *
            mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE);   //cpu 處于idle 的時(shí)間
    final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) *
            mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);  //cpu 處于awker的時(shí)間
    final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);
 
    if (totalPowerMah != 0) {
        addEntry(BatterySipper.DrainType.IDLE, mTypeBatteryRealtimeUs / 1000, totalPowerMah);
    }
}

這里是計(jì)算設(shè)備cpu處于idle 狀態(tài)和 suspend 狀態(tài) 的基準(zhǔn)功耗值铣墨,其中包括:
設(shè)備在最低電量狀態(tài)下處于POWER_CPU_IDLE的功耗
設(shè)備持有wakelock 時(shí)候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;       // 計(jì)算信號(hào)強(qiáng)度的功耗
        signalTimeMs += strengthTimeMs;
        if (i == 0) {
            noCoverageTimeMs = strengthTimeMs;
        }
    }
 
    final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
            / 1000;
    final double p = (scanningTimeMs * mPowerScan) / (60*60*1000); // 計(jì)算搜網(wǎng)的功耗
 
    power += p;
    long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
    long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
    if (remainingActiveTimeMs > 0) {
        power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);  //計(jì)算駐網(wǎng)的功耗
    }
 
    if (power != 0) {
        if (signalTimeMs != 0) {
            app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
        }
        app.mobileActive = remainingActiveTimeMs;
        app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
        app.mobileRadioPowerMah = power;
    }
}

這里統(tǒng)計(jì)的是無(wú)限數(shù)據(jù)網(wǎng)絡(luò)的耗電。
此類功耗計(jì)算包括三個(gè)方面:信號(hào)強(qiáng)度(signalStrenth)办绝,搜索運(yùn)營(yíng)商網(wǎng)(scanning)伊约,駐網(wǎng)(remainingActive)。

信號(hào)強(qiáng)度(signalStrenth): 在android 設(shè)備中分了6(SignalStrength.NUM_SIGNAL_STRENGTH_BINS:"none", "poor", "moderate", "good", "great", "excellent")個(gè)等級(jí),每個(gè)等級(jí)對(duì)應(yīng)相應(yīng)的功率(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屡律;

搜索運(yùn)營(yíng)商網(wǎng)(scanning):搜網(wǎng)過程其實(shí)是一個(gè)耗電的過程,對(duì)應(yīng)的功率(PowerProfile.POWER_RADIO_SCANNING)
子公式:scanningPower = scanningTimeMs * mPowerScan昔驱;

駐網(wǎng)(remainingActive):駐網(wǎng)的過程中是需要保持活動(dòng)的疹尾,讓基站知道該設(shè)備是活躍的狀態(tài)。所以該活動(dòng)的功率(PowerProfile.POWER_RADIO_ACTIVE)
子公式:remainingActivePower = (radioActiveTimeMs - mTotalAppMobileActiveMs)* mPowerRadioOn

總公式:mobileRadioPower = strengthOnPower + scanningPower + remainingActivePower

</br>
</br>

所以以上就是所有硬件耗電的計(jì)算公式方法骤肛。所以硬件的總公式則是
micPowerMah = user_power + phonePower + screenPower + wifiPowerMah + bluetoohPower + memoryPower + idlePower + mobileRadioPower

</br>
</br>
以上則是系統(tǒng)中獲取電量統(tǒng)計(jì)中纳本,針對(duì)于硬件/軟件的功耗值統(tǒng)計(jì)排名的計(jì)算方法,在應(yīng)用需要獲取電池電量統(tǒng)計(jì)時(shí)腋颠,會(huì)去主動(dòng)調(diào)用BatteryStatsHelper 的refreshStats 的方法繁成,將其電量刷新為最新的統(tǒng)計(jì)數(shù)據(jù),繼而獲取mUsageList 統(tǒng)計(jì)列表來顯示系統(tǒng)電量消耗源
計(jì)算電量提供給前臺(tái)app 去顯示的流程圖大致如下:


電量計(jì)算流程及公式圖

</br>

總結(jié)

電量信息統(tǒng)計(jì)服務(wù)的統(tǒng)計(jì)方式可以簡(jiǎn)單總結(jié)為:耗電量 = 模塊耗電功率 * 模塊耗電時(shí)間淑玫,其耗電功率中硬件耗電功率由硬件廠商提供過來的Power_profile.xml 中配置好了巾腕,模塊耗電時(shí)間為系統(tǒng)中各種Timer 計(jì)時(shí)器來統(tǒng)計(jì)的面睛。

總結(jié)下來,電池電量統(tǒng)計(jì)服務(wù)為系統(tǒng)中基礎(chǔ)服務(wù)之一尊搬,其主要功能為系統(tǒng)中的各個(gè)模塊耗電情況進(jìn)行統(tǒng)計(jì)匯總叁鉴。為系統(tǒng)app 或者第三方app 提供耗電信息獲取的接口,也為Android 應(yīng)用開發(fā)者和系統(tǒng)開發(fā)者提供分析功耗問題的入口佛寿。通過該服務(wù)幌墓,我們能迅速獲取到系統(tǒng)耗電排名情況,能迅速定位到問題app所在冀泻。至于系統(tǒng)如何統(tǒng)計(jì)各個(gè)各個(gè)模塊的耗電時(shí)間常侣,以及耗電級(jí)別情況,下一篇博文會(huì)詳細(xì)分析系統(tǒng)電池信息的大會(huì)計(jì)BatteryStatsImpl 類

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弹渔,一起剝皮案震驚了整個(gè)濱河市胳施,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肢专,老刑警劉巖舞肆,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鸟召,居然都是意外死亡胆绊,警方通過查閱死者的電腦和手機(jī)氨鹏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門欧募,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人仆抵,你說我怎么就攤上這事跟继。” “怎么了镣丑?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵舔糖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我莺匠,道長(zhǎng)金吗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任趣竣,我火速辦了婚禮摇庙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遥缕。我一直安慰自己卫袒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布单匣。 她就那樣靜靜地躺著夕凝,像睡著了一般宝穗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上码秉,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天逮矛,我揣著相機(jī)與錄音,去河邊找鬼转砖。 笑死橱鹏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的堪藐。 我是一名探鬼主播莉兰,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼礁竞!你這毒婦竟也來了糖荒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤模捂,失蹤者是張志新(化名)和其女友劉穎捶朵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狂男,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡综看,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了岖食。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片红碑。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泡垃,靈堂內(nèi)的尸體忽然破棺而出析珊,到底是詐尸還是另有隱情,我是刑警寧澤蔑穴,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布忠寻,位于F島的核電站,受9級(jí)特大地震影響存和,放射性物質(zhì)發(fā)生泄漏奕剃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一捐腿、第九天 我趴在偏房一處隱蔽的房頂上張望纵朋。 院中可真熱鬧,春花似錦叙量、人聲如沸倡蝙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寺鸥。三九已至猪钮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胆建,已是汗流浹背烤低。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笆载,地道東北人扑馁。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像凉驻,于是被迫代替她去往敵國(guó)和親腻要。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容