[Android Framework] 8.1 Battery系列(七) BatteryStatsHelper类和耗电量统计

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FightFightFight/article/details/82694381

概述

BatteryStatsHelper类用于负责计算各个应用和服务的电量使用情况,利用BatteryStatsService中统计的时长和电源配置文件中的配置值,通过计算得到耗电量信息供Application层使用。在Settings通过refreshStats()方法从BatteryStatsHelper中获取电池的数据。下面就BatteryStatsHelper从实例化到计算系统用电情况进行分析。

代码路径:
/frameworks/base/core/java/com/android/internal/os/BatteryStatsHelper.java

1.BatteryStatsHelper的初始化方法

要使用BatteryStatsHelper,必须获取其实例,并调用create()方法。因此这里看看其构造方法和create()方法,代码如下:


public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
    mContext = context;
    //是否需要注册BATTERY_CHANGED广播
    mCollectBatteryBroadcast = collectBatteryBroadcast;
    //平板等无移动网络,故该值为true
    mWifiOnly = wifiOnly;
    mPackageManager = context.getPackageManager();
    final Resources resources = context.getResources();
    //表示将数组中的包名作为系统进程对待
    mSystemPackageArray = resources.getStringArray(
            com.android.internal.R.array.config_batteryPackageTypeSystem);
    //表示将数组中的包名作为系统服务对待
    mServicepackageArray = resources.getStringArray(
            com.android.internal.R.array.config_batteryPackageTypeService);
}

create()方法:


public void create(BatteryStats stats) {
    mPowerProfile = new PowerProfile(mContext);
    mStats = stats;
}

public void create(Bundle icicle) {
    if (icicle != null) {
        mStats = sStatsXfer;
        mBatteryBroadcast = sBatteryBroadcastXfer;
    }
    mBatteryInfo = IBatteryStats.Stub.asInterface(
            ServiceManager.getService(BatteryStats.SERVICE_NAME));
    mPowerProfile = new PowerProfile(mContext);
}

构造方法比较简单。create()方法有两个重载,它们有个共同点:获取了PowerProfile对象,这个类在Battery系列三中已经分析过了,是电源配置文件。除此之外,还获得了BatteryStats对象。

1.1.refreshStats()方法

这个方法作为Application层刷新电池使用数据的接口,向上提供数据。电池统计的主要逻辑都是在这个方法中实现的,源代码如下:

