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

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

    

[java]  view plain  copy
  1. private void processAppUsage(SparseArray<UserHandle> asUsers) {  
  2.         final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);  
  3.         SensorManager sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);  
  4.   
  5.         final int which = mStatsType;  
  6.         final int speedSteps = mPowerProfile.getNumSpeedSteps();            //获取CPU可以运转到在几种频率之下  
  7.         final double[] powerCpuNormal = new double[speedSteps];  
  8.         final long[] cpuSpeedStepTimes = new long[speedSteps];  
  9.   
  10.         /** 
  11.          * 根据几种不同的频率,获取每个频率的平均电流值 
  12.          */  
  13.         for (int p = 0; p < speedSteps; p++) {  
  14.             powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);  
  15.         }  
  16.   
  17.         final double mobilePowerPerPacket = getMobilePowerPerPacket();        //移动数据流量功耗  
  18.         final double mobilePowerPerMs = getMobilePowerPerMs();                //数据连通网络时候的功耗  
  19.         final double wifiPowerPerPacket = getWifiPowerPerPacket();            //Wifi传输功耗  
  20.   
  21.         long appWakelockTimeUs = 0;  
  22.         BatterySipper osApp = null;  
  23.         mStatsPeriod = mTypeBatteryRealtime;  
  24.         /** 
  25.          * SparseArray代替HashMap,效率更高,SpareArray用的是稀疏矩阵的方式 
  26.          */  
  27.         SparseArray<? extends Uid> uidStats = mStats.getUidStats();           //获取每一个uid的数据  
  28.   
  29.         final int NU = uidStats.size();  
  30.   
  31.         for (int iu = 0; iu < NU; iu++) {  
  32.             Uid u = uidStats.valueAt(iu);  
  33.             double p;                                                           // in mAs  
  34.             double power = 0;                                                   // in mAs  
  35.             double highestDrain = 0;  
  36.             String packageWithHighestDrain = null;  
  37.   
  38.             Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();  
  39.   
  40.             long cpuTime = 0;  
  41.             long cpuFgTime = 0;  
  42.             long wakelockTime = 0;  
  43.             long gpsTime = 0;  
  44.   
  45.             if (processStats.size() > 0) {  
  46.                 // Process CPU time  
  47.                 for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent  
  48.                         : processStats.entrySet()) {  
  49.   
  50.                     Uid.Proc ps = ent.getValue();  
  51.                     /** 
  52.                      * 分别获取该进程,执行User的代码、执行系统级别的代码,以及在前段运行的时间 
  53.                      */  
  54.                     final long userTime = ps.getUserTime(which);  
  55.                     final long systemTime = ps.getSystemTime(which);  
  56.                     final long foregroundTime = ps.getForegroundTime(which);  
  57.   
  58.                     cpuFgTime += foregroundTime * 10;                           // convert to millis,转化为毫秒  
  59.   
  60.                     final long tmpCpuTime = (userTime + systemTime) * 10;       // convert to millis  
  61.   
  62.                     int totalTimeAtSpeeds = 0;  
  63.                     /** 
  64.                      * 根据CPU所可以运行的几种频率,得到每种不同的频率下的改进程所消耗的时间 
  65.                      */  
  66.                     for (int step = 0; step < speedSteps; step++) {  
  67.                         cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);  
  68.                         totalTimeAtSpeeds += cpuSpeedStepTimes[step];  
  69.                     }  
  70.                     if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;  
  71.                     /** 
  72.                      * 计算出每个CPU频率所消耗的时间,占总消耗时间的百分比,为什么要计算百分比? 
  73.                      */  
  74.                     double processPower = 0;  
  75.                     for (int step = 0; step < speedSteps; step++) {  
  76.                         double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;  
  77.                         if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"  
  78.                                 + step + " ratio=" + makemAh(ratio) + " power="  
  79.                                 + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));  
  80.   
  81.                         processPower += ratio * tmpCpuTime * powerCpuNormal[step];  
  82.                     }  
  83.   
  84.                     cpuTime += tmpCpuTime;                                                  //CPU时间  
  85.   
  86.                     if (DEBUG && processPower != 0) {  
  87.                         Log.d(TAG, String.format("process %s, cpu power=%s",  
  88.                                 ent.getKey(), makemAh(processPower / (60*60*1000))));  
  89.                     }  
  90.                     power += processPower;                                                  //总电量消耗加上该uid进程的消耗  
  91.   
  92.                     if (packageWithHighestDrain == null  
  93.                             || packageWithHighestDrain.startsWith("*")) {  
  94.                         highestDrain = processPower;  
  95.                         packageWithHighestDrain = ent.getKey();  
  96.                     } else if (highestDrain < processPower  
  97.                             && !ent.getKey().startsWith("*")) {  
  98.                         highestDrain = processPower;  
  99.                         packageWithHighestDrain = ent.getKey();  
  100.                     }  
  101.                 }  
  102.             }  
  103.             if (cpuFgTime > cpuTime) {  
  104.                 if (DEBUG && cpuFgTime > cpuTime + 10000) {  
  105.                     Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");  
  106.                 }  
  107.                 cpuTime = cpuFgTime;                            // Statistics may not have been gathered yet.数据还没有聚合??  
  108.             }  
  109.             /** 
  110.              * //该进程CPU所消耗的时间统计结束 
  111.              * 这里 60*60*1000 是将能量转化为 毫安时,得到的结果是 毫秒*毫安 
  112.              */  
  113.             power /= (60*60*1000);  
  114.   
  115.             /** 
  116.              * // Process wake lock usage 
  117.              * 该进程所持有的Wakelock而消耗的电量,一个进程可能持有多个Wakelock 
  118.              */  
  119.             Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();  
  120.             for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry  
  121.                     : wakelockStats.entrySet()) {  
  122.                 Uid.Wakelock wakelock = wakelockEntry.getValue();  
  123.                 // Only care about partial wake locks since full wake locks  
  124.                 // are canceled when the user turns the screen off.  
  125.                 /** 
  126.                  * 这里只关心Patrial的Wakelock,因为 fullwakelock 在屏幕灭的时候 已经被取消 
  127.                  * 通过该循环,可以得到该进程所持有的所有Wakelock的时间 
  128.                  */  
  129.                 BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);  
  130.                 if (timer != null) {  
  131.                     wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which);  
  132.                 }  
  133.             }  
  134.             appWakelockTimeUs += wakelockTime;                      //得到的结果是微秒的形式  
  135.             wakelockTime /= 1000;                                   // convert to millis,转化为毫秒  
  136.   
  137.             // Add cost of holding a wake lock  
  138.             /** 
  139.              * 计算得到持有Wakelock所消耗的电量,电流值为CPU醒着时候的电流值 
  140.              */  
  141.             p = (wakelockTime  
  142.                     * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);  
  143.             if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "  
  144.                     + wakelockTime + " power=" + makemAh(p));  
  145.   
  146.             power += p;  
  147.   
  148.             // Add cost of mobile traffic  
  149.             /** 
  150.              * 计算手机的数据流量,即使用 3G/4G 网络的流量数量 
  151.              */  
  152.             final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);  
  153.             final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);  
  154.             final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);  
  155.             final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);  
  156.   
  157.             /** 
  158.              * 手机连通网络的时间 
  159.              */  
  160.             final long mobileActive = u.getMobileRadioActiveTime(mStatsType);  
  161.   
  162.             if (mobileActive > 0) {  
  163.                 // We are tracking when the radio is up, so can use the active time to  
  164.                 // determine power use.  
  165.                 /** 
  166.                  * 当监控的时候,网络已经连通,所以可以直接用网络连通的时间,来确定功耗值 
  167.                  */  
  168.                 mAppMobileActive += mobileActive;  
  169.                 p = (mobilePowerPerMs * mobileActive) / 1000;  
  170.             } else {  
  171.                 // We are not tracking when the radio is up, so must approximate power use  
  172.                 // based on the number of packets.  
  173.                 /** 
  174.                  * 监控的时候,手机网络没有连通,那么就用流量值*流量电流 来估算出功耗 
  175.                  */  
  176.                 p = (mobileRx + mobileTx) * mobilePowerPerPacket;  
  177.             }  
  178.             if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "  
  179.                     + (mobileRx+mobileTx) + " active time " + mobileActive  
  180.                     + " power=" + makemAh(p));  
  181.   
  182.             power += p;  
  183.   
  184.             // Add cost of wifi traffic  
  185.             /** 
  186.              * WIFI的数据流量功耗,与计算手机数据流量的方式类似 
  187.              */  
  188.             final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);  
  189.             final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);  
  190.             final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);  
  191.             final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);  
  192.   
  193.             p = (wifiRx + wifiTx) * wifiPowerPerPacket;  
  194.   
  195.             if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "  
  196.                     + (mobileRx+mobileTx) + " power=" + makemAh(p));  
  197.   
  198.             power += p;  
  199.   
  200.             // Add cost of keeping WIFI running.  
  201.             /** 
  202.              * WIFI打开时候的功耗,打开WIFI,WIFI并没有其他运作 
  203.              */  
  204.             long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000;  
  205.             mAppWifiRunning += wifiRunningTimeMs;  
  206.             p = (wifiRunningTimeMs  
  207.                     * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);  
  208.             if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "  
  209.                     + wifiRunningTimeMs + " power=" + makemAh(p));  
  210.   
  211.             power += p;  
  212.   
  213.             // Add cost of WIFI scans  
  214.             /** 
  215.              * WIFI扫描时候的功耗 
  216.              */  
  217.             long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;  
  218.             p = (wifiScanTimeMs  
  219.                     * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);  
  220.             if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs  
  221.                     + " power=" + makemAh(p));  
  222.             power += p;  
  223.   
  224.             /** 
  225.              * WIFI 批量扫描模式所造成的功耗 
  226.              */  
  227.             for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {  
  228.                 long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;  
  229.                 p = ((batchScanTimeMs  
  230.                         * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))  
  231.                     ) / (60*60*1000);  
  232.                 if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin  
  233.                         + " time=" + batchScanTimeMs + " power=" + makemAh(p));  
  234.                 power += p;  
  235.             }  
  236.   
  237.             // Process Sensor usage  
  238.             /** 
  239.              * 进程使用传感器造成的功耗 
  240.              * 这里用SpareArray来存储 
  241.              */  
  242.             SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();  
  243.             int NSE = sensorStats.size();  
  244.   
  245.             for (int ise=0; ise<NSE; ise++) {  
  246.                 Uid.Sensor sensor = sensorStats.valueAt(ise);                   //获取Sensor类型所对应的实体  
  247.                 int sensorHandle = sensorStats.keyAt(ise);                      //获取Sensor的类型  
  248.   
  249.                 /** 
  250.                  * 该Sensor所消耗的时间 
  251.                  */  
  252.                 BatteryStats.Timer timer = sensor.getSensorTime();  
  253.                 long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;  
  254.   
  255.                 /** 
  256.                  * 对GPS区别对待,貌似GPS的平均电流值,可以在PowerProfile中得到,而其他Sensor的平均 
  257.                  * 电流值,通过SensorManager获取,每个Sensor的型号功能不同,其电流值应该是做在驱动层 
  258.                  */  
  259.                 double multiplier = 0;  
  260.                 switch (sensorHandle) {  
  261.                     case Uid.Sensor.GPS:  
  262.                         multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);  
  263.                         gpsTime = sensorTime;  
  264.                         break;  
  265.                     default:  
  266.                         List<Sensor> sensorList = sensorManager.getSensorList(  
  267.                                 android.hardware.Sensor.TYPE_ALL);  
  268.                         for (android.hardware.Sensor s : sensorList) {  
  269.                             if (s.getHandle() == sensorHandle) {  
  270.                                 multiplier = s.getPower();  
  271.                                 break;  
  272.                             }  
  273.                         }  
  274.                 }  
  275.                 p = (multiplier * sensorTime) / (60*60*1000);  
  276.                 if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle  
  277.                         + " time=" + sensorTime + " power=" + makemAh(p));  
  278.                 power += p;  
  279.             }  
  280.   
  281.             if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",  
  282.                     u.getUid(), makemAh(power)));  
  283.   
  284.             // Add the app to the list if it is consuming power  
  285.             /** 
  286.              * 如果该应用消耗的电量,就把它增加到列表中 
  287.              */  
  288.             final int userId = UserHandle.getUserId(u.getUid());  
  289.   
  290.             if (power != 0 || u.getUid() == 0) {  
  291.                 BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,  
  292.                         new double[] {power});  
  293.                 app.cpuTime = cpuTime;  
  294.                 app.gpsTime = gpsTime;  
  295.                 app.wifiRunningTime = wifiRunningTimeMs;  
  296.                 app.cpuFgTime = cpuFgTime;  
  297.                 app.wakeLockTime = wakelockTime;  
  298.                 app.mobileRxPackets = mobileRx;  
  299.                 app.mobileTxPackets = mobileTx;  
  300.                 app.mobileActive = mobileActive / 1000;  
  301.                 app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);  
  302.                 app.wifiRxPackets = wifiRx;  
  303.                 app.wifiTxPackets = wifiTx;  
  304.                 app.mobileRxBytes = mobileRxB;  
  305.                 app.mobileTxBytes = mobileTxB;  
  306.                 app.wifiRxBytes = wifiRxB;  
  307.                 app.wifiTxBytes = wifiTxB;  
  308.                 app.packageWithHighestDrain = packageWithHighestDrain;  
  309.   
  310.                 /** 
  311.                  * 对 WIFI 支持进程 和 蓝牙服务进程 区别对待,加入相应的列表中 
  312.                  */  
  313.                 if (u.getUid() == Process.WIFI_UID) {                       //wifi 支持进程  
  314.                     mWifiSippers.add(app);  
  315.                     mWifiPower += power;  
  316.                 } else if (u.getUid() == Process.BLUETOOTH_UID) {           //蓝牙服务进程  
  317.                     mBluetoothSippers.add(app);  
  318.                     mBluetoothPower += power;  
  319.                 }  
  320.                 /** 
  321.                  * Android 5.0 以后是多用户系统,这里要对用户做一些判别操作 
  322.                  */  
  323.                 else if (!forAllUsers && asUsers.get(userId) == null  
  324.                         && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {  
  325.                     List<BatterySipper> list = mUserSippers.get(userId);  
  326.                     /** 
  327.                      * 不同用户,拥有不同的app列表,要根据userId获取到应用列表,如果没有列表则新建, 
  328.                      * 然后在此用户下的应用列表中添加app 
  329.                      */  
  330.                     if (list == null) {  
  331.                         list = new ArrayList<BatterySipper>();  
  332.                         mUserSippers.put(userId, list);  
  333.                     }  
  334.   
  335.                     list.add(app);  
  336.   
  337.                     /** 
  338.                      *  在对不同用户的所在的app消耗的功耗进行统计 
  339.                      */  
  340.                     if (power != 0) {  
  341.                         Double userPower = mUserPower.get(userId);  
  342.                         if (userPower == null) {  
  343.                             userPower = power;  
  344.                         } else {  
  345.                             userPower += power;  
  346.                         }  
  347.                         mUserPower.put(userId, userPower);  
  348.                     }  
  349.                 }  
  350.                 /** 
  351.                  * 不区分用户的情况,则直接相加 
  352.                  */  
  353.                 else {  
  354.                     mUsageList.add(app);  
  355.                     if (power > mMaxPower) mMaxPower = power;  
  356.                     if (power > mMaxRealPower) mMaxRealPower = power;  
  357.                     mComputedPower += power;  
  358.                 }  
  359.                 if (u.getUid() == 0) {  
  360.                     osApp = app;                        //Android 的系统 app  
  361.                 }  
  362.             }  
  363.         }  
  364.   
  365.         // The device has probably been awake for longer than the screen on  
  366.         // time and application wake lock time would account for.  Assign  
  367.         // this remainder to the OS, if possible.  
  368.         /** 
  369.          * 设备在灭屏之后,可能也依旧处于唤醒状态,所以设备的实际唤醒时间要比屏幕亮着的总体时间要长 
  370.          * 这个原因就是Wakelock引起的,笔者根据代码理解,应该是将释放Wakelock的时间归结为系统所消耗 
  371.          * 的时间,这部分时间算在系统头上 
  372.          * 
  373.          */  
  374.         if (osApp != null) {  
  375.             long wakeTimeMillis = mBatteryUptime / 1000;  
  376.             /** 
  377.              * wakeTimeMillis的值查阅源代码的 英文翻译过来是 当前电池正在运行的时间 
  378.              * 电池所运行的时间,减去应用占据Wakelock的时间 再减去屏幕亮起的时间 
  379.              * 剩下的时间,就是要额外计算的,就是不算app所持有的Wakelock时间,且发生在灭屏后的,那 
  380.              * 应该就是 释放Wakelock 所消耗的时间,这里是笔者的推测 
  381.              */  
  382.             wakeTimeMillis -= (appWakelockTimeUs / 1000)  
  383.                     + (mStats.getScreenOnTime(mRawRealtime, which) / 1000);  
  384.   
  385.             //如果存在这样的时间  
  386.             if (wakeTimeMillis > 0) {  
  387.                 double power = (wakeTimeMillis  
  388.                         * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))  
  389.                         /  (60*60*1000);  
  390.   
  391.                 if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "  
  392.                         + makemAh(power));  
  393.   
  394.                 /** 
  395.                  * 补偿进去每一项的值 
  396.                  */  
  397.                 osApp.wakeLockTime += wakeTimeMillis;  
  398.                 osApp.value += power;  
  399.                 osApp.values[0] += power;  
  400.   
  401.                 /** 
  402.                  * 更新最大消耗电量的值 
  403.                  */  
  404.                 if (osApp.value > mMaxPower) mMaxPower = osApp.value;  
  405.                 if (osApp.value > mMaxRealPower) mMaxRealPower = osApp.value;  
  406.   
  407.                 mComputedPower += power;                                            //总消耗值  
  408.             }  
  409.         }  
  410.     }  
    

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值