文章來源于我的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)圖:
從圖中我們可以看出整個(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智蝠。
- 在/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。
- 創(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欲鹏。
- 創(chuàng)建 網(wǎng)絡(luò)流量/Modem Radio 活動(dòng)机久,非充電狀態(tài)次數(shù),拔電狀態(tài)下滅屏赔嚎,Doze活動(dòng) 等的計(jì)數(shù)器LongSamplingCounter
- 創(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í)別的耗電功率)
- 初始化各種充電结缚,日期,電池歷史信息參數(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)圖如下:
耗電統(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 去顯示的流程圖大致如下:
</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 類