public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
        long rawUptimeUs) {
    // Initialize mStats if necessary.
    //获取BatteryStatsImpl对象
    getStats();

    mMaxPower = 0;//最大耗电量
    mMaxRealPower = 0;//最大真实耗电量
    mComputedPower = 0;//通过耗电计算器计算的耗电量总和
    mTotalPower = 0;//总的耗电量

    //存储了BatterySipper的列表,各类耗电量都存储在BatterySipper中,BatterySipper又放在了mUsageList中
    mUsageList.clear();
    //在统计软件耗电过程中使用到wifi的应用对应的BatterySipper列表
    mWifiSippers.clear();
    //在统计软件耗电过程中使用到BT的应用对应的BatterySipper列表
    mBluetoothSippers.clear();
    //设备上有多个用户时,存储了其他用户的耗电信息的SparseArray数据,键为userId,值为对应的List<BatterySipper>
    mUserSippers.clear();
    //存储有数据接受和发送的BatterySipper对象的列表
    mMobilemsppList.clear();

    if (mStats == null) {
        return;
    }
    //初始化八个耗电量计算器,此处不做详细分析
    if (mCpuPowerCalculator == null) {
        mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
    }
    mCpuPowerCalculator.reset();

    if (mMemoryPowerCalculator == null) {
        mMemoryPowerCalculator = new MemoryPowerCalculator(mPowerProfile);
    }
    mMemoryPowerCalculator.reset();

    if (mWakelockPowerCalculator == null) {
        mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
    }
    mWakelockPowerCalculator.reset();

    if (mMobileRadioPowerCalculator == null) {
        mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
    }
    mMobileRadioPowerCalculator.reset(mStats);

    // checkHasWifiPowerReporting can change if we get energy data at a later point, so
    // always check this field.
    final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile);
    if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) {
        mWifiPowerCalculator = hasWifiPowerReporting ?
                new WifiPowerCalculator(mPowerProfile) :
                new WifiPowerEstimator(mPowerProfile);
        mHasWifiPowerReporting = hasWifiPowerReporting;
    }
    mWifiPowerCalculator.reset();

    final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats,
            mPowerProfile);
    if (mBluetoothPowerCalculator == null ||
            hasBluetoothPowerReporting != mHasBluetoothPowerReporting) {
        mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
        mHasBluetoothPowerReporting = hasBluetoothPowerReporting;
    }
    mBluetoothPowerCalculator.reset();

    if (mSensorPowerCalculator == null) {
        mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
                (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
    }
    mSensorPowerCalculator.reset();

    if (mCameraPowerCalculator == null) {
        mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile);
    }
    mCameraPowerCalculator.reset();

    if (mFlashlightPowerCalculator == null) {
        mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
    }
    mFlashlightPowerCalculator.reset();

    //统计类型,有三种:上次充满电后、上次充电后、当前
    mStatsType = statsType;
    //当前系统运行时间
    mRawUptimeUs = rawUptimeUs;
    //当前系统运行时间,包括休眠时间
    mRawRealtimeUs = rawRealtimeUs;
    //电池放电运行时间
    mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs);
    //电池放电运行时间,包括休眠时间
    mBatteryRealtimeUs = mStats.getBatteryRealtime(rawRealtimeUs);
    //对应类型的电池放电运行时间,如上次充满电后的电池运行时间
    mTypeBatteryUptimeUs = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
    //对应类型的电池放电运行时间,包括休眠时间
    mTypeBatteryRealtimeUs = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
    //电池预计使用时长
    mBatteryTimeRemainingUs = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
    //电池预计多久充满时长
    mChargeTimeRemainingUs = mStats.computeChargeTimeRemaining(rawRealtimeUs);

    //最低放电量近似值 = (最低放电等级近似值 * PowerProfile中配置电池容量) / 100,是BatteryStatsService中统计的最低放电量近似值
    mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
            * mPowerProfile.getBatteryCapacity()) / 100;
    //最高放电量近似值 = (最高放电等级近似值 * PowerProfile中配置电池容量) / 100,是BatteryStatsService中统计的最高放电量近似值
    mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
            * mPowerProfile.getBatteryCapacity()) / 100;

    //统计软件的耗电量,统计之后,会将每种类型、每个uid的耗电值存储在对应的BatterySipper中
    processAppUsage(asUsers);

    // Before aggregating apps in to users, collect all apps to sort by their ms per packet.
    //计算每个BatterySipper(每个应用)的每毫接收和发送的数据包mobilemspp
    for (int i = 0; i < mUsageList.size(); i++) {
        BatterySipper bs = mUsageList.get(i);
        bs.computeMobilemspp();
        //如果对应应用的mobilemspp!=0,则将该BatterySipper加入到mMobilemsppList列表中
        if (bs.mobilemspp != 0) {
            mMobilemsppList.add(bs);
        }
    }

    //遍历其他用户的耗电情况
    for (int i = 0; i < mUserSippers.size(); i++) {
        List<BatterySipper> user = mUserSippers.valueAt(i);
        //计算每个BatterySipper(每个应用)的每毫接收和发送的秒数据包(mobilemspp)
        for (int j = 0; j < user.size(); j++) {
            BatterySipper bs = user.get(j);
            bs.computeMobilemspp();
            if (bs.mobilemspp != 0) {
                mMobilemsppList.add(bs);
            }
        }
    }
    //以mobilemspp对mMobilemsppList进行排序
    Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
        @Override
        public int compare(BatterySipper lhs, BatterySipper rhs) {
            return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
        }
    });

    //统计硬件的耗电量
    processMiscUsage();

    //降序排序
    Collections.sort(mUsageList);

    // At this point, we've sorted the list so we are guaranteed the max values are at the top.
    // We have only added real powers so far.
    if (!mUsageList.isEmpty()) {
        //最大耗电电量
        mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
        final int usageListCount = mUsageList.size();
        for (int i = 0; i < usageListCount; i++) {
            //各个BatterySipper最大耗电量之和
            mComputedPower += mUsageList.get(i).totalPowerMah;
        }
    }

    mTotalPower = mComputedPower;
    //BatteryStatsService中统计的最低放电等级数近似值
    if (mStats.getLowDischargeAmountSinceCharge() > 1) {
        //如果最低放电量 > 计算的总耗电量,说明还有未计算的,这部分电量让DrainType.UNACCOUNTED类型的BatterySipper去存储
        if (mMinDrainedPower > mComputedPower) {
            //获取未计算耗电量
            double amount = mMinDrainedPower - mComputedPower;
            //将总的耗电量以mMinDrainedPower的值重置
            mTotalPower = mMinDrainedPower;
            //实例化一个DrainType.UNACCOUNTED类型的BatterySipper,用来存储未计算的耗电量
            BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);

            // Insert the BatterySipper in its sorted position.
            int index = Collections.binarySearch(mUsageList, bs);
            if (index < 0) {
                index = -(index + 1);
            }
            //添加到mUsageList中
            mUsageList.add(index, bs);
            mMaxPower = Math.max(mMaxPower, amount);
        //如果最高放电量 < 计算的总耗电量,说明多算了耗电量,这部分电量让DrainType.OVERCOUNTED类型的BatterySipper去存储
        } else if (mMaxDrainedPower < mComputedPower) {
            //获取多计算的耗电量
            double amount = mComputedPower - mMaxDrainedPower;

            // Insert the BatterySipper in its sorted position.
            //实例化一个DrainType.OVERCOUNTED类型的BatterySipper,用来存储未计算的耗电量
            BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
            int index = Collections.binarySearch(mUsageList, bs);
            if (index < 0) {
                index = -(index + 1);
            }
            //添加到mUsageList中
            mUsageList.add(index, bs);
            mMaxPower = Math.max(mMaxPower, amount);
        }
    }

    // Smear it!
    /**
     * 在统计列表mUsageList中标记不需要显示的统计,标记BatterySipper.shouldHide属性
     * 以及确定每个应用处于前台时的屏幕耗电量
     * 隐藏相关BatterySipper后,需要对为隐藏的BatterySipper设置"涂抹耗电量",具体来说就是将隐藏的这部分电量按比例
     * 添加到各个未隐藏的BatterySipper中的proportionalSmearMah中
     */
    //拿到需要隐藏的BatterySipper的全部总电量
    final double hiddenPowerMah = removeHiddenBatterySippers(mUsageList);
    //获取除隐藏耗电量之外的总的耗电量
    final double totalRemainingPower = getTotalPower() - hiddenPowerMah;
    if (Math.abs(totalRemainingPower) > 1e-3) {//1e-3=0.001
        //得到每个BatterySipper的proportionalSmearMah值
        for (int i = 0, size = mUsageList.size(); i < size; i++) {
            final BatterySipper sipper = mUsageList.get(i);
            if (!sipper.shouldHide) {
                //隐藏耗电量 = 总隐藏耗电量 × (该BatterySipper中记录的耗电量/剩余总的耗电量)
                sipper.proportionalSmearMah = hiddenPowerMah
                        * ((sipper.totalPowerMah + sipper.screenPowerMah)
                        / totalRemainingPower);
                sipper.sumPower();
            }
        }
    }
}

这个方法比较庞大,先来看看其参数:

  • Int statsType:表示需要统计的电池信息类型。有三类:
/*
*所有的电池数据,包括之前的统计数据
*/
public static final int STATS_SINCE_CHARGED = 0;
/*
*只包括当前运行时统计的数据
*/
public static final int STATS_CURRENT = 1;
/*
*包括设备最后一次充电后统计的数据
*/
public static final int STATS_SINCE_UNPLUGGED = 2;
  • SparseArray<UserHandle> asUsers:UserHanler代表设备上的一个用户;
  • long rawRealtimeUs:系统开机后的运行时间,即SystemClock.elapsedRealtime;
  • long rawUptimeUs:系统不包括睡眠的运行时间,即SystemClock.uptimeMillis。

在这个方法中,大部分分析都在注释中,也容易理解。其中关于几个耗电量计算器的逻辑计划先不进行分析,只需要知道,最终计算的值存在了BatterySipper对象中即可。这里主要内容是refreshStats()方法中耗电计算器计算之后的逻辑。

在分析接下来的任务之前,需要分析下在refreshStats()中调用BatteryStatsImpl的几个值的获取。因为这和BatteryStatsImpl中一些逻辑有关,而BatteryStatsImpl是一个非常复杂而且庞大的一个类,要搞情楚BatteryStatsImpl中的逻辑,就需要利用这个特定的场景,一口一口地啃。

1.2.BatteryStatsImpl.computeBatteryUptime()

该方法用来返回总的、最后充满电后的或当前的电池正常运行时间:

@Override
public long computeBatteryUptime(long curTime, int which) {
    return mOnBatteryTimeBase.computeUptime(curTime, which);
}

在这个方法中,mOnBatteryTimeBase是TimeBase的一个实例,专门用于在电池没有插入充电的情况下做一些统计时间的工作。TimeBase的属性值如下:

