本文针对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)