现在诸多关于电池管理的应用做的极其绚烂,可实现如耗电应用排行、剩余时间计算、关闭耗电程序以节省电量等功能,这两天对此模块进行了研究,总结如下。
首先解释下各软硬件耗电量的计算。假设设备(如WIFI)单位时间内消耗的电量为w,运行时间为t,则其在这段时间内的耗电量为W=w*t。根据物理学中的知识,电功率(即所谓电量)计算公式为W=UIt,其中U为电压值,I为电流值,t为运行时间。由于在一部机器中,电压值U是恒定不变的(一般如此),因此可以忽略掉参数U,单独通过电流及时间即可表示电量(比如电池容量为2000mA、2500mA等,以mA为单位进行恒量)。根据以上描述,只要我们获得了某程序或某设备运行的时间,以及其运行时所需要电流值,则可以计算出其消耗的电量(以上理论会在代码中体现)。
某程序或硬件设备的运行时间可以分别通过BatteryStats.Uid.Proc和BatteryStatsImpl中的相关接口获得(后文分析代码时会见到),本文主要讲下电流值(即单位时间消耗电量)的获取。
PowerProfile.java
OK,不多废话,直接给出系统中提供的接口--./frameworks/base/core/java/com/android/internal/os/PowerProfile.java。
/**
* Reports power consumption values for various device activities. Reads values from an XML file.
* Customize the XML file for different devices.
* [hidden]
*/
public class PowerProfile {
先看下这个类的注释:此类就是用来报告各种设备活动的耗电量值,这个值是从一个xml文件(power_profile.xml)中读取,并且这个xml文件是定制化的,即不同的设备有不同的值。
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();
}
初始时sPowerMap(用来保存子系统类型)为空,则从power_profile.xml文件中读取子系统类型,并且调用initCpuClusters()方法得到CPU的性能指标。
此类提供了如下接口:
(1)、public double getAveragePower(String type)
/**
* Returns the average current in mA consumed by the subsystem
* @param type the subsystem type
* @return the average current in milliAmps.返回子系统消耗的平均电流值(mA)
*/
public double getAveragePower(String type) {
return getAveragePowerOrDefault(type, 0);
}
/**
* Returns the average current in mA consumed by the subsystem for the given level.
* @param type the subsystem type
* @param level the level of power at which the subsystem is running. For instance, the
* signal strength of the cell network between 0 and 4 (if there are 4 bars max.)单元网络的信号强度在0-4之间
* If there is no data for multiple levels, the level is ignored.子系统运行时的功率级别。
* @return the average current in milliAmps.
*/
public double getAveragePower(String type, int level) {
if (sPowerMap.containsKey(type)) {
Object data = sPowerMap.get(type);
if (data instanceof Double[]) {
final Double[] values = (Double[]) data;
if (values.length > level && level >= 0) {
return values[level];
} else if (level < 0 || values.length == 0) {
return 0;
} else {
return values[values.length - 1];
}
} else {
return (Double) data;
}
} else {
return 0;
}
}
此方法返回在type子系统下消耗的电流值,单位为mA。type可取PowerProfile中定义的常量值,包括POWER_CPU_IDLE(CPU空闲时),POWER_CPU_ACTIVE(CPU处于活动时),POWER_WIFI_ON(WIFI开启时)等各种状态。例如,如下调用getAveragePower(POWER_CPU_ACTIVE)将返回CPU处于活动时的电流值;getAveragePower(POWER_WIFI_ON)将返回维持WIFI启动状态所需的电流值。结合之前的描述,假设WIFI开启的时间为t(假设此段时间未使用WIFI传输数据,因为WIFI传输数据需要额外的电能消耗),那么在此段时间内WIFI所消耗的电量为W=t*getAveragePower(POWER_WIFI_ON)。
type可取如下值:
- /**
- * No power consumption, or accounted for elsewhere.
- */
- public static final String POWER_NONE = "none";
- /**
- * Power consumption when CPU is in power collapse mode.
- */
- public static final String POWER_CPU_IDLE = "cpu.idle";
- /**
- * Power consumption when CPU is awake (when a wake lock is held). This
- * should be 0 on devices that can go into full CPU power collapse even
- * when a wake lock is held. Otherwise, this is the power consumption in
- * addition to POWERR_CPU_IDLE due to a wake lock being held but with no
- * CPU activity.
- */
- public static final String POWER_CPU_AWAKE = "cpu.awake";
- /**
- * Power consumption when CPU is in power collapse mode.
- */
- public static final String POWER_CPU_ACTIVE = "cpu.active";
- /**
- * Power consumption when WiFi driver is scanning for networks.
- */
- public static final String POWER_WIFI_SCAN = "wifi.scan";
- /**
- * Power consumption when WiFi driver is on.
- */
- public static final String POWER_WIFI_ON = "wifi.on";
- /**
- * Power consumption when WiFi driver is transmitting/receiving.
- */
- public static final String POWER_WIFI_ACTIVE = "wifi.active";
- /**
- * Power consumption when GPS is on.
- */
- public static final String POWER_GPS_ON = "gps.on";
- /**
- * Power consumption when Bluetooth driver is on.
- */
- public static final String POWER_BLUETOOTH_ON = "bluetooth.on";
- /**
- * Power consumption when Bluetooth driver is transmitting/receiving.
- */
- public static final String POWER_BLUETOOTH_ACTIVE = "bluetooth.active";
- /**
- * Power consumption when Bluetooth driver gets an AT command.
- */
- public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
- /**
- * Power consumption when screen is on, not including the backlight power.
- */
- public static final String POWER_SCREEN_ON = "screen.on";
- /**
- * Power consumption when cell radio is on but not on a call.
- */
- public static final String POWER_RADIO_ON = "radio.on";
- /**
- * Power consumption when cell radio is hunting for a signal.
- */
- public static final String POWER_RADIO_SCANNING = "radio.scanning";
- /**
- * Power consumption when talking on the phone.
- */
- public static final String POWER_RADIO_ACTIVE = "radio.active";
- /**
- * Power consumption at full backlight brightness. If the backlight is at
- * 50% brightness, then this should be multiplied by 0.5
- */
- public static final String POWER_SCREEN_FULL = "screen.full";
- /**
- * Power consumed by the audio hardware when playing back audio content. This is in addition
- * to the CPU power, probably due to a DSP and / or amplifier.
- */
- public static final String POWER_AUDIO = "dsp.audio";
- /**
- * Power consumed by any media hardware when playing back video content. This is in addition
- * to the CPU power, probably due to a DSP.
- */
- public static final String POWER_VIDEO = "dsp.video";
代码段1 PowerProfile.java中定义的子系统类型
(2) 、public double getAveragePower(String type, int level)
相比于方法(1),此接口需要传入参数level,现在来解释下level的含义。我们知道,android系统中CPU可以以多种速度运行(假设分别为600MHz,800MHz,1GHZ等),速度不同时CPU消耗的电量也不同,参数level即代表不同的运行频率,显然,方法getAveragePower(String type, int level)将返回type子系统在CPU运行速度级别为level时单位时间内所消耗的电量(即电流值)。
(3)、public double getBatteryCapacity() 获取电池总电量。
/**
* Returns the battery capacity, if available, in milli Amp Hours. If not available,
* it returns zero.
* @return the battery capacity in mAh
*/
public double getBatteryCapacity() {
return getAveragePower(POWER_BATTERY_CAPACITY);
}
(4)、public int getNumSpeedSteps() 获取CPU可以以几种速度运行。
Android6.0中getNumSpeedSteps()方法改成了getNumSpeedStepsInCpuCluster()方法。
power_profile.xml
事实上,通过阅读PowerProfile.java代码及相关注释即可知,此类中各接口返回的电流等数值都是通过读取power_profile.xml文件获得的,即各种子系统消耗的电量值、CPU运行速度值、总电量等信息都是以固定值的形式存储在power_profile.xml中。由于硬件之间的差异,各子系统耗电信息是不同的,因此此文件需要各生产厂商进行定制。android系统原生的power_profile.xml文件的存放路径为:frameworks/base/core/java/com/android/internal/os/PowerProfile.java,/frameworks/base/core/res/res/xml/power_profile.xml。经过各硬件厂商定制后,存放路径可能发生变化,如三星某型号的power_profile.xml路径:device/samsung/maguro/overlay/frameworks/base/core/res/res/xml/power_profile.xml.
<device name="Android">
<!-- All values are in mA except as noted -->
<item name="none">0</item>
<item name="screen.on">200</item> <!-- min brite -->
<item name="bluetooth.active">150</item>
<item name="bluetooth.on">1</item>
<item name="bluetooth.at">1</item> <!-- TBD -->
<item name="screen.full">160</item> <!-- 360 max on calendar -->
<item name="wifi.on">1</item> <!-- wifi处于开启状态,但未传输数据 -->
<item name="wifi.active">150</item> <!-- wifi处于传输数据状态 -->
<item name="wifi.scan">200</item> <!-- wifi处于搜索热点状态 -->
<item name="dsp.audio">150</item>
<item name="dsp.video">200</item>
<item name="radio.active">150</item>
<item name="gps.on">55</item>
<item name="battery.capacity">1750</item>
<item name="radio.scanning">90</item> <!-- TBD -->
<!-- Current consumed by the radio at different signal strengths, when paging -->
<array name="radio.on"> <!-- 1 entry per signal strength bin, TBD -->
<value>3.0</value>
<value>3.0</value>
</array>
<array name="cpu.speeds">
<value>350000</value> <!-- 3.5MHz -->
<value>700000</value> <!-- 7.0MHz -->
<value>920000</value> <!-- 9.2MHz -->
<value>1200000</value> <!-- 1.2GHz -->
</array>
<!-- Power consumption in suspend -->
<item name="cpu.idle">7</item>
<!-- Power consumption due to wake lock held -->
<item name="cpu.awake">20</item>
<!-- Power consumption at different speeds -->
<array name="cpu.active">
<value>120</value>
<value>228</value>
<value>299</value>
<value>397</value>
</array>
</device>
代码段2 power_profile.xml内容
显然,从power_profile.xml可知,此型号机器可以以3.5MHz、7.0MHz、9.2MHz、1.2GHz四种速度运行(<array name="cpu.speeds">定义),且在此四种运行速度下CPU的耗电量分别为120mAh,228mAh,299mAh及397mAh(<array name="cpu.active">)。通过对比代码段1可知,PowerProfile.java中定义的常量即对应于power_profile.xml中各属性名。因此,PowerProfile.java只是用于读取power_profile.xml的接口而已,后者才是存储系统耗电信息的核心文件。
通过上述分析可知,android系统对于电池电量信息统计还是提供了数据与接口的(本人菜鸟,之前一直认为耗电及剩余时间信息是由先前一定时间内的耗电量统计而来的,分析了settings中的电池相关的代码后才知是有据可依的)。根据PowerProfile.java及power_profile.xml,我们可以计算出各应用或设备的耗电量、电池剩余时间等信息,相关内容将在后续文章中描述。
总结:
(1)、Android部件电流信息存于:power_profile.xml;
(2)、每个OEM厂商都私有power_profile.xml文件;
(3)、PowerProfile读取power_profile.xml,并提供API访问部件电流数值。
3、BatteryStats是一个抽象类,BatteryStatsImpl继承了BatteryStats
BatteryStatsImpl用来提供App各部件运行时间。
Android具体耗电量计算方法
具体处理是在BatteryStatsHelper.java类中。
App耗电量统计:processAppUsage() --计算每个uid的耗电量情况,包含CPU、Wakelock、WiFi、蓝牙、传感器、Camera、Flashlight、MobileRadio。
硬件耗电量统计:processMiscUsage()--计算比如屏幕、WiFi、蓝牙等耗电情况。
private void processAppUsage(SparseArray<UserHandle> asUsers) {
final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);//是否针对所有用户
mStatsPeriod = mTypeBatteryRealtimeUs;
BatterySipper osSipper = null;//声明BatterySipper对象,里面包含一些后面要计算的值
final SparseArray<? extends Uid> uidStats = mStats.getUidStats();//获取包含uid对象的数组
final int NU = uidStats.size();
for (int iu = 0; iu < NU; iu++) {//processAppUsage真实统计粒度是Uid
final Uid u = uidStats.valueAt(iu);
final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);//创建BatterySipper对象
//由各个继承了PowerCalculator类的子类去计算app的耗电量
mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
final double totalPower = app.sumPower();//计算总耗电量
if (DEBUG && totalPower != 0) {
Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),
makemAh(totalPower)));
}
// Add the app to the list if it is consuming power.
if (totalPower != 0 || u.getUid() == 0) {
//
// Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
//
final int uid = app.getUid();
final int userId = UserHandle.getUserId(uid);
if (uid == Process.WIFI_UID) {
mWifiSippers.add(app);
} else if (uid == Process.BLUETOOTH_UID) {
mBluetoothSippers.add(app);
} else if (!forAllUsers && asUsers.get(userId) == null
&& UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
// We are told to just report this user's apps as one large entry.
List<BatterySipper> list = mUserSippers.get(userId);
if (list == null) {
list = new ArrayList<>();
mUserSippers.put(userId, list);
}
list.add(app);
} else {
mUsageList.add(app);
}
if (uid == 0) {
osSipper = app;
}
}
}
if (osSipper != null) {
// The device has probably been awake for longer than the screen on
// time and application wake lock time would account for. Assign
// this remainder to the OS, if possible.
mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtimeUs,
mRawUptimeUs, mStatsType);
osSipper.sumPower();
}
}
processAppUsage()方法的参数是一个SparseArray
数组,存储的对象是UserHandle,代表一个用户,可以理解为这个类里面存储了用户的相关信息。然后判断该次计算是否针对所有用户,通过UserHandle
的USER_ALL
值来判断,该值为-1。然后给公共变量int类型的mStatsPeriod
赋值,这个值mTypeBatteryRealtime
的计算过程又在320行的refreshStats
方法中,如下:
mTypeBatteryRealtimeUs = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
这里面用到了BatteryStats类中的computeBatteryRealtime
方法,该方法计算出此次统计电量的时间间隔。
接着,创建一个BatterySipper对象
osSipper
,该对象里面可以存储一些后续我们要计算的值,然后通过BatteryStats
类对象mStats
来得到一个包含Uid
的对象的SparseArray
组数,然后计算了一下这个数组的大小,保存在变量NU中。
然后for
循环计算每个Uid
代表的App
的耗电量,因为BatterySipper
可计算的类型有三种:应用, 系统服务, 硬件类型,所以这个地方传入的是DrainType.APP。我们看下BatterySipper的解释:
/**
* Contains power usage of an application, system service, or hardware type.应用,系统服务,硬件类型
*/
public class BatterySipper implements Comparable<BatterySipper> {
接下来,列举了目前可计算耗电量的模块。如:
mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
其中mStatsType的值为BatteryStats.STATS_SINCE_CHARGED,代表了我们的计算规则是从上次充满电后数据,如下:
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
还有一种规则是STATS_SINCE_UNPLUGGED是拔掉USB线后的数据。而mRawRealtimeUs是当前时间,mRawUptimeUs是运行时间。Android6.0的各个模块都交给了单独的类去计算,这些类都继承于PowerCalculator抽象类:
蓝牙耗电:BluetoothPowerCalculator.java
摄像头耗电:CameraPowerCalculator.java
Cpu耗电:CpuPowerCalculator.java
手电筒耗电:FlashlightPowerCalculator.java
无线电耗电:MobileRadioPowerCalculator.java
传感器耗电:SensorPowerCalculator.java
Wakelock耗电:WakelockPowerCalculator.java
Wifi耗电:WifiPowerCalculator.java
接着计算总耗电量。BatterySipper类的sumPower
方法是统计总耗电量,方法详情如下,其中usagePowerMah
这个值有点特殊,其他的上面都讲过.
/**
* Sum all the powers and store the value into `value`.
* @return the sum of all the power in this BatterySipper.
*/
public double sumPower() {
return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
flashlightPowerMah + bluetoothPowerMah;
}
然后根据是否是DEBUG版本打印信息
;
然后会把刚才计算的电量值添加到列表中:首先判断totalPower
的值和当前uid号
是否符合规则,规则为总耗电量不为0或者用户id为0。当uid
表明为WIFI或者蓝牙时,添加到下面对应的列表中,一般情况下正常的应用我们直接保存到下面的mUsageList
中就行就行,但是也有一些例外:如果我们的系统是单用户系统,且当前的userId
号不在我们的统计范围内,且其进程id
号是大于Process.FIRST_APPLICATION_UID
(10000,系统分配给普通应用的其实id号),我们就要将其存放到mUserSippers
数组中,定义如下:
private final SparseArray<List<BatterySipper>> mUserSippers = new SparseArray<>();
最后判断uid
为0的话,代表是Android
操作系统的耗电量,赋值给osSipper
就可以了,这样一个app
的计算就完成了,遍历部分就不说了,保存这个osSipper
是为了最后一步计算持有唤醒锁的耗电量。
总结:
processAppUsage()方法中利用循环的方法,一次计算每一个应用的耗电量。
1、根据应用的uid来计算该应用的CPU,蓝牙,wifi,手机射频,传感器,camera等硬件的耗电量,所计算的耗电量信息都存储在了BatterySipper这个对象中。
2、将各个硬件模块的耗电量相加计算出该应用的总耗电量
3、根据应用的类型,将应用的耗电信息添加的不同的列表归类。其中若uid==0表示当前应用为系统,然后计算出系统的耗电量
那耗电量到底是怎么计算的呢?可参考:http://blog.csdn.net/itfootball/article/details/49155979
总结:
1、Android电量消耗统计在:BatteryStatsHelper类;
2、getPowerProfile() 获取PowerProfile,PowerProfile类是(读取power_profile.xml文件)获取Android各部件电流值;
3、getStats()获取BatteryStats(App各部件运行时间微秒为单位),通过BatteryStatsService服务类:收集所有消耗电池信息;调用updateExternalStatsSync()从外部获取数据来源(无线控制器,bluetooth芯片组)和更新batterystats信息。
4、refreshState更新电池最新状态
- processAppUsage() :App耗电量统计
- processMiscUsage() :硬件耗电量统计
注意:
Uid与App关系:2个App签名和sharedUserId相同,则在运行时,他们拥有相同Uid。就是说processAppUsage统计的可能是多个App的耗电量数据,对于普通App,出现这种情况的几率较少,而对于Android系统应用则较为常见。