4

BatteryStatsHelper.java源码分析 - 春告鳥

 1 year ago
source link: https://www.cnblogs.com/Cl0ud/p/17051836.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在分析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 { // ....

20230114143529.png

在这里需要提一下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); } }

这篇已经太长了,关于软硬件的耗电量计算就在另外一篇里面写吧

建了一个微信的安全交流群,欢迎添加我微信备注进群,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注 😃

GIF
GIF

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK