原创Android 耗电信息统计服务——BatteryStats源码分析

Posted 风雨田

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原创Android 耗电信息统计服务——BatteryStats源码分析相关的知识,希望对你有一定的参考价值。

android 耗电信息统计服务——BatteryStats源码分析(一)

概述

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 服务中各个相关的类以及其作用做了一个大致的解释,那么其服务是怎么统计的呢,我们继续来一步一步剖析源码

服务启动

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.xml

3.创建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);
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 是如何统计出来的呢。我们来一层一层的抽丝剥茧的根据源码来找寻其原理

从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 和各个硬件上的耗电值综合起来的呢,我们一条一条单个来分析:

软件功耗计算

软件功耗计算函数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;

硬件功耗计算

硬件功耗计算函数在:processMiscUsage()

private void processMiscUsage() 
    addUserUsage();   // 多用户中每个用户的耗电量
    addPhoneUsage();  // modem通话耗电量
    addScreenUsage(); // 屏幕耗电量
    addWiFiUsage();   // wifi耗电量
    addBluetoothUsage(); // 蓝牙耗电量
    addMemoryUsage();    // DDR内存耗电量
    addIdleUsage(); // CPU suspend/idle状态下的耗电量(不包括蜂窝数据空闲功耗)

    if (!mWifiOnly) //(当只有wifi上网功能的设备时不计算蜂窝数据功耗,如平板,电视等)
        addRadioUsage();  //移动数据网络的耗电量
    


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的总数)


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


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


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


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)) / (1000*60*60);


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 级别下的时间)


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

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


所以以上就是所有硬件耗电的计算公式方法。所以硬件的总公式则是
micPowerMah = user_power + phonePower + screenPower + wifiPowerMah + bluetoohPower + memoryPower + idlePower + mobileRadioPower



以上则是系统中获取电量统计中,针对于硬件/软件的功耗值统计排名的计算方法,在应用需要获取电池电量统计时,会去主动调用BatteryStatsHelper 的refreshStats 的方法,将其电量刷新为最新的统计数据,继而获取mUsageList 统计列表来显示系统电量消耗源
计算电量提供给前台app 去显示的流程图大致如下:

总结

电量信息统计服务的统计方式可以简单总结为:耗电量 = 模块耗电功率 * 模块耗电时间,其耗电功率中硬件耗电功率由硬件厂商提供过来的Power_profile.xml 中配置好了,模块耗电时间为系统中各种Timer 计时器来统计的。

总结下来,电池电量统计服务为系统中基础服务之一,其主要功能为系统中的各个模块耗电情况进行统计汇总。为系统app 或者第三方app 提供耗电信息获取的接口,也为Android 应用开发者和系统开发者提供分析功耗问题的入口。通过该服务,我们能迅速获取到系统耗电排名情况,能迅速定位到问题app所在。至于系统如何统计各个各个模块的耗电时间,以及耗电级别情况,下一篇博文会详细分析系统电池信息的大会计BatteryStatsImpl 类

以上是关于原创Android 耗电信息统计服务——BatteryStats源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Android耗电原理及飞书耗电治理

ChatGPT解答:安卓APP耗电量测试方案和源码,用Android代码实现

Android耗电量一体化监控: Battery Historian + APM

Android应用耗电问题排查

Android应用耗电问题排查

Android应用耗电问题排查