淺談Android系統(tǒng)的電量統(tǒng)計(jì)機(jī)制

背景

為了完善產(chǎn)品的工具屬性,進(jìn)行了Android統(tǒng)計(jì)電量相關(guān)特性的調(diào)研。

提出問題

  • Android 對(duì)于每個(gè)進(jìn)程電量是如何統(tǒng)計(jì)的
  • 第三方app如何讀取系統(tǒng)的電量數(shù)據(jù)呢

解決問題

問題1 : Android如何計(jì)算電量

主要分為軟件層硬件層
先看軟件層的計(jì)算規(guī)則:
軟件層的電量是以Uid為單位統(tǒng)計(jì)的,分別計(jì)算每一個(gè)Uid的如下幾項(xiàng)(源代碼代碼位于com.android.settings.fuelgauge.BatteryStatsHelper 的processAppUsage 接口)

  • process cpu time

SpeedStep 機(jī)制可以動(dòng)態(tài)調(diào)整CPU頻率,當(dāng)CPU使用率低下或接近零的時(shí)候動(dòng)態(tài)降低CPU的倍率,令其工作頻率下降,從而降低電壓、功耗以及發(fā)熱;而一旦監(jiān)測(cè)到CPU使用率很高的時(shí)候,立即恢復(fù)到原始的速率工作
--引自百度百科

//Returns the number of speeds that the CPU can be run at. 
final int speedSteps = mPowerProfile.getNumSpeedSteps();  
//存儲(chǔ)不同頻率下的平均電流值(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 耗電 計(jì)算部分 
for (int step = 0; step < speedSteps; step++) 
{ 
double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds; 
processPower += ratio * tmpCpuTime * powerCpuNormal[step]; 
} 

從而不難理解
計(jì)算公式: power = CPU每個(gè)頻率等級(jí)下工作的時(shí)間比例 / CPU工作總時(shí)間 * 應(yīng)用運(yùn)行總時(shí)間

  • process wake lock usage

計(jì)算公式 :power = wakelockTime *averagePower(POWER_CPU_AWAKE)

  • Add cost of mobile traffic (*注:R:receive(接收) T: transmit (傳輸) 下同 *)

計(jì)算公式
power=(mobileRx + mobileTx) * mobilePowerPerByte

  • Add cost of wifi traffic

計(jì)算公式: power = (wifiRx + wifiTx) * wifiPowerPerByte;

  • Add cost of keeping WIFI running.

計(jì)算公式: power = wifiRunningTimeMs * averagePower(POWER_WIFI_ON)

  • Add cost of WIFI scans wifi

計(jì)算公式:power = wifiScanTimeMs * averagerPower(POWER_WIFI_SCAN)

  • Process Sensor usage 計(jì)算uid的每個(gè)傳感器的耗電

計(jì)算公式 : power = averagePower(不同傳感器的耗電) * sensorTime

硬件層主要包括如下幾項(xiàng):

  • 通話時(shí)間

計(jì)算公式
power = phoneOnTimeMs * averagerPower(POWER_RADIO_ACTIVE)

  • 待機(jī)耗電

計(jì)算公式
power = idleTimeMs * averagePower(POWER_CPU_IDLE)

  • wifi 開啟時(shí)的耗電量

計(jì)算公式
power = runningTimeMs * averagePower(POWER_WIFI_ON) + wifi uid 耗電

  • 藍(lán)牙耗電量

計(jì)算公式:power = btOnTimeMs * averagePower(POWER_BLUETOOTH_ON) + btPingCount * averagePower(POWER_BLUETOOTH_AT_CMD)+ bluetooth uid 耗電

  • 手機(jī)基站信號(hào)耗電量

手機(jī)信號(hào)有五個(gè)信號(hào)強(qiáng)度,耗電量是每個(gè)信號(hào)強(qiáng)度的耗電量的累加。
計(jì)算公式:power = strengthTimeMs * averagePower(POWER_RADIO_ON) + scanningTimeMs * averagePower(POWER_RADIO_SCANNING)

  • 屏幕耗電

計(jì)算公式 :
power = screenOnTimeMs * averagePower(POWER_SCREEN_ON) + brightnessTime(不同亮度的時(shí)間)* screenBinPower(不同亮度的電流強(qiáng)度)

問題2:如何訪問系統(tǒng)的電量的數(shù)據(jù)

通過閱讀Settings.apk的源代碼發(fā)現(xiàn)電量的接口主要依賴于以下幾個(gè)類 參考為Kitkat 版本源碼

  • com.android.server.am.BatteryStatsService
  • com.android.internal.os.BatteryStatsImpl
  • com.android.internal.os.PowerProfile
  • com.android.settings.fuelgauge.BatteryStatsHelper
    由此可知有關(guān)訪問電量數(shù)據(jù)的接口是internal 的,作為第三方無法正常的調(diào)用,因此想到反射。
    在BatteryStatsHelper類中可以看到 ,是通過如下代碼訪問電量信息的,
    byte[] data = mBatteryInfo.getStatistics();
    實(shí)際調(diào)用的是BatteryStatsService 類的getStatistics() 接口,接著看下這個(gè)接口的實(shí)現(xiàn):
//這個(gè)接口需要系統(tǒng)權(quán)限,第三方app無法正常調(diào)用,為了測(cè)試把a(bǔ)pp放置在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;
}

可以看出這個(gè)接口需要系統(tǒng)權(quán)限,即便在AndroidManifext.xml申請(qǐng)第三方app也是無法正常調(diào)用,為了測(cè)試方便把a(bǔ)pp放置在system/priv-app目錄下,繞過這層權(quán)限限制。

具體實(shí)現(xiàn)

主要邏輯集中在compat層,實(shí)現(xiàn)了對(duì)應(yīng)原生系統(tǒng)每個(gè)類的反射邏輯以調(diào)用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"); 
}

運(yùn)行結(jié)果

原生系統(tǒng)設(shè)置

Paste_Image.png

BatteryDemo:

Paste_Image.png

由此可見,排序與系統(tǒng)的電量排名和電量占比基本保持一致

總結(jié)

本文主要記錄了筆者在調(diào)研Android 電量計(jì)算特性時(shí)的一些收獲,分享給大家,距離真實(shí)的產(chǎn)品化還有很多工作要做,這篇文章暫不一一贅述。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容