在分析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
建了一个微信的安全交流群,欢迎添加我微信备注进群
,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注 😃