软硬件的耗电量在BatteryStatsHelper.java中的两个方法中实现

  • processAppUsage 软件APP耗电量
  • processMiscUsage 硬件耗电量

还是在线网站http://androidxref.com/上对Android版本6.0.1_r10源码进行分析

具体位置在 /frameworks/base/core/java/com/android/internal/os/BatteryStatsHelper.java

本篇大部分来自 https://blog.csdn.net/FightFightFight/article/details/82694381

APP八大耗电模块

八大模块的耗电计算器

计算项 Class文件
CPU功耗 mCpuPowerCalculator.java
Wakelock功耗 mWakelockPowerCalculator.java
无线电功耗 mMobileRadioPowerCalculator.java
WIFI功耗 mWifiPowerCalculator.java
蓝牙功耗 mBluetoothPowerCalculator.java
Sensor功耗 mSensorPowerCalculator.java
相机功耗 mCameraPowerCalculator.java
闪光灯功耗 mFlashlightPowerCalculator.java

硬件耗电模块

硬件功耗的子项分为7项

功耗项 解释
UserUsage 用户功耗
PhoneUsage 通话功耗
ScreenUsage 屏幕功耗
WiFiUsage Wifi功耗
BluetoothUsage 蓝牙消耗
IdleUsage CPU Idle功耗
RadioUsage 移动无线功耗

processAppUsage方法

该方法用来统计软件耗电量,在进行统计时,以uid为单位进行统计,Android中的uid是应用安装时由系统分配的,每个应用对应一个uid,背后也涉及了Android的安全机制。咱们也可以通过清单文件 AndroidManifest.xml中的shareUserId指定两个应用的uid相同,用来共享部分数据信息

例如这里就是跟系统同一个uid,即设置为系统应用

对于每个uid,也就是我们的一个APP,都会对应一个BatterySipper对象,APP计算得到的耗电数据会存储到BatterySipper对象中

跟进方法查看

是否统计所有用户使用的APP,默认为false

final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);

电池对应类型的真实运行时间

mStatsPeriod = mTypeBatteryRealtime;

该值在refreshStats方法中已经赋值

mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);

系统耗电量

BatterySipper osSipper = null;

获取所有应用APP uid并遍历,实例化BatterySipper对象,存储电量的统计信息

BatterySipper.DrainType.APP表示统计类型为应用耗电

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

软件的耗电归根结底还是使用硬件导致的,比如说屏幕,WIFI,CPU等等,所以接下来就是统计APP对应使用的八大硬件的耗电量

// 计算CPU耗电量
mCpuPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
// 计算wakelock耗电量,也就是唤醒手机,不让它息屏,比如说我们玩游戏的时候手机不会玩着玩着就黑屏
mWakelockPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
// 计算无线电耗电量
mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
// 计算WIFI耗电量
mWifiPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
// 计算蓝牙耗电量
mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
// 计算传感器耗电量
mSensorPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
// 计算相机耗电量
mCameraPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
// 计算闪光灯耗电量
mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);

APP总耗电量

final double totalPower = app.sumPower();
if (DEBUG && totalPower != 0) {
    Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),
                             makemAh(totalPower)));
}

如果统计电量大于零或者为ROOT用户,进入if语句

// Add the app to the list if it is consuming power.
if (totalPower != 0 || u.getUid() == 0) {
    //
    // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
    // 获取对应APP的进程ID
    final int uid = app.getUid();
    // 获取用户ID,默认用户ID为0
    final int userId = UserHandle.getUserId(uid);

如果当前进程为WIFI,WIFI uid为1010,则添加到统计wifi耗电量的列表List<BatterySipper>

if (uid == Process.WIFI_UID) {
    mWifiSippers.add(app);

蓝牙添加到蓝牙列表中 uid为1002

} else if (uid == Process.BLUETOOTH_UID) {
    mBluetoothSippers.add(app);

当不为多用户并且该userId没有对应的用户时,添加到其他用户中

} else if (!forAllUsers && asUsers.get(userId) == null
           && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
    // We are told to just report this user's apps as one large entry.
    List<BatterySipper> list = mUserSippers.get(userId);
    if (list == null) {
        list = new ArrayList<>();
        mUserSippers.put(userId, list);
    }
    list.add(app);

app耗电都添加到mUsageList

} else {
    mUsageList.add(app);
}

如果是root进程,也就是操作系统的耗电量,则设置为osSipper

if (uid == 0) {
    osSipper = app;
}

当然计算并不一定准确,设备真实的唤醒时间可能比屏幕打开时间和应用程序唤醒时间更长,所以如果还剩下没有分配的电量消耗,将其分配给操作系统

if (osSipper != null) {
    // The device has probably been awake for longer than the screen on
    // time and application wake lock time would account for.  Assign
    // this remainder to the OS, if possible.
    mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtime,
                                                mRawUptime, mStatsType);
    osSipper.sumPower();
}

APP对于八大硬件耗电计算

接下来详细分析APP对于八大硬件的耗电量计算