public static class TimeBase {
    //用来存储观察者
    protected final ArrayList<TimeBaseObs> mObservers = new ArrayList<>();

    protected long mUptime;
    protected long mRealtime;

    protected boolean mRunning;//是否正在运行,当拔掉充电器后,开始运行

    protected long mPastUptime;//表示放电情况下度过了多长时间,只有在由放电->充电时才会累加更新
    protected long mUptimeStart;//TimeBase调用setRunning()开始运行时时间,表示开始放电时的时间
    protected long mPastRealtime;
    protected long mRealtimeStart;//TimeBase调用setRunning()开始运行时时间
    protected long mUnpluggedUptime;//一般情况下等于mPastUptime的值,表示刚拔掉充电线时的放电累加时间
    protected long mUnpluggedRealtime;//一般情况下等于mPastRealtime的值

以上这些属性值所代表含义,通过具体的场景分析可得到,而在分析推导的过程中,需要了解TimeBase.setRunning()方法,因为其中几个属性就是在这个方法中赋值的。

1.3.TimeBase.setRunning()

该方法如下:

public boolean setRunning(boolean running, long uptime, long realtime) {
    //running表示是否没有插入充电器,只有running发生改变时才进入该方法
    if (mRunning != running) {
        mRunning = running;
        //说明拔掉充电器
        if (running) {
            //放电开始时间
            mUptimeStart = uptime;
            mRealtimeStart = realtime;
            //所有放电累计时间
            long batteryUptime = mUnpluggedUptime = getUptime(uptime);
            long batteryRealtime = mUnpluggedRealtime = getRealtime(realtime);
            //通知各位观察者,主题发生改变
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onTimeStarted(realtime, batteryUptime, batteryRealtime);
            }
        } else {//说明插入了充电线
            //累加放电时间,在原来基础上+当前时间-放电开始时间
            mPastUptime += uptime - mUptimeStart;
            mPastRealtime += realtime - mRealtimeStart;
            //得到所有放电累计时间
            long batteryUptime = getUptime(uptime);
            long batteryRealtime = getRealtime(realtime);
            //通知各位观察者,主题发生改变
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onTimeStopped(realtime, batteryUptime, batteryRealtime);
            }
        }
        return true;
    }
    return false;
}

通过对setRunning()方法的分析,大致可以理解这些属性值的意义,再根据下面的分析,应该能够有更深入的理解。

再来看computeBatteryUptime()方法中使用到的TimeBase.computeUptime()方法:


public long computeUptime(long curTime, int which) {
    switch (which) {
        //自从上次充满电的时间
        case STATS_SINCE_CHARGED:
            //mUptime + 放电累加时间
            return mUptime + getUptime(curTime);
        //当前的运行时间
        case STATS_CURRENT:
            //放电累加时间
            return getUptime(curTime);
        //自从上次拔掉充电器的运行时间
        case STATS_SINCE_UNPLUGGED:
            //放电累加时间 - 刚拔掉充电线时的放电累计时间
            return getUptime(curTime) - mUnpluggedUptime;
    }
    return 0;
}

其中的getUptime()方法如下:

public long getUptime(long curTime) {
    //放电累计时间,只有在由放点进入充电时会更新该值
    long time = mPastUptime;
    if (mRunning) {//如果此时还在放电,则mRunning=true
        time += curTime - mUptimeStart;//再加上当前时间-放电开始时间这段值
    }
    return time;
}

1.4.BatteryStatsImpl.computeBatteryRealtime()

这个方法用来返回总的、最后充满电后的或当前的电池正常运行时间,包括休眠时间。其原理和computeBatteryRealtime()方法一样,这里不再说明,相关代码如下:

@Override
public long computeBatteryRealtime(long curTime, int which) {
    return mOnBatteryTimeBase.computeRealtime(curTime, which);
}

mOnBatteryTimeBase.computeRealtime():

public long computeRealtime(long curTime, int which) {
    switch (which) {
        case STATS_SINCE_CHARGED:
            //getRealtime()得到放电累计真实时间(包括休眠时)
            return mRealtime + getRealtime(curTime);
        case STATS_CURRENT:
            return getRealtime(curTime);
        case STATS_SINCE_UNPLUGGED:
            return getRealtime(curTime) - mUnpluggedRealtime;
    }
    return 0;
}

其中的getRealtime()方法如下:

public long getRealtime(long curTime) {
    //放电累计时间,包括休眠时间
    long time = mPastRealtime;
    if (mRunning) {
        //如果处于放电,则mRunning=true
        //还需要加上当前时间-放电开始这段时间
        time += curTime - mRealtimeStart;
    }
    return time;
}

