基於Android 6.0的源碼剖析。
Power_profile.xml文件demo:

<?xml version="1.0" encoding="utf-8"?> <!-- ** ** Copyright 2009, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License") ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ --> <device name="Android"> <!-- Most values are the incremental current used by a feature, in mA (measured at nominal voltage). The default values are deliberately incorrect dummy values. OEM's must measure and provide actual values before shipping a device. Example real-world values are given in comments, but they are totally dependent on the platform and can vary significantly, so should be measured on the shipping platform with a power meter. --> <item name="none">0</item> <item name="screen.on">200</item> <!-- ~200mA --> <item name="screen.full">300</item> <!-- ~300mA --> <item name="bluetooth.active">80</item> <!-- Bluetooth data transfer, ~10mA --> <item name="bluetooth.on">1</item> <!-- Bluetooth on & connectable, but not connected, ~0.1mA --> <item name="wifi.on">3</item> <!-- ~3mA --> <item name="wifi.active">200</item> <!-- WIFI data transfer, ~200mA --> <item name="wifi.scan">100</item> <!-- WIFI network scanning, ~100mA --> <item name="dsp.audio">10</item> <!-- ~10mA --> <item name="dsp.video">50</item> <!-- ~50mA --> <item name="camera.flashlight">160</item> <!-- Avg. power for camera flash, ~160mA --> <item name="camera.avg">550</item> <!-- Avg. power use of camera in standard usecases, ~550mA --> <item name="gps.on">50</item> <!-- ~50mA --> <!-- Radio related values. For modems without energy reporting support in firmware, use radio.active, radio.scanning, and radio.on. --> <item name="radio.active">200</item> <!-- ~200mA --> <item name="radio.scanning">10</item> <!-- cellular radio scanning for signal, ~10mA --> <!-- Current consumed by the radio at different signal strengths, when paging --> <array name="radio.on"> <!-- Strength 0 to BINS-1 --> <value>2</value> <!-- ~2mA --> <value>1</value> <!-- ~1mA --> </array> <!-- Radio related values. For modems WITH energy reporting support in firmware, use modem.controller.idle, modem.controller.tx, modem.controller.rx, modem.controller.voltage. --> <item name="modem.controller.idle">0</item> <item name="modem.controller.rx">0</item> <item name="modem.controller.tx">0</item> <item name="modem.controller.voltage">0</item> <!-- A list of heterogeneous CPU clusters, where the value for each cluster represents the number of CPU cores for that cluster. Ex: <array name="cpu.clusters.cores"> <value>4</value> // cluster 0 has cpu0, cpu1, cpu2, cpu3 <value>2</value> // cluster 1 has cpu4, cpu5, cpu6, cpu7 </array> --> <array name="cpu.clusters.cores"> <value>4</value> <value>4</value> </array> <!-- Different CPU speeds for cluster 0 as reported in /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state. There must be one of these for each cluster, labeled: cpu.speeds.cluster0, cpu.speeds.cluster1, etc... 注釋:帶cluster就是區分大小核的細分了--> <array name="cpu.speeds.cluster0"> <value>900000</value> <value>979000</value> <value>1085000</value> <value>1218000</value> <value>1351000</value> <value>1484000</value> <value>1617000</value> <value>1750000</value> <value>1779000</value> <value>1809000</value> <value>1838000</value> <value>1868000</value> <value>1897000</value> <value>1927000</value> <value>1961000</value> <value>2001000</value> </array> <array name="cpu.speeds.cluster1"> <value>900000</value> <value>979000</value> <value>1085000</value> <value>1218000</value> <value>1351000</value> <value>1484000</value> <value>1617000</value> <value>1750000</value> <value>1779000</value> <value>1809000</value> <value>1838000</value> <value>1868000</value> <value>1897000</value> <value>1927000</value> <value>1961000</value> <value>2001000</value> </array> <!-- Current at each CPU speed for cluster 0, as per 'cpu.speeds.cluster0'. Like cpu.speeds.cluster0, there must be one of these present for each heterogeneous CPU cluster. --> <array name="cpu.active.cluster0"> <value>254.95</value> <value>354.95</value> <value>387.15</value> <value>442.86</value> <value>510.20</value> <value>582.65</value> <value>631.99</value> <value>812.02</value> <value>858.84</value> <value>943.23</value> <value>992.45</value> <value>1086.32</value> <value>1151.96</value> <value>1253.80</value> <value>1397.67</value> <value>1450.67</value> </array> <array name="cpu.active.cluster1"> <value>254.95</value> <value>354.95</value> <value>387.15</value> <value>442.86</value> <value>510.20</value> <value>582.65</value> <value>631.99</value> <value>812.02</value> <value>858.84</value> <value>943.23</value> <value>992.45</value> <value>1086.32</value> <value>1151.96</value> <value>1253.80</value> <value>1397.67</value> <value>1450.67</value> </array> <!-- Current when CPU is idle --> <item name="cpu.idle">10</item> <!-- Memory bandwidth power values in mA at the rail. There must be one value for each bucket defined in the device tree. --> <array name="memory.bandwidths"> <value>22.7</value> <!-- mA for bucket: 100mb/s-1.5 GB/s memory bandwidth --> </array> <!-- This is the battery capacity in mAh (measured at nominal voltage) --> <item name="battery.capacity">3000</item> <!-- Wifi related values. --> <!-- Idle Receive current for wifi radio in mA. 0 by default--> <item name="wifi.controller.idle">10</item> <!-- Rx current for wifi radio in mA. 0 by default--> <item name="wifi.controller.rx">150</item> <!-- Tx current for wifi radio in mA. 0 by default--> <item name="wifi.controller.tx">150</item> <!-- Current at each of the wifi Tx levels in mA. The number of tx levels varies per device and is available only of wifi chipsets which support the tx level reporting. Use wifi.tx for other chipsets. none by default --> <array name="wifi.controller.tx_levels"> <!-- mA --> </array> <!-- Operating volatage for wifi radio in mV. 0 by default--> <item name="wifi.controller.voltage">0</item> <array name="wifi.batchedscan"> <!-- mA --> <value>.2</value> <!-- 1-8/hr --> <value>2</value> <!-- 9-64/hr --> <value>20</value> <!-- 65-512/hr --> <value>200</value> <!-- 513-4,096/hr --> <value>500</value> <!-- 4097-/hr --> </array> </device>
一、 概述
Android系統中的耗電統計分為軟件排行榜和硬件排行榜,軟件排序榜是統計每個App的耗電總量的排行榜,硬件排行榜則是統計主要硬件的耗電總量的排行榜。
涉及耗電統計相關的核心類:
/framework/base/core/res/res/xml/power_profile.xml /framework/base/core/java/com/andorid/internal/os/PowerProfile.java /framework/base/core/java/com/andorid/internal/os/BatteryStatsHelper.java /framework/base/core/java/com/andorid/internal/os/BatterySipper.java
PowerProfile.java用於獲取各個組件的電流數值;power_profile.xml是一個可配置的功耗數據文件。
軟件排行榜的計算算法:BatteryStatsHelper類中的processAppUsage()方法。
硬件排行榜的計算算法:BatteryStatsHelper類中的processMiscUsage()方法。
二、軟件排行榜
processAppUsage統計每個App的耗電情況
private void processAppUsage(SparseArray<UserHandle> asUsers) { //判斷是否統計所有用戶的App耗電使用情況,目前該參數為true final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null); mStatsPeriod = mTypeBatteryRealtime; //耗電的統計時長 BatterySipper osSipper = null; //獲取每個uid的統計信息 final SparseArray<? extends Uid> uidStats = mStats.getUidStats(); final int NU = uidStats.size(); // 開始遍歷每個uid的耗電情況 for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0); mCpuPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mWakelockPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mWifiPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mSensorPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mCameraPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); final double totalPower = app.sumPower(); //對App的8項耗電再進行累加。 //將app添加到 app list, WiFi, Bluetooth等,或其他用戶列表 if (totalPower != 0 || u.getUid() == 0) { final int uid = app.getUid(); final int userId = UserHandle.getUserId(uid); if (uid == Process.WIFI_UID) { //uid為wifi的情況 mWifiSippers.add(app); } else if (uid == Process.BLUETOOTH_UID) {//uid為藍牙的情況 mBluetoothSippers.add(app); } else if (!forAllUsers && asUsers.get(userId) == null && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) { //就目前來說 forAllUsers=true,不會進入此分支。 List<BatterySipper> list = mUserSippers.get(userId); if (list == null) { list = new ArrayList<>(); mUserSippers.put(userId, list); } list.add(app); } else { mUsageList.add(app); //把app耗電加入到mUsageList } if (uid == 0) { osSipper = app; // root用戶,代表操作系統的耗電量 } } } //app之外的耗電量 if (osSipper != null) { mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtime, mRawUptime, mStatsType); osSipper.sumPower(); } }
流程分析:mTypeBatteryRealtime
mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType); private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
BatteryStats.STATS_SINCE_CHARGED,計算規則是從上次充滿電后數據;另外STATS_SINCE_UNPLUGGED是拔掉USB線后的數據。說明充電時間的計算是從上一次拔掉設備到現在的耗電量統計。
耗電計算項
8大模塊的耗電計算器,都繼承與PowerCalculator
抽象類
計算項 | Class文件 |
---|---|
CPU功耗 | mCpuPowerCalculator.java |
Wakelock功耗 | mWakelockPowerCalculator.java |
無線電功耗 | mMobileRadioPowerCalculator.java |
WIFI功耗 | mWifiPowerCalculator.java |
藍牙功耗 | mBluetoothPowerCalculator.java |
Sensor功耗 | mSensorPowerCalculator.java |
相機功耗 | mCameraPowerCalculator.java |
閃光燈功耗 | mFlashlightPowerCalculator.java |
計算值添加到列表
mWifiSippers.add(app): uid為wifi的情況.
mBluetoothSippers.add(app): uid為藍牙的情況.
mUsageList.add(app): app耗電加入到mUsageList.
osSipper: root用戶,代表操作系統的耗電量,app之外的wakelock耗電也計算該項.
公式:
processAppUsage統計的是Uid。一般地來說每個App都對應一個Uid,但存在以下特殊情況,如果兩個或多個App簽名和sharedUserId相同,則在運行時,他們擁有相同Uid。對於系統應用uid為system,則統一算入system應用的耗電量。
Uid_Power = process_1_Power + … + process_N_Power,其中所有進程都是屬於同一個uid。 當同一的uid下,只有一個進程時,Uid_Power = process_Power;
其中process_Power = CPU功耗 + Wakelock功耗 + 無線電功耗 + WIFI功耗 + 藍牙功耗 + Sensor功耗 + 相機功耗 + 閃光燈功耗。 接下來開始分配說明每一項的功耗計算公式。
2.1 CPU
CPU功耗項的計算是通過CpuPowerCalculator類,初始化:
public CpuPowerCalculator(PowerProfile profile) { final int speedSteps = profile.getNumSpeedSteps(); //獲取cpu的主頻等級的級數 mPowerCpuNormal = new double[speedSteps]; //用於記錄不同頻率下的功耗值 mSpeedStepTimes = new long[speedSteps]; for (int p = 0; p < speedSteps; p++) { mPowerCpuNormal[p] = profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p); } }
從對象的構造方法,可以看出PowerProfile類提供相應所需的基礎功耗值,而真正的功耗值數據來源於power_profile.xml文件,故可以通過配置合理的基礎功耗值,來達到較為精准的功耗統計結果。后續所有的PowerCalculator子類在構造方法中都有會相應所需的功耗配置項。
CPU功耗可配置項:
POWER_CPU_SPEEDS = “cpu.speeds” 所對應的數組,配置CPU的頻點,頻點個數取決於CPU硬件特性
POWER_CPU_ACTIVE = “cpu.active” 所對應的數組,配置CPU頻點所對應的單位功耗(mAh)
功耗計算:
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { final int speedSteps = mSpeedStepTimes.length; long totalTimeAtSpeeds = 0; for (int step = 0; step < speedSteps; step++) { //獲取Cpu不同頻點下的運行時間 mSpeedStepTimes[step] = u.getTimeAtCpuSpeed(step, statsType); totalTimeAtSpeeds += mSpeedStepTimes[step]; } totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1); //獲取Cpu總共的運行時間 app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000; //獲取cpu在用戶態和內核態的執行時長 double cpuPowerMaMs = 0; // 計算Cpu的耗電量 for (int step = 0; step < speedSteps; step++) { final double ratio = (double) mSpeedStepTimes[step] / totalTimeAtSpeeds; final double cpuSpeedStepPower = ratio * app.cpuTimeMs * mPowerCpuNormal[step]; cpuPowerMaMs += cpuSpeedStepPower; } //追蹤不同進程的耗電情況 double highestDrain = 0; app.cpuFgTimeMs = 0; final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); final int processStatsCount = processStats.size(); //統計同一個uid的不同進程的耗電情況 for (int i = 0; i < processStatsCount; i++) { final BatteryStats.Uid.Proc ps = processStats.valueAt(i); final String processName = processStats.keyAt(i); app.cpuFgTimeMs += ps.getForegroundTime(statsType); final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType) + ps.getForegroundTime(statsType); //App可以有多個packages和多個不同的進程,跟蹤耗電最大的進程 if (app.packageWithHighestDrain == null || app.packageWithHighestDrain.startsWith("*")) { highestDrain = costValue; app.packageWithHighestDrain = processName; } else if (highestDrain < costValue && !processName.startsWith("*")) { highestDrain = costValue; app.packageWithHighestDrain = processName; } } //當Cpu前台時間 大於Cpu時間,將cpuFgTimeMs賦值為cpuTimeMs if (app.cpuFgTimeMs > app.cpuTimeMs) { app.cpuTimeMs = app.cpuFgTimeMs; } app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000); //轉換為mAh } }
子公式: cpuPower = ratio_1 * cpu_time * cpu_ratio_1_power + … +ratio_n * cpu_time * cpu_ratio_n_power. 其中: ratio_i = cpu_speed_time/ cpu_speeds_total_time,(i=1,2,…,N,N為CPU頻點個數).
2.2 Wakelock
Wakelock功耗項的計算是通過WakelockPowerCalculator類
初始化:
public WakelockPowerCalculator(PowerProfile profile) { mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_AWAKE); }
Wakelock功耗可配置項:power_profile.xml文件:
POWER_CPU_AWAKE = “cpu.awake” 所對應的值.
功耗計算:
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { long wakeLockTimeUs = 0; final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats(); final int wakelockStatsCount = wakelockStats.size(); for (int i = 0; i < wakelockStatsCount; i++) { final BatteryStats.Uid.Wakelock wakelock = wakelockStats.valueAt(i); //只統計partial wake locks,由於 full wake locks在滅屏后會自動取消 BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL); if (timer != null) { wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType); } } app.wakeLockTimeMs = wakeLockTimeUs / 1000; //轉換單位為秒 mTotalAppWakelockTimeMs += app.wakeLockTimeMs; //計算喚醒功耗 app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000*60*60); }
子公式:
wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000*60*60);
2.3 Wifi
(1)WifiPowerCalculator
Wifi功耗項的計算是通過WifiPowerCalculator類
初始化:
public WifiPowerCalculator(PowerProfile profile) { mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE); mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX); mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX); }
Wifi功耗可配置項:
POWER_WIFI_CONTROLLER_IDLE = “wifi.controller.idle” 項所對應的值.
POWER_WIFI_CONTROLLER_RX = “wifi.controller.rx” 項所對應的值.
POWER_WIFI_CONTROLLER_TX = “wifi.controller.tx” 項所對應的值.
功耗計算:
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { final long idleTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME, statsType); final long txTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME, statsType); final long rxTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME, statsType); app.wifiRunningTimeMs = idleTime + rxTime + txTime; //計算wifi的時間 app.wifiPowerMah = ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa)) / (1000*60*60); mTotalAppPowerDrain += app.wifiPowerMah; app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, statsType); app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, statsType); app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, statsType); app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, statsType); }
子公式:
wifiPowerMah = ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa)) / (1000* 60* 60);
(2)WifiPowerEstimator
可配置項:
POWER_WIFI_ACTIVE = “wifi.active”(用於計算mWifiPowerPerPacket)
POWER_WIFI_ON=”wifi.on”
POWER_WIFI_SCAN = “wifi.scan”
POWER_WIFI_BATCHED_SCAN = “wifi.batchedscan”;
功耗計算:
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, statsType); app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, statsType); app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, statsType); app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, statsType); final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets) * mWifiPowerPerPacket; app.wifiRunningTimeMs = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs; final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60); final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60); double wifiBatchScanPower = 0; for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { final long batchScanTimeMs = u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; final double batchScanPower = (batchScanTimeMs * mWifiPowerBatchScan) / (1000*60*60); wifiBatchScanPower += batchScanPower; } app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower; }
其中 BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS=5
另外
private static double getWifiPowerPerPacket(PowerProfile profile) { final long WIFI_BPS = 1000000; //初略估算每秒的收發1000000bit final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) / 3600; return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60); }
子公式:
wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
wifiPacketPower = (wifiRxPackets + wifiTxPackets) * mWifiPowerPerPacket;
wifiLockPower = (wifiRunningTimeMs * mWifiPowerOn) / (1000* 60* 60);
wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000* 60* 60);
wifiBatchScanPower = ∑ (batchScanTimeMs * mWifiPowerBatchScan) / (1000* 60* 60) ,5次相加。
2.4 Bluetooth
Bluetooth功耗項的計算是通過BluetoothPowerCalculator類
初始化:
public BluetoothPowerCalculator(PowerProfile profile) { mIdleMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE); mRxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX); mTxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX); }
Bluetooth功耗可配置項(目前藍牙功耗計算的方法為空,此配置暫可忽略):
POWER_BLUETOOTH_CONTROLLER_IDLE = “bluetooth.controller.idle” 所對應的值
POWER_BLUETOOTH_CONTROLLER_RX = “bluetooth.controller.rx” 所對應的值
POWER_BLUETOOTH_CONTROLLER_TX = “bluetooth.controller.tx” 所對應的值
功耗計算:
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { // No per-app distribution yet. }
子公式:
bluePower = 0;還沒有給每個App統計藍牙的算法。
2.5 Camera
Camera功耗項的計算是通過CameraPowerCalculator類
初始化:
public CameraPowerCalculator(PowerProfile profile) { mCameraPowerOnAvg = profile.getAveragePower(PowerProfile.POWER_CAMERA); }
Camera功耗可配置項:
POWER_CAMERA = “camera.avg” 所對應的值
功耗計算:
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { //對於camera功耗的評估比較粗,camera打開認定功耗一致 final BatteryStats.Timer timer = u.getCameraTurnedOnTimer(); if (timer != null) { final long totalTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; app.cameraTimeMs = totalTime; app.cameraPowerMah = (totalTime * mCameraPowerOnAvg) / (1000*60*60); } else { app.cameraTimeMs = 0; app.cameraPowerMah = 0; } }
子公式:
cameraPowerMah = (totalTime * mCameraPowerOnAvg) / (1000*60*60);
2.6 Flashlight
Flashlight功耗項的計算是通過FlashlightPowerCalculator類
初始化:
public FlashlightPowerCalculator(PowerProfile profile) { mFlashlightPowerOnAvg = profile.getAveragePower(PowerProfile.POWER_FLASHLIGHT); }
Flashlight功耗可配置項:
POWER_FLASHLIGHT = “camera.flashlight” 所對應的值。
功耗計算:
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { final BatteryStats.Timer timer = u.getFlashlightTurnedOnTimer(); if (timer != null) { final long totalTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; app.flashlightTimeMs = totalTime; app.flashlightPowerMah = (totalTime * mFlashlightPowerOnAvg) / (1000*60*60); } else { app.flashlightTimeMs = 0; app.flashlightPowerMah = 0; } }
子公式:
flashlightPowerMah = (totalTime * mFlashlightPowerOnAvg) / (1000*60*60);
flashlight計算方式與Camera功耗計算思路一樣。
2.7 MobileRadio
無線電功耗項的計算是通過MobileRadioPowerCalculator類
初始化:
public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) { mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE); for (int i = 0; i < mPowerBins.length; i++) { mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i); } mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING); mStats = stats; }
無線電功耗可配置項:
POWER_RADIO_ACTIVE = “radio.active” 所對應的值。
功耗計算:
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, statsType); app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, statsType); app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000; app.mobileActiveCount = u.getMobileRadioActiveCount(statsType); app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA, statsType); app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA, statsType); if (app.mobileActive > 0) { // 當追蹤信號的開頭,則采用mobileActive時間乘以相應功耗 mTotalAppMobileActiveMs += app.mobileActive; app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60); } else { //當沒有追蹤信號開關,則采用收發數據包來評估功耗 app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets) * getMobilePowerPerPacket(rawRealtimeUs, statsType); } }
計算出每收/發一個數據包的功耗值
private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) { final long MOBILE_BPS = 200000; //Extract average bit rates from system final double MOBILE_POWER = mPowerRadioOn / 3600; final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, statsType); final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, statsType); final long mobileData = mobileRx + mobileTx; final long radioDataUptimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0) ? (mobileData / (double)radioDataUptimeMs) : (((double)MOBILE_BPS) / 8 / 2048); return (MOBILE_POWER / mobilePps) / (60*60); }
子公式:
情況一:當追蹤信號活動時間,即mobileActive > 0,采用:
mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000* 60* 60);
情況二:當沒有追蹤信號活動時間,則采用:
mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets) * MobilePowerPerPacket
其中MobilePowerPerPacket = ((mPowerRadioOn / 3600) / mobilePps) / (60*60), mobilePps= (mobileRx + mobileTx)/radioDataUptimeMs
2.8 Sensor
Sensor功耗項的計算是通過SensorPowerCalculator類
初始化:
public SensorPowerCalculator(PowerProfile profile, SensorManager sensorManager) { mSensors = sensorManager.getSensorList(Sensor.TYPE_ALL); mGpsPowerOn = profile.getAveragePower(PowerProfile.POWER_GPS_ON); }
Sensor功耗可配置項:
POWER_GPS_ON = “gps.on” 所對應的值。
功耗計算:
d calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { // 計算沒有個Uid final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); final int NSE = sensorStats.size(); for (int ise = 0; ise < NSE; ise++) { final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise); final int sensorHandle = sensorStats.keyAt(ise); final BatteryStats.Timer timer = sensor.getSensorTime(); final long sensorTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; switch (sensorHandle) { case BatteryStats.Uid.Sensor.GPS: //對於uid為GPS的情況 app.gpsTimeMs = sensorTime; app.gpsPowerMah = (app.gpsTimeMs * mGpsPowerOn) / (1000*60*60); break; default: final int sensorsCount = mSensors.size(); for (int i = 0; i < sensorsCount; i++) { //統計所有的sensor情況 final Sensor s = mSensors.get(i); if (s.getHandle() == sensorHandle) { app.sensorPowerMah += (sensorTime * s.getPower()) / (1000*60*60); break; } } break; } } }
GPS的功耗計算與sensor的計算方法放在一塊
子公式:
gpsPowerMah = (app.gpsTimeMs * mGpsPowerOn) / (1000* 60* 60);
軟件功耗總公式
BatterySipper中,功耗計算方法:
public double sumPower() { return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah + flashlightPowerMah; }
軟件功耗的子項共分為9項:
功耗項 | 解釋 |
---|---|
usage | 通用的功耗 |
cpu | cpu的消耗 |
wakelock | 喚醒帶來的功耗 |
mobileRadio | 移動無線的功耗 |
wifi | wifi功耗 |
gps | 定位的功耗 |
sensor | 傳感器的功耗 |
camera | 相機功耗 |
flashlight | 閃光燈功耗 |
目前沒有統計藍牙的耗電,源碼中統計藍牙耗電的方法是空方法。gps耗電的計算在sensor模塊一起計算的。
軟件功耗總公式:
三、 硬件排行榜
processMiscUsage()
private void processMiscUsage() { addUserUsage(); addPhoneUsage(); addScreenUsage(); addWiFiUsage(); addBluetoothUsage(); addIdleUsage(); if (!mWifiOnly) { //對於只有wifi上網功能的設備,將不算計此項 addRadioUsage(); } }
硬件功耗的子項共分為7項:
功耗項 | 解釋 |
---|---|
UserUsage | 用戶功耗 |
PhoneUsage | 通話功耗 |
ScreenUsage | 屏幕功耗 |
WiFiUsage | Wifi功耗 |
BluetoothUsage | 藍牙消耗 |
IdleUsage | CPU Idle功耗 |
RadioUsage | 移動無線功耗 |
3.1 User
用戶功耗的類型DrainType.USER
功耗計算:
private void addUserUsage() { for (int i = 0; i < mUserSippers.size(); i++) { final int userId = mUserSippers.keyAt(i); BatterySipper bs = new BatterySipper(DrainType.USER, null, 0); bs.userId = userId; aggregateSippers(bs, mUserSippers.valueAt(i), "User"); //將UserSippers中的功耗都合入bs. mUsageList.add(bs); } }
合計:
private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) { for (int i=0; i<from.size(); i++) { BatterySipper wbs = from.get(i); bs.add(wbs); //將from中的功耗數據合入bs } bs.computeMobilemspp(); bs.sumPower(); //計算總功耗 }
子公式:
user_power = user_1_power + user_2_power + … + user_n_power; (n為所有的user的總數)
3.2 Phone
通話功耗的類型為DrainType.PHONE
通話功耗可配置項:
POWER_RADIO_ACTIVE = “radio.active” 所對應的值。
功耗計算:
private void addPhoneUsage() { long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtime, mStatsType) / 1000; double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) * phoneOnTimeMs / (60*60*1000); if (phoneOnPower != 0) { addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); } }
子公式:
phone_powers = (phoneOnTimeMs * phoneOnPower) / (60* 60* 1000)
3.3 CPU Idle
CPU Idle功耗的類型為DrainType.IDLE
CPU Idle功耗可配置項:
- PowerProfile.POWER_CPU_IDLE = “cpu.idle” 所對應的值
功耗計算:
private void addIdleUsage() { long idleTimeMs = (mTypeBatteryRealtime - mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000; double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)) / (60*60*1000); if (idlePower != 0) { addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower); } }
子公式:
idlePower = (idleTimeMs * cpuIdlePower) / (60* 60* 1000)
3.4 Screen
屏幕功耗的類型為DrainType.SCREEN
屏幕功耗可配置項
- POWER_SCREEN_ON = “screen.on” 所對應的值
- POWER_SCREEN_FULL = “screen.full” 所對應的值
功耗計算:
private void addScreenUsage() { double power = 0; long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtime, mStatsType) / 1000; power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON); final double screenFullPower = mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { double screenBinPower = screenFullPower * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtime, mStatsType) / 1000; double p = screenBinPower*brightnessTime; power += p; } power /= (60*60*1000); // To hours if (power != 0) { addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power); } }
其中參數:
BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS = 5; static final String[] SCREEN_BRIGHTNESS_NAMES = { "dark", "dim", "medium", "light", "bright" }; static final String[] SCREEN_BRIGHTNESS_SHORT_NAMES = { "0", "1", "2", "3", "4" };
子公式:
公式:screen_power = screenOnTimeMs * screenOnPower + backlight_power
其中:backlight_power = 0.1 * dark_brightness_time * screenFullPower + 0.3 * dim_brightness_time * screenFullPower + 0.5 * medium_brightness_time * screenFullPower + 0.7 * light_brightness_time + 0.9 * bright_brightness_time * screenFullPower;
3.5 Wifi
(1)WifiPowerCalculater
Wifi的硬件功耗的類型DrainType.WIFI。
Wifi功耗可配置項
power_profile.xml文件:
- POWER_WIFI_CONTROLLER_IDLE = “wifi.controller.idle” 所對應的值
- POWER_WIFI_CONTROLLER_RX = “wifi.controller.rx” 所對應的值
- POWER_WIFI_CONTROLLER_TX = “wifi.controller.tx” 所對應的值
功耗計算:
wifi的硬件功耗是指除去App消耗之外的剩余wifi功耗
private void addWiFiUsage() { BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0); mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, mStatsType); //計算wifi硬件功耗 aggregateSippers(bs, mWifiSippers, "WIFI"); //將Wifi硬件合計入總功耗 if (bs.totalPowerMah > 0) { mUsageList.add(bs); } }
計算:
@Override public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, long rawUptimeUs, int statsType) { final long idleTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME, statsType); final long rxTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME, statsType); final long txTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME, statsType); app.wifiRunningTimeMs = idleTimeMs + rxTimeMs + txTimeMs; double powerDrainMah = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_POWER_DRAIN, statsType) / (double)(1000*60*60); if (powerDrainMah == 0) { //對於不直接報告功耗的組件,可通過直接計算的方式來獲取 powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) + (rxTimeMs * mRxCurrentMa)) / (1000*60*60); } app.wifiPowerMah = Math.max(0, powerDrainMah - mTotalAppPowerDrain); }
子公式:wifiPowerMah = powerDrainMah - mTotalAppPowerDrain;
powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) +(rxTimeMs * mRxCurrentMa);
mTotalAppPowerDrain是通過calculateApp計算過程中獲取,記錄所有App的wifi功耗值之和。
(2)WifiPowerEstimator
可配置項
POWER_WIFI_ON=”wifi.on”
功耗計算:
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, long rawUptimeUs, int statsType) { final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) / 1000; final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn) / (1000*60*60); app.wifiRunningTimeMs = totalRunningTimeMs; app.wifiPowerMah = Math.max(0, powerDrain); }
子公式:
wifiPowerMah = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn) / (1000* 60* 60);
3.6 Bluetooth
藍牙功耗的類型DrainType.BLUETOOTH
藍牙功耗可配置項
- POWER_BLUETOOTH_CONTROLLER_IDLE = “bluetooth.controller.idle” 所對應的值
- POWER_BLUETOOTH_CONTROLLER_RX = “bluetooth.controller.rx” 所對應的值
- POWER_BLUETOOTH_CONTROLLER_TX = “bluetooth.controller.tx” 所對應的值
功耗計算:
private void addBluetoothUsage() { BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0); mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, mStatsType); aggregateSippers(bs, mBluetoothSippers, "Bluetooth"); //將藍牙功耗合計入總功耗 if (bs.totalPowerMah > 0) { mUsageList.add(bs); } }
計算:
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, long rawUptimeUs, int statsType) { final long idleTimeMs = stats.getBluetoothControllerActivity( BatteryStats.CONTROLLER_IDLE_TIME, statsType); final long txTimeMs = stats.getBluetoothControllerActivity( BatteryStats.CONTROLLER_TX_TIME, statsType); final long rxTimeMs = stats.getBluetoothControllerActivity( BatteryStats.CONTROLLER_RX_TIME, statsType); final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs; double powerMah = stats.getBluetoothControllerActivity( BatteryStats.CONTROLLER_POWER_DRAIN, statsType) / (double)(1000*60*60); if (powerMah == 0) { //對於不直接報告功耗的組件,可通過直接計算的方式來獲取 powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa)) / (1000*60*60); } app.usagePowerMah = powerMah; app.usageTimeMs = totalTimeMs; }
子公式:
公式:usagePowerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa)) / (1000 * 60 * 60);
藍牙的計算與3.5 wifi計算方式相仿。
3.7 MobileRadio
無線電功耗的類型為DrainType.CELL
無線電功耗可配置項
power_profile.xml文件:
- POWER_RADIO_ON = “radio.on” 所對應的數組,該數組大小為
NUM_SIGNAL_STRENGTH_BINS = 5
- POWER_RADIO_SCANNING = “radio.scanning” 所對應的值
功耗計算:
對於只支持wifi的設備則不需要計算該項。
private void addRadioUsage() { BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0); mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtime, mRawUptime, mStatsType); radio.sumPower(); if (radio.totalPowerMah > 0) { mUsageList.add(radio); } }
計算:
@Override public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, long rawUptimeUs, int statsType) { double power = 0; long signalTimeMs = 0; long noCoverageTimeMs = 0; for (int i = 0; i < mPowerBins.length; i++) { long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType) / 1000; final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000); power += p; signalTimeMs += strengthTimeMs; if (i == 0) { noCoverageTimeMs = strengthTimeMs; } } final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType) / 1000; final double p = (scanningTimeMs * mPowerScan) / (60*60*1000); power += p; long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs; if (remainingActiveTimeMs > 0) { power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60); } if (power != 0) { if (signalTimeMs != 0) { app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; } app.mobileActive = remainingActiveTimeMs; app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType); app.mobileRadioPowerMah = power; } }
常量:
public static final int NUM_SIGNAL_STRENGTH_BINS = 5; public static final String[] SIGNAL_STRENGTH_NAMES = { "none", "poor", "moderate", "good", "great" };
子公式:
mobileRadioPowerMah = strengthOnPower + scanningPower + remainingActivePower
其中:strengthOnPower = none_strength_Ms * none_strength_Power + poor_strength_Ms * poor_strength_Power + moderate_strength_Ms * moderate_strength_Power + good_strength_Ms * good_strength_Power + great_strength_Ms * great_strength_Power;
scanningPower = scanningTimeMs * mPowerScan;
remainingActivePower = (radioActiveTimeMs - mTotalAppMobileActiveMs)* mPowerRadioOn;
硬件耗電總公式:
四、總結
(1) 關於WifiPowerCalculator && WifiPowerEstimator的選擇問題
在 BatteryStatsHelper.java中:
final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile); if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) { mWifiPowerCalculator = hasWifiPowerReporting ? new WifiPowerCalculator(mPowerProfile) : new WifiPowerEstimator(mPowerProfile); mHasWifiPowerReporting = hasWifiPowerReporting; }
當hasWifiPowerReporting = true時,采用WifiPowerCalculator算法;
當hasWifiPowerReporting = false時,采用WifiPowerEstimator算法;
其中:
public static boolean checkHasWifiPowerReporting(BatteryStats stats, PowerProfile profile) { return stats.hasWifiActivityReporting() && profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 && profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 && profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0; }
mHasWifiActivityReporting的默認值為false,故WIFI計算方式默認采用WifiPowerEstimator方式。
(2) 硬件與軟件排行榜,功耗項對比:
功耗項 | 軟件榜 | 硬件榜 |
---|---|---|
CPU | √ | √ |
無線電 | √ | √ |
WIFI | √ | √ |
藍牙 | √ | √ |
Wakelock | √ | - |
Sensor | √ | - |
相機 | √ | - |
閃光燈 | √ | - |
通話 | - | √ |
屏幕 | - | √ |
用戶 | - | √ |
其中√代表包含此功耗項。
(3) 硬件與軟件排行榜,可配置項對比:
對於wifi的統計存在兩種方式.
功耗項 | 軟件榜 | 硬件榜 |
---|---|---|
CPU | cpu.active | cpu.idle |
無線電 | radio.active | radio.on/scanning |
WIFI_Calculater | wifi.controller.idle/rx/tx | wifi.controller.idle/rx/tx |
WIFI_Estimator | wifi.on/ scan/ batchedscan | wifi.on |
藍牙 | bluetooth.controller.idle/rx/tx | bluetooth.controller.idle/rx/tx |
Wakelock | cpu.awake | - |
Sensor | gps.on | - |
相機 | camera.avg | - |
閃光燈 | camera.flashlight | - |
通話 | - | radio.active |
屏幕 | - | screen.on |
用戶 | - |
只有通過配置文件,精確地配置好每一項的基礎功耗值,才能有一個精確的功耗統計結果。