在分析PowerUsageSummary
的時候,其實可以發現主要獲取應用和服務電量使用情況的實現是在BatteryStatsHelper.java
中
還是線上網站http://androidxref.com/上對Android版本6.0.1_r10原始碼進行分析
具體位置在 /frameworks/base/core/java/com/android/internal/os/BatteryStatsHelper.java
create方法
檢視構造方法
public BatteryStatsHelper(Context context) {
this(context, true);
}
public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
this(context, collectBatteryBroadcast, checkWifiOnly(context));
}
public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
mContext = context;
mCollectBatteryBroadcast = collectBatteryBroadcast;
mWifiOnly = wifiOnly;
}
設定是否需要註冊BATTERY_CHANGED
駐留廣播,該廣播監聽系統電池電量和充電狀態
mCollectBatteryBroadcast = collectBatteryBroadcast;
裝置是否只有wifi,無行動網路,比如說平板或者車機,有的就是不能插SIM卡的
mWifiOnly = wifiOnly;
檢視create方法
public void create(BatteryStats stats) {
mPowerProfile = new PowerProfile(mContext);
mStats = stats;
}
public void create(Bundle icicle) {
if (icicle != null) {
mStats = sStatsXfer;
mBatteryBroadcast = sBatteryBroadcastXfer;
}
mBatteryInfo = IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME));
mPowerProfile = new PowerProfile(mContext);
}
其中都獲取了PowerProfile物件
mPowerProfile = new PowerProfile(mContext);
PowerProfile建立
持續跟進
public PowerProfile(Context context) {
// Read the XML file for the given profile (normally only one per
// device)
if (sPowerMap.size() == 0) {
readPowerValuesFromXml(context);
}
initCpuClusters();
}
可以看到這裡有一段註釋: Read the XML file for the given profile (normally only one perdevice
跟進readPowerValuesFromXml
方法,其實這個方法就是用來解析power_profile.xml
檔案的,該檔案在原始碼中的位置為 /frameworks/base/core/res/res/xml/power_profile.xml
,power_profile.xml
是一個可配置的功耗資料檔案
private void readPowerValuesFromXml(Context context) {
int id = com.android.internal.R.xml.power_profile;
final Resources resources = context.getResources();
XmlResourceParser parser = resources.getXml(id);
boolean parsingArray = false;
ArrayList<Double> array = new ArrayList<Double>();
String arrayName = null;
try {
// ....
在這裡需要提一下Android中對於應用和硬體的耗電量計算方式:
有一張“價格表”,記錄每種硬體1秒鐘耗多少電。有一張“購物清單”,記錄apk使用了哪幾種硬體,每種硬體用了多長時間。假設某個應用累計使用了60秒的cpu,cpu1秒鐘耗1mAh,那這個應用就消耗了60mAh的電
這裡的價格表就是我們找到的power_profile.xml
檔案,手機的硬體是各不相同的,所以每一款手機都會有一張自己的"價格表",這張表的準確性由手機廠商負責。
這也是為什麼我們碰到讀取xml檔案的時候註釋裡面會有normally only one perdevice
如果我們想要看自己手機的power_profile.xml檔案咋辦,它會儲存在手機的/system/framework/framework-res.apk
路徑中,我們可以將它pull出來,透過反編譯的手法獲得power_profile.xml
檔案
refreshStats方法
接著可以看到過載的refreshStats
/**
* Refreshes the power usage list.
*/
public void refreshStats(int statsType, int asUser) {
SparseArray<UserHandle> users = new SparseArray<>(1);
users.put(asUser, new UserHandle(asUser));
refreshStats(statsType, users);
}
/**
* Refreshes the power usage list.
*/
public void refreshStats(int statsType, List<UserHandle> asUsers) {
final int n = asUsers.size();
SparseArray<UserHandle> users = new SparseArray<>(n);
for (int i = 0; i < n; ++i) {
UserHandle userHandle = asUsers.get(i);
users.put(userHandle.getIdentifier(), userHandle);
}
refreshStats(statsType, users);
}
/**
* Refreshes the power usage list.
*/
public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {
refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
SystemClock.uptimeMillis() * 1000);
}
refreshStats
是重新整理電池使用資料的介面,向上提供資料,其中的具體實現在
public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
long rawUptimeUs) {
// Initialize mStats if necessary.
getStats();
mMaxPower = 0;
mMaxRealPower = 0;
mComputedPower = 0;
mTotalPower = 0;
mUsageList.clear();
mWifiSippers.clear();
mBluetoothSippers.clear();
mUserSippers.clear();
mMobilemsppList.clear();
if (mStats == null) {
return;
}
if (mCpuPowerCalculator == null) {
mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
mCpuPowerCalculator.reset();
if (mWakelockPowerCalculator == null) {
mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
}
mWakelockPowerCalculator.reset();
if (mMobileRadioPowerCalculator == null) {
mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
}
mMobileRadioPowerCalculator.reset(mStats);
// checkHasWifiPowerReporting can change if we get energy data at a later point, so
// always check this field.
final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile);
if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) {
mWifiPowerCalculator = hasWifiPowerReporting ?
new WifiPowerCalculator(mPowerProfile) :
new WifiPowerEstimator(mPowerProfile);
mHasWifiPowerReporting = hasWifiPowerReporting;
}
mWifiPowerCalculator.reset();
final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats,
mPowerProfile);
if (mBluetoothPowerCalculator == null ||
hasBluetoothPowerReporting != mHasBluetoothPowerReporting) {
mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
mHasBluetoothPowerReporting = hasBluetoothPowerReporting;
}
mBluetoothPowerCalculator.reset();
if (mSensorPowerCalculator == null) {
mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
(SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
}
mSensorPowerCalculator.reset();
if (mCameraPowerCalculator == null) {
mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile);
}
mCameraPowerCalculator.reset();
if (mFlashlightPowerCalculator == null) {
mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
}
mFlashlightPowerCalculator.reset();
mStatsType = statsType;
mRawUptime = rawUptimeUs;
mRawRealtime = rawRealtimeUs;
mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs);
mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs);
mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs);
if (DEBUG) {
Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime="
+ (rawUptimeUs/1000));
Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime="
+ (mBatteryUptime/1000));
Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime="
+ (mTypeBatteryUptime/1000));
}
mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
* mPowerProfile.getBatteryCapacity()) / 100;
mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
* mPowerProfile.getBatteryCapacity()) / 100;
processAppUsage(asUsers);
// Before aggregating apps in to users, collect all apps to sort by their ms per packet.
for (int i=0; i<mUsageList.size(); i++) {
BatterySipper bs = mUsageList.get(i);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
for (int i=0; i<mUserSippers.size(); i++) {
List<BatterySipper> user = mUserSippers.valueAt(i);
for (int j=0; j<user.size(); j++) {
BatterySipper bs = user.get(j);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
}
Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
@Override
public int compare(BatterySipper lhs, BatterySipper rhs) {
return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
}
});
processMiscUsage();
Collections.sort(mUsageList);
// At this point, we've sorted the list so we are guaranteed the max values are at the top.
// We have only added real powers so far.
if (!mUsageList.isEmpty()) {
mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
final int usageListCount = mUsageList.size();
for (int i = 0; i < usageListCount; i++) {
mComputedPower += mUsageList.get(i).totalPowerMah;
}
}
if (DEBUG) {
Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
+ makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
}
mTotalPower = mComputedPower;
if (mStats.getLowDischargeAmountSinceCharge() > 1) {
if (mMinDrainedPower > mComputedPower) {
double amount = mMinDrainedPower - mComputedPower;
mTotalPower = mMinDrainedPower;
BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);
// Insert the BatterySipper in its sorted position.
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
} else if (mMaxDrainedPower < mComputedPower) {
double amount = mComputedPower - mMaxDrainedPower;
// Insert the BatterySipper in its sorted position.
BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
}
}
}
我們依次分析
SparseArray<UserHandle> asUsers
UserHanler代表裝置上的一個使用者long rawRealtimeUs
系統開機後的執行時間long rawUptimeUs
系統不包括休眠的執行時間
public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
long rawUptimeUs) {
初始化Stats操作
getStats()
如果mStats為空,則初始化
public BatteryStats getStats() {
if (mStats == null) {
load();
}
return mStats;
}
mMaxPower = 0; // 最大耗電量
mMaxRealPower = 0; // 最大真實耗電量
mComputedPower = 0; // 透過耗電計算器計算的耗電量總和
mTotalPower = 0; // 總的耗電量
重新整理耗電量之前需要先清空之前的資料,clear都是清空操作
mUsageList.clear(); // 儲存了BatterySipper列表,各類耗電量都儲存在BatterySipper中,BatterySipper儲存在mUsageList中
mWifiSippers.clear(); // 在統計軟體耗電過程中使用到WIFI的應用,其對應的BatterySipper列表
mBluetoothSippers.clear(); // 在統計軟體耗電過程中使用到BlueTooth的應用,其對應的BatterySipper列表
mUserSippers.clear(); // 裝置上有多個使用者時,儲存了其他使用者的耗電資訊的SparseArray資料,鍵為userId,值為對應的List<BatterySipper>
mMobilemsppList.clear(); // 儲存有資料接收和傳送的BatterySipper物件的列表
初始化八大模組的耗電計算器,都繼承於PowerCalculator
抽象類,八大模組在processAppUsage
方法中進行分析,這裡只需要知道有哪八個以及進行的操作是初始化即可
計算項 | Class檔案 |
---|---|
CPU功耗 | mCpuPowerCalculator.java |
Wakelock功耗 | mWakelockPowerCalculator.java |
無線電功耗 | mMobileRadioPowerCalculator.java |
WIFI功耗 | mWifiPowerCalculator.java |
藍芽功耗 | mBluetoothPowerCalculator.java |
Sensor功耗 | mSensorPowerCalculator.java |
相機功耗 | mCameraPowerCalculator.java |
閃光燈功耗 | mFlashlightPowerCalculator.java |
if (mCpuPowerCalculator == null) {
mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
mCpuPowerCalculator.reset();
if (mWakelockPowerCalculator == null) {
mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
}
mWakelockPowerCalculator.reset();
if (mMobileRadioPowerCalculator == null) {
mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
}
mMobileRadioPowerCalculator.reset(mStats);
// checkHasWifiPowerReporting can change if we get energy data at a later point, so
// always check this field.
final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile);
if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) {
mWifiPowerCalculator = hasWifiPowerReporting ?
new WifiPowerCalculator(mPowerProfile) :
new WifiPowerEstimator(mPowerProfile);
mHasWifiPowerReporting = hasWifiPowerReporting;
}
mWifiPowerCalculator.reset();
final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats,
mPowerProfile);
if (mBluetoothPowerCalculator == null ||
hasBluetoothPowerReporting != mHasBluetoothPowerReporting) {
mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
mHasBluetoothPowerReporting = hasBluetoothPowerReporting;
}
mBluetoothPowerCalculator.reset();
if (mSensorPowerCalculator == null) {
mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
(SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
}
mSensorPowerCalculator.reset();
if (mCameraPowerCalculator == null) {
mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile);
}
mCameraPowerCalculator.reset();
if (mFlashlightPowerCalculator == null) {
mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
}
mFlashlightPowerCalculator.reset();
電量統計需要先設定統計時間段,透過設定統計型別mStatsType變數來表示
mStatsType = statsType;
有三種可選值
// 統計從上一次充電以來至現在的耗電量
public static final int STATS_SINCE_CHARGED = 0;
// 統計系統啟動以來到現在的耗電量
public static final int STATS_CURRENT = 1;
// 統計從上一次拔掉USB線以來到現在的耗電量
public static final int STATS_SINCE_UNPLUGGED = 2;
當前系統的執行時間
mRawUptimeUs = rawUptimeUs;
當前系統的真實執行時間,包括休眠時間
mRawRealtimeUs = rawRealtimeUs;
剩下的也是一堆時間
mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs); // 電池放電執行時間
mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs); // 電池真實放電執行時間,包含休眠時間
mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType); // 對應型別的電池放電執行時間,如上次充滿電後的電池執行時間
mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType); // 對應型別的電池放電執行時間,包括休眠時間
mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs); // 電池預計使用時長
mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs); // 電池預計多久充滿時長
DEBUG模式下會輸出時間日誌,這不重要
if (DEBUG) {
Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime="
+ (rawUptimeUs/1000));
Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime="
+ (mBatteryUptime/1000));
Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime="
+ (mTypeBatteryUptime/1000));
}
計算最低和最高的電量近似值
該方法待會詳細說明,現在我們只需要知道它主要進行統計APP軟體的耗電量操作,統計之後會將每種型別,每個UID的耗電值儲存在對應的BatterySipper
中
processAppUsage(asUsers);
對每個應用程式的每毫秒ms接收和傳送的資料包mobilemspp
進行排序
for (int i=0; i<mUsageList.size(); i++) {
BatterySipper bs = mUsageList.get(i);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
// 遍歷其他使用者的耗電情況
for (int i=0; i<mUserSippers.size(); i++) {
List<BatterySipper> user = mUserSippers.valueAt(i);
for (int j=0; j<user.size(); j++) {
BatterySipper bs = user.get(j);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
}
對mMobilemsppList
進行排序
Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
@Override
public int compare(BatterySipper lhs, BatterySipper rhs) {
return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
}
});
計算硬體的耗電量,跟前面的processAppUsage(asUsers);
對應,這兩個方法我們都後面再說
processMiscUsage();
對軟硬體耗電量結果進行降序排序
Collections.sort(mUsageList);
獲取最大耗電量
因為我們剛才進行了排序,所以耗電最多的硬體/軟體正位於頂部,賦值mMaxRealPower
最大真實耗電量
遍歷usageList
計算得到mComputedPower
耗電量總和
if (!mUsageList.isEmpty()) {
mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
final int usageListCount = mUsageList.size();
for (int i = 0; i < usageListCount; i++) {
mComputedPower += mUsageList.get(i).totalPowerMah;
}
}
如果存在未計算到的耗電量,例項化一個DrainType.UNACCOUNTED
型別的BatterySipper
進行儲存,並新增到mUsageList
中
mTotalPower = mComputedPower;
if (mStats.getLowDischargeAmountSinceCharge() > 1) {
// 如果最低放電量 > 計算的總耗電量,說明還有未計算的
if (mMinDrainedPower > mComputedPower) {
double amount = mMinDrainedPower - mComputedPower;
mTotalPower = mMinDrainedPower;
// 例項化一個DrainType.UNACCOUNTED型別的BatterySipper,用來儲存未計算的耗電量
BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);
// Insert the BatterySipper in its sorted position.
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
}
如果存在計算多了的耗電量,例項化一個DrainType.OVERCOUNTED
型別的BatterySipper
進行儲存,並新增到mUsageList
中
// 如果最高放電量 < 計算的總耗電量,說明多算了耗電量
else if (mMaxDrainedPower < mComputedPower) {
double amount = mComputedPower - mMaxDrainedPower;
// Insert the BatterySipper in its sorted position.
BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
}
}
這篇已經太長了,關於軟硬體的耗電量計算就在另外一篇裡面寫吧
參考連結
- https://blog.csdn.net/FightFightFight/article/details/82694381
- http://gityuan.com/2016/01/10/power_rank/
- https://duanqz.github.io/2015-07-21-batterystats-part1
- http://androidxref.com/6.0.1_r10/xref//frameworks/base/core/java/com/android/internal/os/BatteryStatsHelper.java
END
建了一個微信的安全交流群,歡迎新增我微信備註進群
,一起來聊天吹水哇,以及一個會發布安全相關內容的公眾號,歡迎關注 ?