之前都没有写博客的习惯,工作上的一些想法和思路过一段时间就忘的差不多了。最近在做Settings相关功能的SDK定制,所以想趁这个机会把手上遇到的思路和解决方法整理下,方便日后回顾。想做成一个系列,其中有的也是参考网络大神的思路,有的是翻看源码整理的,请大家多多关照。
Android的电量信息通常都是指设备的电量信息,例如充电状态、耗电量、充电方式等等。常用的获取方式是注册“Intent.ACTION_BATTERY_CHANGED”广播获取,并且需要申请“<uses-permission android:name="android.permission.BATTERY_STATS"/>”权限。如果想获取某个应用对应的耗电量呢,要怎么做?Settings已经提供了实现方法,我们可以参考源码来实现。
具体实现在Settings “com.android.settings.fuelgauge”路径下,主要入口是PowerUsageSummary.java,该类继承自PowerUsageBase.java,其实就是一个“SettingsPreferenceFragment”。在排除UI方面的创建实现后,关注“refreshStats()”这个方法就行了,所有有关数据的处理都在这个方法里面实现。以下是代码,其中跟UI有关的代码就不贴了,具体分析见注释。
(此处先插入对于BatteryStatsHelper的分析,略读refreshStats()方法,可发现主要的数据源是从BatteryStatsHelper中获取的,而BatteryStatsHelper在PowerUsageBase.java中定义,在其生命周期中,主要做了一下几个操作:
//onAttach 中调用,创建实例
mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);
mStatsHelper = new BatteryStatsHelper(activity, true);
//onCreate 中调用,初始化BatteryStatsHelper
mStatsHelper.create(icicle);
//onStart 中调用,清除历史数据
mStatsHelper.clearStats();
//onResume 中调用,删除历史数据文件,并注册电量状态广播,同时再次清除历史数据
BatteryStatsHelper.dropFile(getActivity(), BatteryHistoryPreference.BATTERY_HISTORY_FILE);
updateBatteryStatus(getActivity().registerReceiver(mBatteryInfoReceiver,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED)));
if (mHandler.hasMessages(MSG_REFRESH_STATS)) {
mHandler.removeMessages(MSG_REFRESH_STATS);
mStatsHelper.clearStats();
}
//子类中会重写该方法,主要是在UserProfile和BatteryStatsHelper之间建立连接
protected void refreshStats() {
mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, mUm.getUserProfiles());
}
这些操作需要在从BatteryStatsHelper中获取数据前执行,如果想自己封装方法的话,在创建BatteryStatsHelper实例后执行即可)
/**
* 该方法执行前,上述的关于BatteryStatsHelper的几个初始化方法已经执行。
*/
protected void refreshStats() {
super.refreshStats();//调用父类的更新方法,更新UserProfile配置
...
...//此处省略关于UI的更新实现
...
//电源配置信息
final PowerProfile powerProfile = mStatsHelper.getPowerProfile();
//电池状态信息
final BatteryStats stats = mStatsHelper.getStats();
//获取平均耗电量,用于和阈值对比
final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
...
...//省略获取UI相关的数据
...
//如果平均耗电量小于阈值,或不使用假数据的话,就不显示应用耗电量
if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) {
//根据UID进行合并分组,
final List<BatterySipper> usageList = getCoalescedUsageList(
//getFakeStat 伪造数据显示
USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList());
//获取上次充满电后消耗的电量。
//mStatsType的值为BatteryStats.STATS_SINCE_CHARGED,代表了我们的计算规则是从上次充满电后数据,还有一种规则是STATS_SINCE_UNPLUGGED是拔掉USB线后的数据
final int dischargeAmount = USE_FAKE_DATA ? 5000
: stats != null ? stats.getDischargeAmount(mStatsType) : 0;
final int numSippers = usageList.size();
//遍历BatterySipper进行处理
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);
//如果百分比小于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更新
...
final double percentOfMax = (sipper.totalPowerMah * 100)
/ mStatsHelper.getMaxPower();
sipper.percent = percentOfTotal;
...
...
...省略UI更新
}
}
...
...//省略UI更新
...
}
至此,应用就可以获取到Settings电池中显示的应用耗电量信息。如果想获取全部已安装应用的耗电量,则控制上面continue的判断即可,全部屏蔽就可以获取全部的应用耗电量。在不同的API版本,有些方法可能被隐藏起来,需要用反射的方式获取。还有的计算策略有偏差,或者干脆某个参数被舍弃了,这些差异只要查看源码就可以了解。
最重要的权限忘记说了,应用需要申请如下权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.DEVICE_POWER"/>
<uses-permission android:name="android.permission.BATTERY_STATS"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
并且对于普通应用来说,是没有权限通过这种方式获取应用耗电量信息的,系统会抛出如下异常:
java.lang.SecurityException: uid 10089 does not have android.permission.BATTERY_STATS.
应用需要配置android.uid.system,并且进行系统签名:就是获取系统平台下的这两个文件,然后用java -jar命令进行签名。
稍后会将源码上传,仅供参考。如果需要运行的话,需要将对应系统的上述两个文件放在sign目录下,并用install.sh中的命令签名后再安装。