BatteryStatsHelper.java原始碼分析

春告鳥發表於2023-01-14

在分析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.xmlpower_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);
    }
}

這篇已經太長了,關於軟硬體的耗電量計算就在另外一篇裡面寫吧

參考連結

END

建了一個微信的安全交流群,歡迎新增我微信備註進群,一起來聊天吹水哇,以及一個會發布安全相關內容的公眾號,歡迎關注 ?

GIF GIF

相關文章