现在回到BatteryStatsHelper的refreshStats()中,进入到核心内容,也就是耗电量的计算部分。关于八个耗电统计计算器的实例化,这里暂且不分析,在之后的内容中会独立分析。

统计耗电量分为两部分:软件耗电和硬件耗电,分别用processAppUsage()方法和processMiscUsage()进行统计,下面分别来看其如何统计的。

1.5.processAppUsage(asUsers)

processAppUsage(asUsers)方法用来统计软件耗电量。在进行统计时,是以uid为单位进行统计,Android中的Uid是在应用安装时由系统分配的,每个应用对应一个uid,不过通过清单文件中的shareUserId可以指定两个应用的Uid相同。

在统计耗电量时,每个Uid,都会有一个对应的BatterySipper对象,八大计算器计算后的数据将会保存在BatterySipper对象中,下面详细看这个方法是如何实现的:

private void processAppUsage(SparseArray<UserHandle> asUsers) {
    //是否是所有用户,默认false
    final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
    //电池正常运行时间(放电时间)
    mStatsPeriod = mTypeBatteryRealtimeUs;

    BatterySipper osSipper = null;
    //获取BatteryStatsService中统计的Uid的SparseArray
    final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
    final int NU = uidStats.size();
    //遍历每个uid
    for (int iu = 0; iu < NU; iu++) {
        final Uid u = uidStats.valueAt(iu);
        //实例化BatterySipper对象,该对象用来存储统计信息,第一个参数表示统计类型为应用耗电
        final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
        //计算每个uid的cpu耗电量
        mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //计算每个uid的wakelock耗电量
        mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //计算每个uid的无线电耗电量
        mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
                mStatsType);
        //计算每个uid的wifi耗电量
        mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //计算每个uid的蓝牙耗电量
        mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
                mStatsType);
        //计算每个uid的传感器耗电量
        mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //计算每个uid的相机耗电量
        mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //计算每个uid的闪光灯耗电量
        mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
                mStatsType);
        //通过BatterySipper得到总的耗电量
        final double totalPower = app.sumPower();

        // Add the app to the list if it is consuming power.
        //如果统计电量大于零或者为root用户,u.getUid()获取对应app的进程id
        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请求者(uid为1010),则将该BatterySipper添加到统计Wifi耗电量的列表List<BatterySipper>中
            if (uid == Process.WIFI_UID) {
                mWifiSippers.add(app);
            //如果是BT服务进程(uid为1002),则将该BatterySipper添加到统计蓝牙耗电量的列表List<BatterySipper>中
            } else if (uid == Process.BLUETOOTH_UID) {
                mBluetoothSippers.add(app);
            //forAllUsers为false,asUsers.get(userId)== null表示没有统计对应的userId的耗电量,
            //因此将BatterySipper添加到表示其他用户的List中,然后将List存储到SparseArray中,以userId为key
            } 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);
            } else {
                //将BatterySipper对象添加到列表中
                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, mRawRealtimeUs,
                mRawUptimeUs, mStatsType);
        osSipper.sumPower();
    }
}

详细的解释都在方法注释中了,接下来看看统计硬件耗电量的方法。

1.6.processMiscUsage()方法

processMiscUsage()方法用来统计硬件耗电量,在这个方法中,会分别统计多个类型的耗电量,该方法如下:

private void processMiscUsage() {
    //累加所有的其他用户的耗电情况,统计到DrainType.USER类型的BatterySipper中
    addUserUsage();
    //累加所有通话耗电情况,统计到DrainType.PHONE类型的BatterySipper中
    addPhoneUsage();
    //累加所有亮屏的耗电情况,统计到DrainType.SCREEN类型的BatterySipper中
    addScreenUsage();
    //累加所有WIFI的耗电情况,统计到DrainType.WIFI类型的BatterySipper中
    addWiFiUsage();
    //累加所有BT的耗电情况,统计到DrainType.BLUETOOTH类型的BatterySipper中
    addBluetoothUsage();
    //累加所有DDR的耗电情况,统计到DrainType.MEMORY类型的BatterySipper中
    addMemoryUsage();
    //累加所有待机时的耗电情况,统计到DrainType.IDLE类型的BatterySipper中
    addIdleUsage(); // Not including cellular idle power
    // Don't compute radio usage if it's a wifi-only device
    //对于平板设备来说,不支持GMS网络,因此mWifiOnly为ture
    if (!mWifiOnly) {
        //累加所有移动网络(radio)的耗电情况,统计到DrainType.CELL类型的BatterySipper中
        addRadioUsage();
    }
}

我们一个个看这些方法:

1.6.1.addUserUsage()

private void addUserUsage() {
    //遍历SparseArray<List<BatterySipper>>,其key为userId,值为List<BatterySipper>
    for (int i = 0; i < mUserSippers.size(); i++) {
        //得到每一个userId
        final int userId = mUserSippers.keyAt(i);
        //实例化一个表示统计其他用户耗电量的BatterySipper
        BatterySipper bs = new BatterySipper(DrainType.USER, null, 0);
        bs.userId = userId;
        //收集"其他用户"所有的BatterySipper中的耗电量到bs中,如果添加了用户的话,就会有其他用户
        aggregateSippers(bs, mUserSippers.valueAt(i), "User");
        //添加到集合
        mUsageList.add(bs);
    }
}

1.6.2.addPhoneUsage()

private void addPhoneUsage() {
    //获取BatteryStatsService中统计的通话总时长
    long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtimeUs, 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);
    }
}

1.6.3.addScreenUsage()

private void addScreenUsage() {
    double power = 0;
    //获取BatteryStatsService中统计的屏幕亮屏时间
    long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtimeUs, mStatsType) / 1000;
    //总消耗电量 = 亮屏时间 * 最低亮度时每毫秒所消耗电量
    power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
    //获取屏幕以最高亮度打开时每毫秒消耗的电量
    final double screenFullPower =
            mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
    //遍历?
    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;
        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
    if (power != 0) {
        //实例化一个DrainTyep为SCREEN类型的BatterySipper,并将screenOnTimeMs、power进行赋值给BatterySipper
        addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
    }
}

这个方法中一部分逻辑还有待研究,做个标记。

1.6.4.addWiFiUsage()

private void addWiFiUsage() {
    //实例化一个DrainType.WIFI的BatterySipper,用来存储wifi耗电量
    BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
    //通过WIFI耗电量计算器计算可能无法归因于应用的耗电量
    mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
            mStatsType);
    //收集统计app耗电时获取的wifi耗电量和剩余wifi耗电量到bs中
    aggregateSippers(bs, mWifiSippers, "WIFI");
    //将bs添加到mUsageList中
    if (bs.totalPowerMah > 0) {
        mUsageList.add(bs);
    }
}

1.6.5.addBluetoothUsage()

private void addBluetoothUsage() {
    //实例化DrainTyep.BLEUTOOTH类型的BatterySipper,用于存储BT耗电量
    BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
    //通过BT耗电量计算器计算可能无法归因于应用的耗电量
    mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
            mStatsType);
    //收集统计app耗电时获取的BT耗电量和剩余BT耗电量到bs中
    aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
    //将bs添加到mUsageList中
    if (bs.totalPowerMah > 0) {
        mUsageList.add(bs);
    }
}

1.6.6.addMemoryUsage()

private void addMemoryUsage() {
    //实例化DrainTyep.MEMORY类型的BatterySipper,用于存储DDR耗电量
    BatterySipper memory = new BatterySipper(DrainType.MEMORY, null, 0);
    //通过DDR耗电量计算器计算耗电量
    mMemoryPowerCalculator.calculateRemaining(memory, mStats, mRawRealtimeUs, mRawUptimeUs,
            mStatsType);
    //得到DDR耗电总和
    memory.sumPower();
    //添加到集合
    if (memory.totalPowerMah > 0) {
        mUsageList.add(memory);
    }
}

1.6.7.addIdleUsage()

private void addIdleUsage() {
    //获取mPowerProfile中cpu处于空闲状态时的耗电量
    final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) *
            mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
    //获取mPowerProfile中cpu处于活动状态时的耗电量
    final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) *
            mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);
    //得到cpu总的耗电量
    final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);

    if (totalPowerMah != 0) {
        //实例化一个DrainTyep.IDLE类型的BatterySipper,并将mTypeBatteryRealtimeUs / 1000、totalPowerMah进行赋值给BatterySipper
        addEntry(BatterySipper.DrainType.IDLE, mTypeBatteryRealtimeUs / 1000, totalPowerMah);
    }
}

1.6.8.addRadioUsage()

