4

PowerUsageSummary.java源码分析 - 春告鳥

 2 years ago
source link: https://www.cnblogs.com/Cl0ud/p/17044945.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.java源码分析 - 春告鳥 - 博客园

在在线网站http://androidxref.com/上对Android版本6.0.1_r10源码进行分析

官方手机的应用耗电排行具体实现位置在:/packages/apps/Settings/src/com/android/settings/fuelgauge/PowerUsageSummary.java

PowerUsageSummary类的作用是筛选耗电量最多的前十个应用并且展示



PowerUsageSummary`类继承自 `PowerUsageBase

开始的一部分的UI界面的创建和一些常量的定义,比如:

  • USE_FAKE_DATA,定义是否要使用假数据;
  • private BatteryHistoryPreference mHistPref;BatteryHistoryPreference类获取耗电量历史数据(读取sp文件)

sp文件数据来自power_usage_summary.xml文件

  • PreferenceGroup类:统计所有APP耗电量

主要目光放在refreshStats方法里



super.refreshStats();

跟进父类方法



protected void refreshStats() { mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, mUm.getUserProfiles()); }

BatteryStats.STATS_SINCE_CHARGED传入的是我们的计算规则

  • STATS_SINCE_CHARGED 上次充满电后数据
  • STATS_SINCE_UNPLUGGED 拔掉USB线后的数据

mUm.getUserProfiles() 是传入的多用户



mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);

这也是由Android的安全机制导致的,即多用户下的多应用

mStatsHelper.refreshStats方法现在我们只要知道是刷新当前的电量统计的就行

然后是一些UI的刷新,该部分略过



final PowerProfile powerProfile = mStatsHelper.getPowerProfile(); final BatteryStats stats = mStatsHelper.getStats(); final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);

可以看到mStatsHelper无处不在,实际上电量统计的核心实现就是该部分实现的

mStatsHelper.getPowerProfile()获取电源的配置信息,浅跟进一下



public PowerProfile getPowerProfile() { return mPowerProfile; }

初始化是在这里



public void create(BatteryStats stats) { mPowerProfile = new PowerProfile(mContext); mStats = stats; }


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 { // ....

20230111211426.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文件

mStatsHelper.getStats()返回BatteryStats对象,跟进可以发现实际上返回的是BatteryStatsImpl,它描述了所有与电量消耗有关的信息



final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);

见名知意,获取设备的平均耗电量,用于与阈值进行对比

这部分看上去是界面和主题的显示



TypedValue value = new TypedValue(); getContext().getTheme().resolveAttribute(android.R.attr.colorControlNormal, value, true); int colorControl = getContext().getColor(value.resourceId);

检查消耗的电量是否大于阈值,以及是否使用假数据,否则不显示应用耗电量



if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) {

根据UID进行合并分组



final List<BatterySipper> usageList = getCoalescedUsageList(USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList());

其中getCoalescedUsageList方法对UID进行分组

getFakeStats()方法返回一堆假数据



private static List<BatterySipper> getFakeStats() { ArrayList<BatterySipper> stats = new ArrayList<>(); float use = 5; for (DrainType type : DrainType.values()) { if (type == DrainType.APP) { continue; } stats.add(new BatterySipper(type, null, use)); use += 5; } stats.add(new BatterySipper(DrainType.APP, new FakeUid(Process.FIRST_APPLICATION_UID), use)); stats.add(new BatterySipper(DrainType.APP, new FakeUid(0), use));

// Simulate dex2oat process. BatterySipper sipper = new BatterySipper(DrainType.APP, new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID)), 10.0f); sipper.packageWithHighestDrain = "dex2oat"; stats.add(sipper);

sipper = new BatterySipper(DrainType.APP, new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID + 1)), 10.0f); sipper.packageWithHighestDrain = "dex2oat"; stats.add(sipper);

sipper = new BatterySipper(DrainType.APP, new FakeUid(UserHandle.getSharedAppGid(Process.LOG_UID)), 9.0f); stats.add(sipper);

return stats; }

mStatsHelper.getUsageList()返回BatterySipper数组,每个BatterySipper代表一个应用(uid)的消耗的电量信息

BatteryStatsHelper.java中的refreshStats方法中对mUsageList进行了赋值,这部分的具体操作在分析BatteryStatsHelper.java的时候再提



final int dischargeAmount = USE_FAKE_DATA ? 5000 : stats != null ? stats.getDischargeAmount(mStatsType) : 0;

这里的mStatsType值为



