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.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
文件
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
建了一个微信的安全交流群,欢迎添加我微信备注进群
,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注
PowerUsageSummary.java源码分析的更多相关文章
- Java源码分析 | CharSequence
本文基于 OracleJDK 11, HotSpot 虚拟机. CharSequence 定义 CharSequence 是 java.lang 包下的一个接口,是 char 值的可读序列, 即其本身 ...
- Java源码分析:关于 HashMap 1.8 的重大更新(转载)
http://blog.csdn.net/carson_ho/article/details/79373134 前言 HashMap 在 Java 和 Android 开发中非常常见 而HashMap ...
- JAVA源码分析-HashMap源码分析(二)
本文继续分析HashMap的源码.本文的重点是resize()方法和HashMap中其他的一些方法,希望各位提出宝贵的意见. 话不多说,咱们上源码. final Node<K,V>[] r ...
- Java源码分析之LinkedList
LinkedList与ArrayList正好相对,同样是List的实现类,都有增删改查等方法,但是实现方法跟后者有很大的区别. 先归纳一下LinkedList包含的API 1.构造函数: ①Linke ...
- Java源码分析:Guava之不可变集合ImmutableMap的源码分析
一.案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样-- public static final Map<String,String& ...
- JAVA源码分析-HashMap源码分析(一)
一直以来,HashMap就是Java面试过程中的常客,不管是刚毕业的,还是工作了好多年的同学,在Java面试过程中,经常会被问到HashMap相关的一些问题,而且每次面试都被问到一些自己平时没有注意的 ...
- 【转】【java源码分析】Map中的hash算法分析
全网把Map中的hash()分析的最透彻的文章,别无二家. 2018年05月09日 09:08:08 阅读数:957 你知道HashMap中hash方法的具体实现吗?你知道HashTable.Conc ...
- 【Java源码分析】LinkedList类
LinkedList<E> 源码解读 继承AbstractSequentialList<E> 实现List<E>, Deque<E>, Cloneabl ...
- 一致性哈希Java源码分析
首次接触一致性哈希是在学习memcached的时候,为了解决分布式服务器的负载均衡或者说选路的问题,一致性哈希算法不仅能够使memcached服务器被选中的概率(数据分布)更加均匀,而且使得服务器的增 ...
- JAVA源码分析------锁(1)
http://870604904.iteye.com/blog/2258604 第一次写博客,也就是记录一些自己对于JAVA的一些理解,不足之处,请大家指出,一起探讨. 这篇博文我打算说一下JAVA中 ...
随机推荐
- Selenium+Python系列 - 开发环境搭建
一.写在前面 我从未想过自己会写python系列的自动化文章,有些同学会问,那你现在为什么又开始写了? 不止一个人找过我,问我可以写一些Python自动化的文章吗,答案是肯定的. 笔者Java党,整P ...
- 小程序返回上一级页面背景音乐报错 setBackgroundAudioState:fail title is nil!;
小程序初始化在onLoad的时候加载了一次背景音乐. 如果此时报错是title必传.如果没有 会报错一次 setBackgroundAudioState:fail title is nil!; 这个都 ...
- Docker_基础知识
容器概述 容器本义:盛装物体.隔离物体. 容器意义:解决虚拟化资源浪费的问题. 容器沿革:1979---2013--- 版本:企业版(EE)/社区版(CE)1. ...
- 齐博x1给表单某个字段设置初始值
自定义表单虽然后台可以设置默认初始值,但是有时候想在前台动态设置初始值的话,可以在URL中添加该字段名,给他动态赋值即可.比如下面的price字段就是动态赋值的.
- 知识图谱-生物信息学-医学顶刊论文(Briefings in Bioinformatics-2021):MPG:一种有效的自我监督框架,用于学习药物分子的全局表示以进行药物发现
6.(2021.9.14)Briefings-MPG:一种有效的自我监督框架,用于学习药物分子的全局表示以进行药物发现 论文标题:An effective self-supervised framew ...
- vue3 页面跳转
需要引入 useRouter import {useRouter} from "vue-router"; 然后声明对象 代码如下 export default { setup() ...
- Jmeter之聚合报告“造假”
通过Jmeter,模拟一个"虚假"的聚合报告,可"应付"日常现场项目的性能测试验收.本文档着重介绍jmeter的固定定时器,通过设置随机的延迟时间(如想业务场景 ...
- jvm之垃圾收集一之垃圾回收算法
最近又重新在读深入理解java虚拟机一书,吸取第一次读完到现在已经忘记的差不都的教训,这次的学习之旅想通过博客的形式记录下自己的所学所感,以备后续继续学习备忘所用!这次先记录下垃圾收集相关知识点: 垃 ...
- java学习之EL和JSTL
0x00前言 EL和JSTL都是JSP的内容的拓展,都是开发的一些东西,稍微学习记录一下,避免以后忘记 0x01EL 0x1基本用法 概念:Expression language 表达式语言 作用:替 ...
- 嵌入式-C语言基础:快速选择排序实现从大到小排序
#include<stdio.h> int main() { /*简单选择排序:从大到小:一共比较sizeArr-1轮,每一轮的第一个数是arr[i],第一个数依次和它后面的每个数比较*/ ...