Android中App耗电量统计核心函数注解

    本文针对Android 5.0 以及以版本,对APP耗电量统计的核心函数,processAppUsage 进行解读,代码中增加了大量注释以及笔者个人的理解。

    

private void processAppUsage(SparseArray<UserHandle> asUsers) {
        final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
        SensorManager sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);

        final int which = mStatsType;
        final int speedSteps = mPowerProfile.getNumSpeedSteps();            //获取CPU可以运转到在几种频率之下
        final double[] powerCpuNormal = new double[speedSteps];
        final long[] cpuSpeedStepTimes = new long[speedSteps];

        /**
         * 根据几种不同的频率,获取每个频率的平均电流值
         */
        for (int p = 0; p < speedSteps; p++) {
            powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
        }

        final double mobilePowerPerPacket = getMobilePowerPerPacket();        //移动数据流量功耗
        final double mobilePowerPerMs = getMobilePowerPerMs();                //数据连通网络时候的功耗
        final double wifiPowerPerPacket = getWifiPowerPerPacket();            //Wifi传输功耗

        long appWakelockTimeUs = 0;
        BatterySipper osApp = null;
        mStatsPeriod = mTypeBatteryRealtime;
        /**
         * SparseArray代替HashMap,效率更高,SpareArray用的是稀疏矩阵的方式
         */
        SparseArray<? extends Uid> uidStats = mStats.getUidStats();           //获取每一个uid的数据

        final int NU = uidStats.size();

        for (int iu = 0; iu < NU; iu++) {
            Uid u = uidStats.valueAt(iu);
            double p;                                                           // in mAs
            double power = 0;                                                   // in mAs
            double highestDrain = 0;
            String packageWithHighestDrain = null;

            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();

            long cpuTime = 0;
            long cpuFgTime = 0;
            long wakelockTime = 0;
            long gpsTime = 0;

            if (processStats.size() > 0) {
                // Process CPU time
                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
                        : processStats.entrySet()) {

                    Uid.Proc ps = ent.getValue();
                    /**
                     * 分别获取该进程,执行User的代码、执行系统级别的代码,以及在前段运行的时间
                     */
                    final long userTime = ps.getUserTime(which);
                    final long systemTime = ps.getSystemTime(which);
                    final long foregroundTime = ps.getForegroundTime(which);

                    cpuFgTime += foregroundTime * 10;                           // convert to millis,转化为毫秒

                    final long tmpCpuTime = (userTime + systemTime) * 10;       // convert to millis

                    int totalTimeAtSpeeds = 0;
                    /**
                     * 根据CPU所可以运行的几种频率,得到每种不同的频率下的改进程所消耗的时间
                     */
                    for (int step = 0; step < speedSteps; step++) {
                        cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
                        totalTimeAtSpeeds += cpuSpeedStepTimes[step];
                    }
                    if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
                    /**
                     * 计算出每个CPU频率所消耗的时间,占总消耗时间的百分比,为什么要计算百分比?
                     */
                    double processPower = 0;
                    for (int step = 0; step < speedSteps; step++) {
                        double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
                        if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
                                + step + " ratio=" + makemAh(ratio) + " power="
                                + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));

                        processPower += ratio * tmpCpuTime * powerCpuNormal[step];
                    }

                    cpuTime += tmpCpuTime;                                                  //CPU时间

                    if (DEBUG && processPower != 0) {
                        Log.d(TAG, String.format("process %s, cpu power=%s",
                                ent.getKey(), makemAh(processPower / (60*60*1000))));
                    }
                    power += processPower;                                                  //总电量消耗加上该uid进程的消耗

                    if (packageWithHighestDrain == null
                            || packageWithHighestDrain.startsWith("*")) {
                        highestDrain = processPower;
                        packageWithHighestDrain = ent.getKey();
                    } else if (highestDrain < processPower
                            && !ent.getKey().startsWith("*")) {
                        highestDrain = processPower;
                        packageWithHighestDrain = ent.getKey();
                    }
                }
            }
            if (cpuFgTime > cpuTime) {
                if (DEBUG && cpuFgTime > cpuTime + 10000) {
                    Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
                }
                cpuTime = cpuFgTime;                            // Statistics may not have been gathered yet.数据还没有聚合??
            }
            /**
             * //该进程CPU所消耗的时间统计结束
             * 这里 60*60*1000 是将能量转化为 毫安时,得到的结果是 毫秒*毫安
             */
            power /= (60*60*1000);

            /**
             * // Process wake lock usage
             * 该进程所持有的Wakelock而消耗的电量,一个进程可能持有多个Wakelock
             */
            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
            for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
                    : wakelockStats.entrySet()) {
                Uid.Wakelock wakelock = wakelockEntry.getValue();
                // Only care about partial wake locks since full wake locks
                // are canceled when the user turns the screen off.
                /**
                 * 这里只关心Patrial的Wakelock,因为 fullwakelock 在屏幕灭的时候 已经被取消
                 * 通过该循环,可以得到该进程所持有的所有Wakelock的时间
                 */
                BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
                if (timer != null) {
                    wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which);
                }
            }
            appWakelockTimeUs += wakelockTime;                      //得到的结果是微秒的形式
            wakelockTime /= 1000;                                   // convert to millis,转化为毫秒

            // Add cost of holding a wake lock
            /**
             * 计算得到持有Wakelock所消耗的电量,电流值为CPU醒着时候的电流值
             */
            p = (wakelockTime
                    * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);
            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "
                    + wakelockTime + " power=" + makemAh(p));

            power += p;

            // Add cost of mobile traffic
            /**
             * 计算手机的数据流量,即使用 3G/4G 网络的流量数量
             */
            final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
            final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
            final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
            final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);

            /**
             * 手机连通网络的时间
             */
            final long mobileActive = u.getMobileRadioActiveTime(mStatsType);

            if (mobileActive > 0) {
                // We are tracking when the radio is up, so can use the active time to
                // determine power use.
                /**
                 * 当监控的时候,网络已经连通,所以可以直接用网络连通的时间,来确定功耗值
                 */
                mAppMobileActive += mobileActive;
                p = (mobilePowerPerMs * mobileActive) / 1000;
            } else {
                // We are not tracking when the radio is up, so must approximate power use
                // based on the number of packets.
                /**
                 * 监控的时候,手机网络没有连通,那么就用流量值*流量电流 来估算出功耗
                 */
                p = (mobileRx + mobileTx) * mobilePowerPerPacket;
            }
            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
                    + (mobileRx+mobileTx) + " active time " + mobileActive
                    + " power=" + makemAh(p));

            power += p;

            // Add cost of wifi traffic
            /**
             * WIFI的数据流量功耗,与计算手机数据流量的方式类似
             */
            final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
            final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
            final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
            final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);

            p = (wifiRx + wifiTx) * wifiPowerPerPacket;

            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
                    + (mobileRx+mobileTx) + " power=" + makemAh(p));

            power += p;

            // Add cost of keeping WIFI running.
            /**
             * WIFI打开时候的功耗,打开WIFI,WIFI并没有其他运作
             */
            long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000;
            mAppWifiRunning += wifiRunningTimeMs;
            p = (wifiRunningTimeMs
                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "
                    + wifiRunningTimeMs + " power=" + makemAh(p));

            power += p;

            // Add cost of WIFI scans
            /**
             * WIFI扫描时候的功耗
             */
            long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
            p = (wifiScanTimeMs
                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);
            if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
                    + " power=" + makemAh(p));
            power += p;

            /**
             * WIFI 批量扫描模式所造成的功耗
             */
            for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
                long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
                p = ((batchScanTimeMs
                        * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
                    ) / (60*60*1000);
                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
                        + " time=" + batchScanTimeMs + " power=" + makemAh(p));
                power += p;
            }

            // Process Sensor usage
            /**
             * 进程使用传感器造成的功耗
             * 这里用SpareArray来存储
             */
            SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
            int NSE = sensorStats.size();

            for (int ise=0; ise<NSE; ise++) {
                Uid.Sensor sensor = sensorStats.valueAt(ise);                   //获取Sensor类型所对应的实体
                int sensorHandle = sensorStats.keyAt(ise);                      //获取Sensor的类型

                /**
                 * 该Sensor所消耗的时间
                 */
                BatteryStats.Timer timer = sensor.getSensorTime();
                long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;

                /**
                 * 对GPS区别对待,貌似GPS的平均电流值,可以在PowerProfile中得到,而其他Sensor的平均
                 * 电流值,通过SensorManager获取,每个Sensor的型号功能不同,其电流值应该是做在驱动层
                 */
                double multiplier = 0;
                switch (sensorHandle) {
                    case Uid.Sensor.GPS:
                        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
                        gpsTime = sensorTime;
                        break;
                    default:
                        List<Sensor> sensorList = sensorManager.getSensorList(
                                android.hardware.Sensor.TYPE_ALL);
                        for (android.hardware.Sensor s : sensorList) {
                            if (s.getHandle() == sensorHandle) {
                                multiplier = s.getPower();
                                break;
                            }
                        }
                }
                p = (multiplier * sensorTime) / (60*60*1000);
                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
                        + " time=" + sensorTime + " power=" + makemAh(p));
                power += p;
            }

            if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",
                    u.getUid(), makemAh(power)));

            // Add the app to the list if it is consuming power
            /**
             * 如果该应用消耗的电量,就把它增加到列表中
             */
            final int userId = UserHandle.getUserId(u.getUid());

            if (power != 0 || u.getUid() == 0) {
                BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,
                        new double[] {power});
                app.cpuTime = cpuTime;
                app.gpsTime = gpsTime;
                app.wifiRunningTime = wifiRunningTimeMs;
                app.cpuFgTime = cpuFgTime;
                app.wakeLockTime = wakelockTime;
                app.mobileRxPackets = mobileRx;
                app.mobileTxPackets = mobileTx;
                app.mobileActive = mobileActive / 1000;
                app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
                app.wifiRxPackets = wifiRx;
                app.wifiTxPackets = wifiTx;
                app.mobileRxBytes = mobileRxB;
                app.mobileTxBytes = mobileTxB;
                app.wifiRxBytes = wifiRxB;
                app.wifiTxBytes = wifiTxB;
                app.packageWithHighestDrain = packageWithHighestDrain;

                /**
                 * 对 WIFI 支持进程 和 蓝牙服务进程 区别对待,加入相应的列表中
                 */
                if (u.getUid() == Process.WIFI_UID) {                       //wifi 支持进程
                    mWifiSippers.add(app);
                    mWifiPower += power;
                } else if (u.getUid() == Process.BLUETOOTH_UID) {           //蓝牙服务进程
                    mBluetoothSippers.add(app);
                    mBluetoothPower += power;
                }
                /**
                 * Android 5.0 以后是多用户系统,这里要对用户做一些判别操作
                 */
                else if (!forAllUsers && asUsers.get(userId) == null
                        && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
                    List<BatterySipper> list = mUserSippers.get(userId);
                    /**
                     * 不同用户,拥有不同的app列表,要根据userId获取到应用列表,如果没有列表则新建,
                     * 然后在此用户下的应用列表中添加app
                     */
                    if (list == null) {
                        list = new ArrayList<BatterySipper>();
                        mUserSippers.put(userId, list);
                    }

                    list.add(app);

                    /**
                     *  在对不同用户的所在的app消耗的功耗进行统计
                     */
                    if (power != 0) {
                        Double userPower = mUserPower.get(userId);
                        if (userPower == null) {
                            userPower = power;
                        } else {
                            userPower += power;
                        }
                        mUserPower.put(userId, userPower);
                    }
                }
                /**
                 * 不区分用户的情况,则直接相加
                 */
                else {
                    mUsageList.add(app);
                    if (power > mMaxPower) mMaxPower = power;
                    if (power > mMaxRealPower) mMaxRealPower = power;
                    mComputedPower += power;
                }
                if (u.getUid() == 0) {
                    osApp = app;                        //Android 的系统 app
                }
            }
        }

        // 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.
        /**
         * 设备在灭屏之后,可能也依旧处于唤醒状态,所以设备的实际唤醒时间要比屏幕亮着的总体时间要长
         * 这个原因就是Wakelock引起的,笔者根据代码理解,应该是将释放Wakelock的时间归结为系统所消耗
         * 的时间,这部分时间算在系统头上
         *
         */
        if (osApp != null) {
            long wakeTimeMillis = mBatteryUptime / 1000;
            /**
             * wakeTimeMillis的值查阅源代码的 英文翻译过来是 当前电池正在运行的时间
             * 电池所运行的时间,减去应用占据Wakelock的时间 再减去屏幕亮起的时间
             * 剩下的时间,就是要额外计算的,就是不算app所持有的Wakelock时间,且发生在灭屏后的,那
             * 应该就是 释放Wakelock 所消耗的时间,这里是笔者的推测
             */
            wakeTimeMillis -= (appWakelockTimeUs / 1000)
                    + (mStats.getScreenOnTime(mRawRealtime, which) / 1000);

            //如果存在这样的时间
            if (wakeTimeMillis > 0) {
                double power = (wakeTimeMillis
                        * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))
                        /  (60*60*1000);

                if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
                        + makemAh(power));

                /**
                 * 补偿进去每一项的值
                 */
                osApp.wakeLockTime += wakeTimeMillis;
                osApp.value += power;
                osApp.values[0] += power;

                /**
                 * 更新最大消耗电量的值
                 */
                if (osApp.value > mMaxPower) mMaxPower = osApp.value;
                if (osApp.value > mMaxRealPower) mMaxRealPower = osApp.value;

                mComputedPower += power;                                            //总消耗值
            }
        }
    }
    

    比Android4.4 相比起来,耗电统计精细了一点,两者的大致流程一样,主要是通过得到每个app占据CPU的时间(CPU分为不同的频率,不同频率的时间也要计算出来)、Wakelock的时间、数据流量的时间、WIFI的时间(包括WIFI的打开,工作,扫描,批量扫描几种不同的时间)以及各个传感器(GPS、光线,三轴加速,陀螺仪等)所消耗的时间,根据PowerProfile中已经存储的已知电流值,计算出其消耗的电量,并将单位转换为毫安时(mAh)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值