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