版权声明:本文为博主原创文章,未经博主允许不得转载。
本文针对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)