(算了,直接看这篇吧,这部分我暂时还用不到 http://gityuan.com/2016/01/10/power_rank/

1 CPU

2 Wakelock

3 WIFI

4 BlueTooth

5 Camera

6 Flashlight

7 MobileRadio

8 Sensor

processMiscUsage方法

processMiscUsage()方法来统计硬件耗电量

private void processMiscUsage() {
    addUserUsage();
    addPhoneUsage();
    addScreenUsage();
    addWiFiUsage();
    addBluetoothUsage();
    addIdleUsage(); // Not including cellular idle power
    // Don't compute radio usage if it's a wifi-only device
    if (!mWifiOnly) {
        addRadioUsage();
    }
}

计算用户耗电量

addUserUsage();

具体实现如下,将UserSippers中的功耗都合入bs

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

aggregateSippers方法实现如下

private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
    for (int i=0; i<from.size(); i++) {
        BatterySipper wbs = from.get(i);
        if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTimeMs);
        bs.add(wbs); // 将from中的功耗数据合入bs
    }
    bs.computeMobilemspp();
    bs.sumPower(); // 计算总功耗
}

累加所有通话耗电情况,统计到DrainType.PHONE类型的BatterySipper

addPhoneUsage();

具体实现为

private void addPhoneUsage() {
    // 获取BatteryStatsService中统计的通话总时长
    long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtime, mStatsType) / 1000;
    // 获取mPowerProfile文件中无线电发送/接收信号时消耗的平均电量
    double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
        * phoneOnTimeMs / (60*60*1000);
    if (phoneOnPower != 0) {
        // 实例化一个DrainTyep为PHONE类型的BatterySipper,并将phoneOnTimeMs、phoneOnPower进行赋值给BatterySipper
        addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
    }
}

累加所有亮屏的耗电情况,统计到DrainType.SEREEN类型的BatterySipper

addScreenUsage();
private void addScreenUsage() {
    double power = 0;
    // 获取BatteryStatsService 中统计的屏幕亮屏时间
    long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtime, mStatsType) / 1000;
    // 屏幕耗电量 = 亮屏时间 * 每毫秒亮屏耗电量
    power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
    // 屏幕以最高亮度打开时每毫秒消耗的电量
    final double screenFullPower =
        mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
    // 遍历相加所有的屏幕耗电值,存储到power
    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, mRawRealtime, mStatsType)
            / 1000;
        double p = screenBinPower*brightnessTime;
        if (DEBUG && p != 0) {
            Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
                  + " power=" + makemAh(p / (60 * 60 * 1000)));
        }
        power += p;
    }
    // 单位切换为小时
    power /= (60*60*1000); // To hours
    // 实例化一个DrainType为SCREEN类型的BatterySipper,并将screenOnTimeMs,power进行赋值给BatterySipper
    if (power != 0) {
        addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
    }
}

WIFI耗电量,硬件功耗类型为DrainType.WIFI

addWiFiUsage();
private void addWiFiUsage() {
    // 实例化一个DrainType.WIFI的BatterySipper,用来存储WIFI的耗电量
    BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
    // 计算不属于应用消耗的WIFI耗电
    mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, mStatsType);
    // 收集统计APP耗电时获取的WIFI耗电量和剩余WIFI耗电量到bs中
    aggregateSippers(bs, mWifiSippers, "WIFI");
    // 将bs添加到mUsageList中
    if (bs.totalPowerMah > 0) {
        mUsageList.add(bs);
    }
}

BlueTooth蓝牙耗电量 DrainTyep.BLUETOOTH类型

private void addBluetoothUsage() {
    // 实例化蓝牙BatterySipper
    BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
    // 通过BT耗电量计算器计算除应用以外的蓝牙耗电量
    mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime,
                                                 mStatsType);
    aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
    if (bs.totalPowerMah > 0) {
        mUsageList.add(bs);
    }
}

CPU IDLE耗电量,类型为DrainType.IDLE

addIdleUsage();
private void addIdleUsage() {
    long idleTimeMs = (mTypeBatteryRealtime
                       - mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000;
    double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
        / (60*60*1000);
    if (DEBUG && idlePower != 0) {
        Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower));
    }
    if (idlePower != 0) {
        addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower);
    }
}

如果不是仅WIFI设备(比如平板就只有WIFI),咱们还需要计算流量消耗

if (!mWifiOnly) {
    addRadioUsage();
}

private void addRadioUsage() {
    BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
    // 通过无线电耗电量计算器计算无法归因于应用的耗电量
    mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtime, mRawUptime,
                                                   mStatsType);
    // 得到无线电耗电量总和
    radio.sumPower();
    if (radio.totalPowerMah > 0) {
        mUsageList.add(radio);
    }
}

软硬件功耗项对比

功耗项 软件榜 硬件榜
CPU
无线电
WIFI
蓝牙
Wakelock -
Sensor -
相机 -
闪光灯 -
通话 -
屏幕 -
用户 -

参考链接

END

建了一个微信的安全交流群,欢迎添加我微信备注进群,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注 ????

软硬件耗电量计算-小白菜博客
GIF