private void addRadioUsage() {
    //实例化一个DrainType.CELL的BatterySipper,用来存储无线电耗电量
    BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
    //通过无线电耗电量计算器计算无法归因于应用的耗电量
    mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtimeUs, mRawUptimeUs,
            mStatsType);
    //得到无线电耗电量总和
    radio.sumPower();
    //添加到mUsageList中
    if (radio.totalPowerMah > 0) {
        mUsageList.add(radio);
    }
}

当统计完各个应用和硬件的耗电量之后,让我们再回到refreshStats()中,看其最后两部分的逻辑。

第一部分是从”mStats.getLowDischargeAmountSinceCharge()“开始的这个if语句。
在这里,它会将统计的所有的app和硬件的耗电量总和和BatteryStatsService中记录的放电量近似值做对比,如果通过耗电量计算器计算的总耗电量小于BatteryStatsService中的最低放电近似值,则说明还有未记录的耗电量,将这部分耗电量记录在BatterySipper.DrainType.UNACCOUNTED类型的BatterySipper中。同理,如果通过耗电量计算器计算的总耗电量大于BatteryStatsService中的最高放电近似值,则说明还多计算了耗电量,将这部分多计算的耗电量记录在BatterySipper.DrainType.OVERCOUNTED类型的BatterySipper中。

第二部分是从调用removeHiddenBatterySippers()开始。拿到所有的耗电量后,有一部分类型的BatterySipper是需要隐藏的,因此通过removeHiddenBatterySippers()方法进行隐藏,而隐藏后,这些BatterySipper的耗电量则会按照比例分摊在其他未隐藏的BatterySipper上。下面我们来看看这个方法。

1.7.removeHiddenBatterySippers()

public double removeHiddenBatterySippers(List<BatterySipper> sippers) {
    double proportionalSmearPowerMah = 0;
    BatterySipper screenSipper = null;
    for (int i = sippers.size() - 1; i >= 0; i--) {
        final BatterySipper sipper = sippers.get(i);
        //是否需要隐藏这个BatterySipper
        sipper.shouldHide = shouldHideSipper(sipper);
        if (sipper.shouldHide) {
            if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED
                    && sipper.drainType != BatterySipper.DrainType.SCREEN
                    && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED
                    && sipper.drainType != BatterySipper.DrainType.BLUETOOTH
                    && sipper.drainType != BatterySipper.DrainType.WIFI
                    && sipper.drainType != BatterySipper.DrainType.IDLE) {
                // Don't add it if it is overcounted, unaccounted or screen
                //对于需要隐藏的BatterySipper,除以上条件之外的BatterySipper需要累计下对应电量
                proportionalSmearPowerMah += sipper.totalPowerMah;
            }
        }
        //获取统计亮屏耗电量的BatterySipper
        if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
            screenSipper = sipper;
        }
    }
    //根据每个应用进程前台活动时间,获取作为每个应用处于前台时的屏幕耗电量
    smearScreenBatterySipper(sippers, screenSipper);

    return proportionalSmearPowerMah;
}

在这个方法中,有两个作用:

  • 1.标记需要隐藏的BatterySipper,并统计隐藏的BatterySipper的耗电量总和;
  • 2.根据每个应用进程前台活动时间,获取每个应用处于前台时的屏幕耗电量。

其中第一点已经说过了,有些耗电量的BatterySipper可以进行隐藏,例如在Settings中,并没有将所有的统计的BatterySipper都列出来,而是将有的隐藏掉了。

再来说第二点,因为在之前统计屏幕耗电量时,统计的是屏幕亮屏时的耗电量,并没有单独对某一个应用在前台时屏幕耗电量进行统计,因此这里通过获取应用在前台的时间,得到每个应用对屏幕的耗电量,具体计算公式为:

单个应用对屏幕的耗电量 = 总的屏幕亮屏耗电量 * 单个应用处于前台的时长 / 所有应用处于前台的总时长。

我们看对应计算方法smearScreenBatterySipper()

public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
    long totalActivityTimeMs = 0;
    final SparseLongArray activityTimeArray = new SparseLongArray();
    for (int i = 0, size = sippers.size(); i < size; i++) {
        final BatteryStats.Uid uid = sippers.get(i).uidObj;
        if (uid != null) {
            //获取对应进程前台运行时间
            final long timeMs = getProcessForegroundTimeMs(uid,
                    BatteryStats.STATS_SINCE_CHARGED);
            //以<uid,timems>的形式进行存储
            activityTimeArray.put(uid.getUid(), timeMs);
            //累计所有的应用进程前台运行时间
            totalActivityTimeMs += timeMs;
        }
    }

    //如果所有应用的前台运行时间 >= 10分钟
    if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
        //得到DrainType.SCREEN的统计耗电量
        final double screenPowerMah = screenSipper.totalPowerMah;
        for (int i = 0, size = sippers.size(); i < size; i++) {
            final BatterySipper sipper = sippers.get(i);
            //每个BatterySipper的屏幕耗电量 = DrainType.SCREEN类BatterySipper中的总电量 * 对应前台时间/总时间
            sipper.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0)
                    / totalActivityTimeMs;
        }
    }
}

