文章来源于我的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 类