本文共 21824 字,大约阅读时间需要 72 分钟。
Android 中关于耗电的统计一般是关于功耗分析的重要信息,Bettery-historian工具也是依托于解析BatteryStats 的dump 信息来提供界面直观分析,并且电池电量耗费的源头实在太多,基本Android 设备上任何一个活动都会引起电池电量的消耗,Android 在统计电量上也在不断完善,不断的在更新,具体化耗电详情。耗电名单在主要记录在BatterySipper里面,虽然在源码中他并没有集成在service 端,实在frameworks/base/core 下,但是谷歌开放sdk 中并没有公开电量统计的API 或者文档,但是并不代表没有,因为安全中心->省电优化→耗电排行 中就是通过app 能显示出耗电详情排行,所以我们将从这个入口开始分析Android 是如何记录设备电池的耗电详情信息的
由于系统中形形色色,所有的活动都会耗电,所以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 服务中各个相关的类以及其作用做了一个大致的解释,那么其服务是怎么统计的呢,我们继续来一步一步剖析源码
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 中。我们这里需要重点关注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。 2. 在/data/system/ 下创建 batterystats.bin 文件和其备份文件 batterystats.bin.tmp,创建电池信息校准文件 batterystats-checkin.bin ,电池每日使用信息 batterystats-daily.xml3.创建mHandler,其looper 使用的是ActivityManager 的Looper。
4. 创建各种耗电活动的timer 计时器,标识该活动使用的时长,每一个计时器,每个timer 对应一个唯一的type。 5. 创建 网络流量/Modem Radio 活动,非充电状态次数,拔电状态下灭屏,Doze活动 等的计数器LongSamplingCounter 6. 创建wifi,蓝牙,基带数据活动最大级别对应的耗电统计,蓝牙和wifi 均只有一个级别,modem有的级别为5(5个传输速率对应5个级别的耗电功率) 7. 初始化各种充电,日期,电池历史信息参数再来说道说道的PowerProfile 文件,向BatteryStatsImpl中设置的PowerProfile 对象其实是两方面构成:1.解析power_profile.xml ,将该配置文件中的各项耗电功率读取出来,设置到电量统计计算类BatteryStatsImpl ;2. 原生上添加增加蓝牙,wifi不同状态下的耗电电流和电压值
服务启动并不复杂,只是做一些初始化的工作,大致简图如下:当我们进入到原生手机:设置→ 电池→ 应用使用情况 (MIUI的安全中心→省电优化→耗电排行) 代码基本都是一样,可以看到电池各个模块的耗电排行,那么他是怎么计算出来的呢,我们由此为入口,由点及面的来展开
mHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);ListusageList = 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 是如何统计出来的呢。我们来一层一层的抽丝剥茧的根据源码来找寻其原理
从BatteryStatsHelper 中定义的相关usage list 能看出来,系统中将耗电总共分成了五大类:App,Wifi,Bluetooth ,User,Mobile。getUsageList中获取到的list 就是这五类耗电信息的综合。当我们每次进入到耗电详情排名界面时(或者dump时),都会刷新一次当前实际耗电信息。而在刷新电池耗电信息,来执行一次聚合所有的耗电信息到usage 中。我们来看看其核心函数:public void refreshStats(int statsType, SparseArrayasUsers, long rawRealtimeUs, long rawUptimeUs) { // Initialize mStats if necessary. getStats();...... //初始化一些PowerCalculato 以及各类时间参数 processAppUsage(asUsers);.... // 记录移动数据流量到mMobilemsppList 中 processMiscUsage(); Collections.sort(mUsageList);.... // 对统计数据做一些去杂和优化}
该函数实际有两百多行,但是其核心处理只有两个函数:
processAppUsage 计算软件app功耗 processMiscUsage 计算硬件功耗 那么他是怎么将各个app 和各个硬件上的耗电值综合起来的呢,我们一条一条单个来分析:软件功耗计算函数processAppUsage() : 在 sumPower()计算总和
final SparseArray 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;硬件功耗计算函数在:processMiscUsage()
private void processMiscUsage() { addUserUsage(); // 多用户中每个用户的耗电量 addPhoneUsage(); // modem通话耗电量 addScreenUsage(); // 屏幕耗电量 addWiFiUsage(); // wifi耗电量 addBluetoothUsage(); // 蓝牙耗电量 addMemoryUsage(); // DDR内存耗电量 addIdleUsage(); // CPU suspend/idle状态下的耗电量(不包括蜂窝数据空闲功耗) if (!mWifiOnly) { //(当只有wifi上网功能的设备时不计算蜂窝数据功耗,如平板,电视等) addRadioUsage(); //移动数据网络的耗电量 }}
多用户下各个用户的耗电量
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的总数)
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);
/** * 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);
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 计算@Overridepublic 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@Overridepublic 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)) / (1000*60*60);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);
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); }}@Overridepublic 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)) / (1000*60*60);
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); }}@Overridepublic void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, long rawUptimeUs, int statsType) { double totalMah = 0; long totalTimeMs = 0; LongSparseArray 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 级别下的时间)
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);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); }}@Overridepublic 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
所以以上就是所有硬件耗电的计算公式方法。所以硬件的总公式则是
micPowerMah = user_power + phonePower + screenPower + wifiPowerMah + bluetoohPower + memoryPower + idlePower + mobileRadioPower以上则是系统中获取电量统计中,针对于硬件/软件的功耗值统计排名的计算方法,在应用需要获取电池电量统计时,会去主动调用BatteryStatsHelper 的refreshStats 的方法,将其电量刷新为最新的统计数据,继而获取mUsageList 统计列表来显示系统电量消耗源 计算电量提供给前台app 去显示的流程图大致如下:
电量信息统计服务的统计方式可以简单总结为:耗电量 = 模块耗电功率 * 模块耗电时间,其耗电功率中硬件耗电功率由硬件厂商提供过来的Power_profile.xml 中配置好了,模块耗电时间为系统中各种Timer 计时器来统计的。
总结下来,电池电量统计服务为系统中基础服务之一,其主要功能为系统中的各个模块耗电情况进行统计汇总。为系统app 或者第三方app 提供耗电信息获取的接口,也为Android 应用开发者和系统开发者提供分析功耗问题的入口。通过该服务,我们能迅速获取到系统耗电排名情况,能迅速定位到问题app所在。至于系统如何统计各个各个模块的耗电时间,以及耗电级别情况,下一篇博文会详细分析系统电池信息的大会计BatteryStatsImpl 类