private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;

这里我们前面提过,含义是

  • STATS_SINCE_CHARGED 上次充满电后数据
  • STATS_SINCE_UNPLUGGED 拔掉USB线后的数据

所以这段的含义是获取上次充满电之后的电量消耗



stats.getDischargeAmount(mStatsType)

接下来遍历BatterySipper,对每一个UID代表的APP的耗电量进行过滤



final int numSippers = usageList.size(); for (int i = 0; i < numSippers; i++) { final BatterySipper sipper = usageList.get(i); if ((sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP) { continue; } double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower(); final double percentOfTotal = ((sipper.totalPowerMah / totalPower) * dischargeAmount); if (((int) (percentOfTotal + .5)) < 1) { continue; }

如果耗电功率小于阈值则不进行显示



if ((sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP) {

获取设备总耗电量



double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower();

计算占用总耗电量的百分比



final double percentOfTotal = ((sipper.totalPowerMah / totalPower) * dischargeAmount);

如果比例小于0.5,则不进行下一步操作



if (((int) (percentOfTotal + .5)) < 1) { continue; }

对某些情况进行过滤



if (sipper.drainType == BatterySipper.DrainType.OVERCOUNTED) { // Don't show over-counted unless it is at least 2/3 the size of // the largest real entry, and its percent of total is more significant if (sipper.totalPowerMah < ((mStatsHelper.getMaxRealPower()*2)/3)) { continue; } if (percentOfTotal < 10) { continue; } if ("user".equals(Build.TYPE)) { continue; } } if (sipper.drainType == BatterySipper.DrainType.UNACCOUNTED) { // Don't show over-counted unless it is at least 1/2 the size of // the largest real entry, and its percent of total is more significant if (sipper.totalPowerMah < (mStatsHelper.getMaxRealPower()/2)) { continue; } if (percentOfTotal < 5) { continue; } if ("user".equals(Build.TYPE)) { continue; } }

进行UI界面的更新,其中也包含了获取应用的icon图标



final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid())); final BatteryEntry entry = new BatteryEntry(getActivity(), mHandler, mUm, sipper); final Drawable badgedIcon = mUm.getBadgedIconForUser(entry.getIcon(), userHandle); final CharSequence contentDescription = mUm.getBadgedLabelForUser(entry.getLabel(), userHandle); final PowerGaugePreference pref = new PowerGaugePreference(getActivity(), badgedIcon, contentDescription, entry);

获取当前应用的最大百分比,以及占总数的百分比



final double percentOfMax = (sipper.totalPowerMah * 100) / mStatsHelper.getMaxPower(); sipper.percent = percentOfTotal;


pref.setTitle(entry.getLabel()); pref.setOrder(i + 1); pref.setPercent(percentOfMax, percentOfTotal); if (sipper.uidObj != null) { pref.setKey(Integer.toString(sipper.uidObj.getUid())); } if ((sipper.drainType != DrainType.APP || sipper.uidObj.getUid() == 0) && sipper.drainType != DrainType.USER) { pref.setTint(colorControl); } addedSome = true; mAppListGroup.addPreference(pref); if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST + 1)) { break; }

其中这里对显示的数量进行了限制



if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST + 1)) { break; }

MAX_ITEMS_TO_LIST的赋值



private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10;

循环外有对addedSome的判断



if (!addedSome) { addNotAvailableMessage(); }

实际上就是判断是不是有符合要求的耗电应用,如果没有的话,就显示一条提示信息



private void addNotAvailableMessage() { Preference notAvailable = new Preference(getActivity()); notAvailable.setTitle(R.string.power_usage_not_available); mAppListGroup.addPreference(notAvailable); }

这部分就是PowerUsageSummary.java文件获取Settings电池中显示的应用耗电量信息,根据我们上面的分析,实际上控制上面的continue就能获取全部已安装应用的耗电量。在Android的不同API版本中,会有一些适配的工作量

关于申请权限,普通应用是没有办法获取到应用耗电量信息的,系统会抛出异常

java.lang.SecurityException: uid 10089 does not have android.permission.BATTERY_STATS.

如果想要进行相关API的调用,首先应用需要配置android.uid.system成为系统应用,并且进行系统签名,才能够拥有相关权限,本地编译的话需要调用Android的internal接口,我使用的是替换本地android.jar才可以正常打包出apk文件

本地编写了一个获取Android应用耗电量的demo,运行截图如下

20230111211449.png

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

GIF
GIF

__EOF__


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK