首页 > 编程语言 >PowerUsageSummary.java源码分析

PowerUsageSummary.java源码分析

时间:2023-01-11 21:22:05浏览次数:63  
标签:stats java sipper final 源码 mStatsHelper new PowerUsageSummary BatterySipper

在在线网站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 {
        // ....

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

UI更新

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,运行截图如下

END

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

标签:stats,java,sipper,final,源码,mStatsHelper,new,PowerUsageSummary,BatterySipper
From: https://www.cnblogs.com/Cl0ud/p/17044945.html

相关文章

  • 【Java篇】统计一个字符串中有几种字符,每个字符的个数(集合实现)
     主要考察的是对字符串中的方法的熟悉程度,以及对集合的熟悉程度因为要统计字符的个数,所以首先想到的是keyvalue形式的集合,也就是Map@Testpublicvoidtest(){......
  • 【java】冒泡Bubble算法
    冒泡Bubble算法 微信公众号:​​程序yuan​​关注可获得更多干货和视频教程哦。问题或建议,请公众号留言;面试中很常被考到的一道题,就是冒泡排序,可以说是非常经典了参考网上......
  • 【springboot异常】 Exception in thread "main" java.lang.UnsupportedClassVersionE
    微信公众号:​​程序yuan​​关注可获得更多干货哦!问题或建议,请公众号留言; 在我们运行SpringBoot应用的时候会遇到这样一个问题。Exceptioninthread"main"java.lang.U......
  • java 递归复制文件夹和文件
    公司的电脑是有安全检查的,每次复制一个文件到U盘中都要扫描半天,特别的慢,为了避开电脑的扫描,想到了java中的io流,写的一个使用递归的方式复制文件夹的demo。保存到博客中,跟大......
  • java高级--反射(reflect)
    1.获取Class对象的几种方式/***获取Class对象的三种方式*1Object——>getClass();*2任何数据类型(包括基本数据类型)都有一个“静态”的class属性*3通过Class类......
  • An Introduction to JavaScript
    AnIntroductiontoJavaScriptAnIntroductiontoJavaScriptLet’sseewhat’ssospecialaboutJavaScript,whatwecanachievewithit,andwhatothertechn......
  • 【学懂Java】(二)流程控制语句
    流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。包含顺序结构、分支结构、循环结构。顺序结构不必多说。一.分支结构if语......
  • Java程序的加载与执行
    Java程序的运行包括两个非常重要的阶段编译阶段运行阶段编译阶段编译阶段主要的任务是检查Java源程序是否符合Java语法,符合就能够正常生成字节码文件字节码文件......
  • 7.JavaScript--正则表达式
    实验原理正则表达式是用于处理字符串的强大工具,其他编程语言中也有正则表达式式的概念,区别只在于不同的编程语言实现支持的语法数量不同。它拥有自己的独特的语法以及一个......
  • Java发送HttpPost请求
    依赖jar包<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.6</version></dependency>发......