背景
为了完善产品的工具属性,进行了Android统计电量相关特性的调研。
提出问题
- Android 对于每个进程电量是如何统计的
- 第三方app如何读取系统的电量数据呢
解决问题
问题1 : Android如何计算电量
主要分为软件层与硬件层
先看软件层的计算规则:
软件层的电量是以Uid为单位统计的,分别计算每一个Uid的如下几项(源代码代码位于com.android.settings.fuelgauge.BatteryStatsHelper 的processAppUsage 接口)
- process cpu time
SpeedStep 机制可以动态调整CPU频率,当CPU使用率低下或接近零的时候动态降低CPU的倍率,令其工作频率下降,从而降低电压、功耗以及发热;而一旦监测到CPU使用率很高的时候,立即恢复到原始的速率工作
--引自百度百科
//Returns the number of speeds that the CPU can be run at.
final int speedSteps = mPowerProfile.getNumSpeedSteps();
//存储不同频率下的平均电流值(mA)
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);
}
//cpu 耗电 计算部分
for (int step = 0; step < speedSteps; step++)
{
double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
processPower += ratio * tmpCpuTime * powerCpuNormal[step];
}
从而不难理解
计算公式: power = CPU每个频率等级下工作的时间比例 / CPU工作总时间 * 应用运行总时间
- process wake lock usage
计算公式 :power = wakelockTime *averagePower(POWER_CPU_AWAKE)
- Add cost of mobile traffic (*注:R:receive(接收) T: transmit (传输) 下同 *)
计算公式 :
power=(mobileRx + mobileTx) * mobilePowerPerByte
- Add cost of wifi traffic
计算公式: power = (wifiRx + wifiTx) * wifiPowerPerByte;
- Add cost of keeping WIFI running.
计算公式: power = wifiRunningTimeMs * averagePower(POWER_WIFI_ON)
- Add cost of WIFI scans wifi
计算公式:power = wifiScanTimeMs * averagerPower(POWER_WIFI_SCAN)
- Process Sensor usage 计算uid的每个传感器的耗电
计算公式 : power = averagePower(不同传感器的耗电) * sensorTime
硬件层主要包括如下几项:
- 通话时间
计算公式
power = phoneOnTimeMs * averagerPower(POWER_RADIO_ACTIVE)
- 待机耗电
计算公式 :
power = idleTimeMs * averagePower(POWER_CPU_IDLE)
- wifi 开启时的耗电量
计算公式:
power = runningTimeMs * averagePower(POWER_WIFI_ON) + wifi uid 耗电
- 蓝牙耗电量
计算公式:power = btOnTimeMs * averagePower(POWER_BLUETOOTH_ON) + btPingCount * averagePower(POWER_BLUETOOTH_AT_CMD)+ bluetooth uid 耗电
- 手机基站信号耗电量
手机信号有五个信号强度,耗电量是每个信号强度的耗电量的累加。
计算公式:power = strengthTimeMs * averagePower(POWER_RADIO_ON) + scanningTimeMs * averagePower(POWER_RADIO_SCANNING)
- 屏幕耗电
计算公式 :
power = screenOnTimeMs * averagePower(POWER_SCREEN_ON) + brightnessTime(不同亮度的时间)* screenBinPower(不同亮度的电流强度)
问题2:如何访问系统的电量的数据
通过阅读Settings.apk的源代码发现电量的接口主要依赖于以下几个类 参考为Kitkat 版本源码
- com.android.server.am.BatteryStatsService
- com.android.internal.os.BatteryStatsImpl
- com.android.internal.os.PowerProfile
- com.android.settings.fuelgauge.BatteryStatsHelper
由此可知有关访问电量数据的接口是internal 的,作为第三方无法正常的调用,因此想到反射。
在BatteryStatsHelper类中可以看到 ,是通过如下代码访问电量信息的,
byte[] data = mBatteryInfo.getStatistics();
实际调用的是BatteryStatsService 类的getStatistics() 接口,接着看下这个接口的实现:
//这个接口需要系统权限,第三方app无法正常调用,为了测试把app放置在system/priv-app目录下
public byte[] getStatistics() {
mContext.enforceCallingPermission( android.Manifest.permission.BATTERY_STATS, null);
Parcel out = Parcel.obtain();
mStats.writeToParcel(out, 0);
byte[] data = out.marshall();
out.recycle(); return data;
}
可以看出这个接口需要系统权限,即便在AndroidManifext.xml申请第三方app也是无法正常调用,为了测试方便把app放置在system/priv-app目录下,绕过这层权限限制。
具体实现
主要逻辑集中在compat层,实现了对应原生系统每个类的反射逻辑以调用getStatistics 为例:
private byte[] getStatics() {
try {
Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
Method getServiceMethod = serviceManagerClass.getMethod("getService", java.lang.String.class);
Object batteryStatsService = getServiceMethod.invoke(null, "batterystats");
Class<?> batteryStatsStubClass = Class.forName("com.android.internal.app.IBatteryStats$Stub");
Object batteryStatsStubObject = batteryStatsStubClass.getMethod("asInterface", android.os.IBinder.class).invoke(null, batteryStatsService);
return (byte[]) batteryStatsStubClass.getMethod("getStatistics").invoke(batteryStatsStubObject);
} catch (NoSuchMethodException e) { e.printStackTrace();
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (InvocationTargetException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } throw new UnsupportedOperationException("getStatics unsupport operation");
}
运行结果
原生系统设置:
BatteryDemo:
由此可见,排序与系统的电量排名和电量占比基本保持一致
总结
本文主要记录了笔者在调研Android 电量计算特性时的一些收获,分享给大家,距离真实的产品化还有很多工作要做,这篇文章暂不一一赘述。