背景
為了完善產品的工具屬性,進行了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 電量計算特性時的一些收獲,分享給大家,距離真實的產品化還有很多工作要做,這篇文章暫不一一贅述。