分析到这里,关于refreshStats()方法中的大部分流程就完毕了。接下来简单看看BatterySipper类。

2.BatterySipper类

这个类在前面的计算中数次用到,但是并没有进行说明,这里对它进行下说明。
BatterySipper是一个用来存放应用、系统服务、硬件的耗电量的一个类,它定义了多个表示电量相关的属性,其中还定义了一个枚举类,下面我们简单看下其内部逻辑。

2.1.构造方法

BatterySipper类构造方法如下:

public BatterySipper(DrainType drainType, Uid uid, double value) {
    //总的耗电量,计算耗电量初始化时传入0
    this.totalPowerMah = value;
    //耗电类型
    this.drainType = drainType;
    //Uid对象
    uidObj = uid;
}

2.2.BatterySipper.DrainType

DrainType是一个枚举类,定义了实例化BatterySipper时可选的类型:

public enum DrainType {
    IDLE,//用于实例化统计待机耗电量的BatterySipper
    CELL,//无线电
    PHONE,//通话
    WIFI,//WIFI
    BLUETOOTH,
    FLASHLIGHT,
    SCREEN,
    APP,//应用耗电
    USER,//用于实例化其他用户的耗电量的BatterySipper,Android支持多用户
    UNACCOUNTED,//未计算的
    OVERCOUNTED,//多算了的
    CAMERA,//相机
    MEMORY//DDR
}

2.3.sumPower()

这个方法用来统计一个BatterySipper的总耗电量:

public double sumPower() {
    //总的耗电量为所有耗电量计算器得到的耗电量之和
    totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
            sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
            flashlightPowerMah + bluetoothPowerMah;
    //"涂抹"后的总耗电量,一些类型的BatterySipper需要隐藏,因此将这些隐藏BatterySipper的耗电量按比例分给其他BatterySipper
    totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;

    return totalPowerMah;
}

其他方法不再说明了,使用的也比较少,其中还有一个add()方法,这个方法我们在分析每个耗电量计算器时在说明。

3.总结

对于BatteryStatsHelper中耗电量统计已经进行了完整的分析,现在来对耗电量统计做个比较清晰的总结。

在Settings中的耗电量统计,就是来自于BatteryStatsHelper中的数据,在BatteryStatsHelper中,refreshStats()方法用来更新耗电量统计,每调用该方法一次,都会获取新的耗电量。

统计耗电量时,以uid为单位(Android中每个应用都有唯一uid),通过八个PowerCalculator对象计算每个应用分别使用对应资源(如cpu、wakelock)的耗电量,然后存储到对应的BatterySipper对象中。

在BatterySipper中,定义了多个耗电量类型,如屏幕耗电、wifi耗电、……,在统计耗电量过程中,针对一个类型,实例化一个BatterySipper对象,如统计WIFI耗电量,会实例化一个BatterySipper.DrainType.WIFI类型的BatterySipper,然后将WIFI的耗电量存储到它中。最终,将每个BatterySipper加入到List中(mUsageList)。

Settings中也是通过拿到这个mUsageList,从而从BatterySipper中解析出所需的耗电量。

通过八个耗电量计算器得到总的耗电量后,这个耗电量并不是最终值,在BatteryStatsService中,也对耗电量做了一个记录:最低放电量近似值和最高放电量近似值(mLowDischargeAmountSinceChargemHighDischargeAmountSinceCharge),每放一个电,该值累计加1。这俩值会和计算后的值进行对比,如果计算的耗电量 < 最低放电近似值,则说明存在未计算的耗电量,因此会将这部分未计算的耗电量存储到BatterySipper.DrainType.UNACCOUNTED类型的BatterySipper中;如果计算的耗电量 > 最高放电量,则说明多算了耗电量,因此会将这部分多算的耗电量存储到BatterySipper.DrainType.OVERCOUNTED类型的BatterySipper中。
有时候一些设备在进入Settings后,耗电信息中出现“多算了的”或者“少算了的”项,就是由于这个原因。

猜你喜欢

转载自blog.csdn.net/FightFightFight/article/details/82694381