概述
BatteryStatsHelper类用于负责计算各个应用和服务的电量使用情况,利用BatteryStatsService中统计的时长和电源配置文件中的配置值,通过计算得到耗电量信息供Application层使用。在Settings通过refreshStats()
方法从BatteryStatsHelper中获取电池的数据。下面就BatteryStatsHelper从实例化到计算系统用电情况进行分析。
代码路径:
/frameworks/base/core/java/com/android/internal/os/BatteryStatsHelper.java
1.BatteryStatsHelper的初始化方法
要使用BatteryStatsHelper,必须获取其实例,并调用create()
方法。因此这里看看其构造方法和create()
方法,代码如下:
public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
mContext = context;
//是否需要注册BATTERY_CHANGED广播
mCollectBatteryBroadcast = collectBatteryBroadcast;
//平板等无移动网络,故该值为true
mWifiOnly = wifiOnly;
mPackageManager = context.getPackageManager();
final Resources resources = context.getResources();
//表示将数组中的包名作为系统进程对待
mSystemPackageArray = resources.getStringArray(
com.android.internal.R.array.config_batteryPackageTypeSystem);
//表示将数组中的包名作为系统服务对待
mServicepackageArray = resources.getStringArray(
com.android.internal.R.array.config_batteryPackageTypeService);
}
create()
方法:
public void create(BatteryStats stats) {
mPowerProfile = new PowerProfile(mContext);
mStats = stats;
}
public void create(Bundle icicle) {
if (icicle != null) {
mStats = sStatsXfer;
mBatteryBroadcast = sBatteryBroadcastXfer;
}
mBatteryInfo = IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME));
mPowerProfile = new PowerProfile(mContext);
}
构造方法比较简单。create()方法有两个重载,它们有个共同点:获取了PowerProfile对象,这个类在Battery系列三中已经分析过了,是电源配置文件。除此之外,还获得了BatteryStats对象。
1.1.refreshStats()方法
这个方法作为Application层刷新电池使用数据的接口,向上提供数据。电池统计的主要逻辑都是在这个方法中实现的,源代码如下:
public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
long rawUptimeUs) {
// Initialize mStats if necessary.
//获取BatteryStatsImpl对象
getStats();
mMaxPower = 0;//最大耗电量
mMaxRealPower = 0;//最大真实耗电量
mComputedPower = 0;//通过耗电计算器计算的耗电量总和
mTotalPower = 0;//总的耗电量
//存储了BatterySipper的列表,各类耗电量都存储在BatterySipper中,BatterySipper又放在了mUsageList中
mUsageList.clear();
//在统计软件耗电过程中使用到wifi的应用对应的BatterySipper列表
mWifiSippers.clear();
//在统计软件耗电过程中使用到BT的应用对应的BatterySipper列表
mBluetoothSippers.clear();
//设备上有多个用户时,存储了其他用户的耗电信息的SparseArray数据,键为userId,值为对应的List<BatterySipper>
mUserSippers.clear();
//存储有数据接受和发送的BatterySipper对象的列表
mMobilemsppList.clear();
if (mStats == null) {
return;
}
//初始化八个耗电量计算器,此处不做详细分析
if (mCpuPowerCalculator == null) {
mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
mCpuPowerCalculator.reset();
if (mMemoryPowerCalculator == null) {
mMemoryPowerCalculator = new MemoryPowerCalculator(mPowerProfile);
}
mMemoryPowerCalculator.reset();
if (mWakelockPowerCalculator == null) {
mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
}
mWakelockPowerCalculator.reset();
if (mMobileRadioPowerCalculator == null) {
mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
}
mMobileRadioPowerCalculator.reset(mStats);
// checkHasWifiPowerReporting can change if we get energy data at a later point, so
// always check this field.
final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile);
if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) {
mWifiPowerCalculator = hasWifiPowerReporting ?
new WifiPowerCalculator(mPowerProfile) :
new WifiPowerEstimator(mPowerProfile);
mHasWifiPowerReporting = hasWifiPowerReporting;
}
mWifiPowerCalculator.reset();
final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats,
mPowerProfile);
if (mBluetoothPowerCalculator == null ||
hasBluetoothPowerReporting != mHasBluetoothPowerReporting) {
mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
mHasBluetoothPowerReporting = hasBluetoothPowerReporting;
}
mBluetoothPowerCalculator.reset();
if (mSensorPowerCalculator == null) {
mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
(SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
}
mSensorPowerCalculator.reset();
if (mCameraPowerCalculator == null) {
mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile);
}
mCameraPowerCalculator.reset();
if (mFlashlightPowerCalculator == null) {
mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
}
mFlashlightPowerCalculator.reset();
//统计类型,有三种:上次充满电后、上次充电后、当前
mStatsType = statsType;
//当前系统运行时间
mRawUptimeUs = rawUptimeUs;
//当前系统运行时间,包括休眠时间
mRawRealtimeUs = rawRealtimeUs;
//电池放电运行时间
mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs);
//电池放电运行时间,包括休眠时间
mBatteryRealtimeUs = mStats.getBatteryRealtime(rawRealtimeUs);
//对应类型的电池放电运行时间,如上次充满电后的电池运行时间
mTypeBatteryUptimeUs = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
//对应类型的电池放电运行时间,包括休眠时间
mTypeBatteryRealtimeUs = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
//电池预计使用时长
mBatteryTimeRemainingUs = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
//电池预计多久充满时长
mChargeTimeRemainingUs = mStats.computeChargeTimeRemaining(rawRealtimeUs);
//最低放电量近似值 = (最低放电等级近似值 * PowerProfile中配置电池容量) / 100,是BatteryStatsService中统计的最低放电量近似值
mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
* mPowerProfile.getBatteryCapacity()) / 100;
//最高放电量近似值 = (最高放电等级近似值 * PowerProfile中配置电池容量) / 100,是BatteryStatsService中统计的最高放电量近似值
mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
* mPowerProfile.getBatteryCapacity()) / 100;
//统计软件的耗电量,统计之后,会将每种类型、每个uid的耗电值存储在对应的BatterySipper中
processAppUsage(asUsers);
// Before aggregating apps in to users, collect all apps to sort by their ms per packet.
//计算每个BatterySipper(每个应用)的每毫接收和发送的数据包mobilemspp
for (int i = 0; i < mUsageList.size(); i++) {
BatterySipper bs = mUsageList.get(i);
bs.computeMobilemspp();
//如果对应应用的mobilemspp!=0,则将该BatterySipper加入到mMobilemsppList列表中
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
//遍历其他用户的耗电情况
for (int i = 0; i < mUserSippers.size(); i++) {
List<BatterySipper> user = mUserSippers.valueAt(i);
//计算每个BatterySipper(每个应用)的每毫接收和发送的秒数据包(mobilemspp)
for (int j = 0; j < user.size(); j++) {
BatterySipper bs = user.get(j);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
}
//以mobilemspp对mMobilemsppList进行排序
Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
@Override
public int compare(BatterySipper lhs, BatterySipper rhs) {
return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
}
});
//统计硬件的耗电量
processMiscUsage();
//降序排序
Collections.sort(mUsageList);
// At this point, we've sorted the list so we are guaranteed the max values are at the top.
// We have only added real powers so far.
if (!mUsageList.isEmpty()) {
//最大耗电电量
mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
final int usageListCount = mUsageList.size();
for (int i = 0; i < usageListCount; i++) {
//各个BatterySipper最大耗电量之和
mComputedPower += mUsageList.get(i).totalPowerMah;
}
}
mTotalPower = mComputedPower;
//BatteryStatsService中统计的最低放电等级数近似值
if (mStats.getLowDischargeAmountSinceCharge() > 1) {
//如果最低放电量 > 计算的总耗电量,说明还有未计算的,这部分电量让DrainType.UNACCOUNTED类型的BatterySipper去存储
if (mMinDrainedPower > mComputedPower) {
//获取未计算耗电量
double amount = mMinDrainedPower - mComputedPower;
//将总的耗电量以mMinDrainedPower的值重置
mTotalPower = mMinDrainedPower;
//实例化一个DrainType.UNACCOUNTED类型的BatterySipper,用来存储未计算的耗电量
BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);
// Insert the BatterySipper in its sorted position.
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
//添加到mUsageList中
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
//如果最高放电量 < 计算的总耗电量,说明多算了耗电量,这部分电量让DrainType.OVERCOUNTED类型的BatterySipper去存储
} else if (mMaxDrainedPower < mComputedPower) {
//获取多计算的耗电量
double amount = mComputedPower - mMaxDrainedPower;
// Insert the BatterySipper in its sorted position.
//实例化一个DrainType.OVERCOUNTED类型的BatterySipper,用来存储未计算的耗电量
BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
//添加到mUsageList中
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
}
}
// Smear it!
/**
* 在统计列表mUsageList中标记不需要显示的统计,标记BatterySipper.shouldHide属性
* 以及确定每个应用处于前台时的屏幕耗电量
* 隐藏相关BatterySipper后,需要对为隐藏的BatterySipper设置"涂抹耗电量",具体来说就是将隐藏的这部分电量按比例
* 添加到各个未隐藏的BatterySipper中的proportionalSmearMah中
*/
//拿到需要隐藏的BatterySipper的全部总电量
final double hiddenPowerMah = removeHiddenBatterySippers(mUsageList);
//获取除隐藏耗电量之外的总的耗电量
final double totalRemainingPower = getTotalPower() - hiddenPowerMah;
if (Math.abs(totalRemainingPower) > 1e-3) {//1e-3=0.001
//得到每个BatterySipper的proportionalSmearMah值
for (int i = 0, size = mUsageList.size(); i < size; i++) {
final BatterySipper sipper = mUsageList.get(i);
if (!sipper.shouldHide) {
//隐藏耗电量 = 总隐藏耗电量 × (该BatterySipper中记录的耗电量/剩余总的耗电量)
sipper.proportionalSmearMah = hiddenPowerMah
* ((sipper.totalPowerMah + sipper.screenPowerMah)
/ totalRemainingPower);
sipper.sumPower();
}
}
}
}
这个方法比较庞大,先来看看其参数:
Int statsType
:表示需要统计的电池信息类型。有三类:
/*
*所有的电池数据,包括之前的统计数据
*/
public static final int STATS_SINCE_CHARGED = 0;
/*
*只包括当前运行时统计的数据
*/
public static final int STATS_CURRENT = 1;
/*
*包括设备最后一次充电后统计的数据
*/
public static final int STATS_SINCE_UNPLUGGED = 2;
SparseArray<UserHandle> asUsers
:UserHanler代表设备上的一个用户;long rawRealtimeUs
:系统开机后的运行时间,即SystemClock.elapsedRealtime;long rawUptimeUs
:系统不包括睡眠的运行时间,即SystemClock.uptimeMillis。
在这个方法中,大部分分析都在注释中,也容易理解。其中关于几个耗电量计算器的逻辑计划先不进行分析,只需要知道,最终计算的值存在了BatterySipper对象中即可。这里主要内容是refreshStats()方法中耗电计算器计算之后的逻辑。
在分析接下来的任务之前,需要分析下在refreshStats()
中调用BatteryStatsImpl的几个值的获取。因为这和BatteryStatsImpl中一些逻辑有关,而BatteryStatsImpl是一个非常复杂而且庞大的一个类,要搞情楚BatteryStatsImpl中的逻辑,就需要利用这个特定的场景,一口一口地啃。
1.2.BatteryStatsImpl.computeBatteryUptime()
该方法用来返回总的、最后充满电后的或当前的电池正常运行时间:
@Override
public long computeBatteryUptime(long curTime, int which) {
return mOnBatteryTimeBase.computeUptime(curTime, which);
}
在这个方法中,mOnBatteryTimeBase是TimeBase的一个实例,专门用于在电池没有插入充电的情况下做一些统计时间的工作。TimeBase的属性值如下:
public static class TimeBase {
//用来存储观察者
protected final ArrayList<TimeBaseObs> mObservers = new ArrayList<>();
protected long mUptime;
protected long mRealtime;
protected boolean mRunning;//是否正在运行,当拔掉充电器后,开始运行
protected long mPastUptime;//表示放电情况下度过了多长时间,只有在由放电->充电时才会累加更新
protected long mUptimeStart;//TimeBase调用setRunning()开始运行时时间,表示开始放电时的时间
protected long mPastRealtime;
protected long mRealtimeStart;//TimeBase调用setRunning()开始运行时时间
protected long mUnpluggedUptime;//一般情况下等于mPastUptime的值,表示刚拔掉充电线时的放电累加时间
protected long mUnpluggedRealtime;//一般情况下等于mPastRealtime的值
以上这些属性值所代表含义,通过具体的场景分析可得到,而在分析推导的过程中,需要了解TimeBase.setRunning()
方法,因为其中几个属性就是在这个方法中赋值的。
1.3.TimeBase.setRunning()
该方法如下:
public boolean setRunning(boolean running, long uptime, long realtime) {
//running表示是否没有插入充电器,只有running发生改变时才进入该方法
if (mRunning != running) {
mRunning = running;
//说明拔掉充电器
if (running) {
//放电开始时间
mUptimeStart = uptime;
mRealtimeStart = realtime;
//所有放电累计时间
long batteryUptime = mUnpluggedUptime = getUptime(uptime);
long batteryRealtime = mUnpluggedRealtime = getRealtime(realtime);
//通知各位观察者,主题发生改变
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onTimeStarted(realtime, batteryUptime, batteryRealtime);
}
} else {//说明插入了充电线
//累加放电时间,在原来基础上+当前时间-放电开始时间
mPastUptime += uptime - mUptimeStart;
mPastRealtime += realtime - mRealtimeStart;
//得到所有放电累计时间
long batteryUptime = getUptime(uptime);
long batteryRealtime = getRealtime(realtime);
//通知各位观察者,主题发生改变
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onTimeStopped(realtime, batteryUptime, batteryRealtime);
}
}
return true;
}
return false;
}
通过对setRunning()方法的分析,大致可以理解这些属性值的意义,再根据下面的分析,应该能够有更深入的理解。
再来看computeBatteryUptime()
方法中使用到的TimeBase.computeUptime()
方法:
public long computeUptime(long curTime, int which) {
switch (which) {
//自从上次充满电的时间
case STATS_SINCE_CHARGED:
//mUptime + 放电累加时间
return mUptime + getUptime(curTime);
//当前的运行时间
case STATS_CURRENT:
//放电累加时间
return getUptime(curTime);
//自从上次拔掉充电器的运行时间
case STATS_SINCE_UNPLUGGED:
//放电累加时间 - 刚拔掉充电线时的放电累计时间
return getUptime(curTime) - mUnpluggedUptime;
}
return 0;
}
其中的getUptime()
方法如下:
public long getUptime(long curTime) {
//放电累计时间,只有在由放点进入充电时会更新该值
long time = mPastUptime;
if (mRunning) {//如果此时还在放电,则mRunning=true
time += curTime - mUptimeStart;//再加上当前时间-放电开始时间这段值
}
return time;
}
1.4.BatteryStatsImpl.computeBatteryRealtime()
这个方法用来返回总的、最后充满电后的或当前的电池正常运行时间,包括休眠时间。其原理和computeBatteryRealtime()方法一样,这里不再说明,相关代码如下:
@Override
public long computeBatteryRealtime(long curTime, int which) {
return mOnBatteryTimeBase.computeRealtime(curTime, which);
}
mOnBatteryTimeBase.computeRealtime():
public long computeRealtime(long curTime, int which) {
switch (which) {
case STATS_SINCE_CHARGED:
//getRealtime()得到放电累计真实时间(包括休眠时)
return mRealtime + getRealtime(curTime);
case STATS_CURRENT:
return getRealtime(curTime);
case STATS_SINCE_UNPLUGGED:
return getRealtime(curTime) - mUnpluggedRealtime;
}
return 0;
}
其中的getRealtime()方法如下:
public long getRealtime(long curTime) {
//放电累计时间,包括休眠时间
long time = mPastRealtime;
if (mRunning) {
//如果处于放电,则mRunning=true
//还需要加上当前时间-放电开始这段时间
time += curTime - mRealtimeStart;
}
return time;
}
现在回到BatteryStatsHelper的refreshStats()中,进入到核心内容,也就是耗电量的计算部分。关于八个耗电统计计算器的实例化,这里暂且不分析,在之后的内容中会独立分析。
统计耗电量分为两部分:软件耗电和硬件耗电,分别用processAppUsage()
方法和processMiscUsage()
进行统计,下面分别来看其如何统计的。
1.5.processAppUsage(asUsers)
processAppUsage(asUsers)方法用来统计软件耗电量。在进行统计时,是以uid为单位进行统计,Android中的Uid是在应用安装时由系统分配的,每个应用对应一个uid,不过通过清单文件中的shareUserId可以指定两个应用的Uid相同。
在统计耗电量时,每个Uid,都会有一个对应的BatterySipper对象,八大计算器计算后的数据将会保存在BatterySipper对象中,下面详细看这个方法是如何实现的:
private void processAppUsage(SparseArray<UserHandle> asUsers) {
//是否是所有用户,默认false
final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
//电池正常运行时间(放电时间)
mStatsPeriod = mTypeBatteryRealtimeUs;
BatterySipper osSipper = null;
//获取BatteryStatsService中统计的Uid的SparseArray
final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
final int NU = uidStats.size();
//遍历每个uid
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
//实例化BatterySipper对象,该对象用来存储统计信息,第一个参数表示统计类型为应用耗电
final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
//计算每个uid的cpu耗电量
mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
//计算每个uid的wakelock耗电量
mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
//计算每个uid的无线电耗电量
mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
//计算每个uid的wifi耗电量
mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
//计算每个uid的蓝牙耗电量
mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
//计算每个uid的传感器耗电量
mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
//计算每个uid的相机耗电量
mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
//计算每个uid的闪光灯耗电量
mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
//通过BatterySipper得到总的耗电量
final double totalPower = app.sumPower();
// Add the app to the list if it is consuming power.
//如果统计电量大于零或者为root用户,u.getUid()获取对应app的进程id
if (totalPower != 0 || u.getUid() == 0) {
//
// Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
//获取对应app的进程id
final int uid = app.getUid();
//获取用户id,默认用户ID为0
final int userId = UserHandle.getUserId(uid);
//如果是wifi请求者(uid为1010),则将该BatterySipper添加到统计Wifi耗电量的列表List<BatterySipper>中
if (uid == Process.WIFI_UID) {
mWifiSippers.add(app);
//如果是BT服务进程(uid为1002),则将该BatterySipper添加到统计蓝牙耗电量的列表List<BatterySipper>中
} else if (uid == Process.BLUETOOTH_UID) {
mBluetoothSippers.add(app);
//forAllUsers为false,asUsers.get(userId)== null表示没有统计对应的userId的耗电量,
//因此将BatterySipper添加到表示其他用户的List中,然后将List存储到SparseArray中,以userId为key
} 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 {
//将BatterySipper对象添加到列表中
mUsageList.add(app);
}
//如果是root进程(系统用户),则将osSipper置为当前对象
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();
}
}
详细的解释都在方法注释中了,接下来看看统计硬件耗电量的方法。
1.6.processMiscUsage()方法
processMiscUsage()
方法用来统计硬件耗电量,在这个方法中,会分别统计多个类型的耗电量,该方法如下:
private void processMiscUsage() {
//累加所有的其他用户的耗电情况,统计到DrainType.USER类型的BatterySipper中
addUserUsage();
//累加所有通话耗电情况,统计到DrainType.PHONE类型的BatterySipper中
addPhoneUsage();
//累加所有亮屏的耗电情况,统计到DrainType.SCREEN类型的BatterySipper中
addScreenUsage();
//累加所有WIFI的耗电情况,统计到DrainType.WIFI类型的BatterySipper中
addWiFiUsage();
//累加所有BT的耗电情况,统计到DrainType.BLUETOOTH类型的BatterySipper中
addBluetoothUsage();
//累加所有DDR的耗电情况,统计到DrainType.MEMORY类型的BatterySipper中
addMemoryUsage();
//累加所有待机时的耗电情况,统计到DrainType.IDLE类型的BatterySipper中
addIdleUsage(); // Not including cellular idle power
// Don't compute radio usage if it's a wifi-only device
//对于平板设备来说,不支持GMS网络,因此mWifiOnly为ture
if (!mWifiOnly) {
//累加所有移动网络(radio)的耗电情况,统计到DrainType.CELL类型的BatterySipper中
addRadioUsage();
}
}
我们一个个看这些方法:
1.6.1.addUserUsage()
private void addUserUsage() {
//遍历SparseArray<List<BatterySipper>>,其key为userId,值为List<BatterySipper>
for (int i = 0; i < mUserSippers.size(); i++) {
//得到每一个userId
final int userId = mUserSippers.keyAt(i);
//实例化一个表示统计其他用户耗电量的BatterySipper
BatterySipper bs = new BatterySipper(DrainType.USER, null, 0);
bs.userId = userId;
//收集"其他用户"所有的BatterySipper中的耗电量到bs中,如果添加了用户的话,就会有其他用户
aggregateSippers(bs, mUserSippers.valueAt(i), "User");
//添加到集合
mUsageList.add(bs);
}
}
1.6.2.addPhoneUsage()
private void addPhoneUsage() {
//获取BatteryStatsService中统计的通话总时长
long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtimeUs, mStatsType) / 1000;
//获取mPowerProfile文件中无线电发送/接收信号时消耗的平均电量
double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
* phoneOnTimeMs / (60 * 60 * 1000);
if (phoneOnPower != 0) {
//实例化一个DrainTyep为PHONE类型的BatterySipper,并将phoneOnTimeMs、phoneOnPower进行赋值给BatterySipper
addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
}
}
1.6.3.addScreenUsage()
private void addScreenUsage() {
double power = 0;
//获取BatteryStatsService中统计的屏幕亮屏时间
long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtimeUs, mStatsType) / 1000;
//总消耗电量 = 亮屏时间 * 最低亮度时每毫秒所消耗电量
power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
//获取屏幕以最高亮度打开时每毫秒消耗的电量
final double screenFullPower =
mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
//遍历?
for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
double screenBinPower = screenFullPower * (i + 0.5f)
/ BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtimeUs, mStatsType)
/ 1000;
double p = screenBinPower * brightnessTime;
if (DEBUG && p != 0) {
Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+ " power=" + makemAh(p / (60 * 60 * 1000)));
}
power += p;
}
power /= (60 * 60 * 1000); // To hours
if (power != 0) {
//实例化一个DrainTyep为SCREEN类型的BatterySipper,并将screenOnTimeMs、power进行赋值给BatterySipper
addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
}
}
这个方法中一部分逻辑还有待研究,做个标记。
1.6.4.addWiFiUsage()
private void addWiFiUsage() {
//实例化一个DrainType.WIFI的BatterySipper,用来存储wifi耗电量
BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
//通过WIFI耗电量计算器计算可能无法归因于应用的耗电量
mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
//收集统计app耗电时获取的wifi耗电量和剩余wifi耗电量到bs中
aggregateSippers(bs, mWifiSippers, "WIFI");
//将bs添加到mUsageList中
if (bs.totalPowerMah > 0) {
mUsageList.add(bs);
}
}
1.6.5.addBluetoothUsage()
private void addBluetoothUsage() {
//实例化DrainTyep.BLEUTOOTH类型的BatterySipper,用于存储BT耗电量
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
//通过BT耗电量计算器计算可能无法归因于应用的耗电量
mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
//收集统计app耗电时获取的BT耗电量和剩余BT耗电量到bs中
aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
//将bs添加到mUsageList中
if (bs.totalPowerMah > 0) {
mUsageList.add(bs);
}
}
1.6.6.addMemoryUsage()
private void addMemoryUsage() {
//实例化DrainTyep.MEMORY类型的BatterySipper,用于存储DDR耗电量
BatterySipper memory = new BatterySipper(DrainType.MEMORY, null, 0);
//通过DDR耗电量计算器计算耗电量
mMemoryPowerCalculator.calculateRemaining(memory, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
//得到DDR耗电总和
memory.sumPower();
//添加到集合
if (memory.totalPowerMah > 0) {
mUsageList.add(memory);
}
}
1.6.7.addIdleUsage()
private void addIdleUsage() {
//获取mPowerProfile中cpu处于空闲状态时的耗电量
final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) *
mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
//获取mPowerProfile中cpu处于活动状态时的耗电量
final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) *
mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);
//得到cpu总的耗电量
final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);
if (totalPowerMah != 0) {
//实例化一个DrainTyep.IDLE类型的BatterySipper,并将mTypeBatteryRealtimeUs / 1000、totalPowerMah进行赋值给BatterySipper
addEntry(BatterySipper.DrainType.IDLE, mTypeBatteryRealtimeUs / 1000, totalPowerMah);
}
}
1.6.8.addRadioUsage()
private void addRadioUsage() {
//实例化一个DrainType.CELL的BatterySipper,用来存储无线电耗电量
BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
//通过无线电耗电量计算器计算无法归因于应用的耗电量
mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
//得到无线电耗电量总和
radio.sumPower();
//添加到mUsageList中
if (radio.totalPowerMah > 0) {
mUsageList.add(radio);
}
}
当统计完各个应用和硬件的耗电量之后,让我们再回到refreshStats()
中,看其最后两部分的逻辑。
第一部分是从”mStats.getLowDischargeAmountSinceCharge()
“开始的这个if语句。
在这里,它会将统计的所有的app和硬件的耗电量总和和BatteryStatsService中记录的放电量近似值做对比,如果通过耗电量计算器计算的总耗电量小于BatteryStatsService中的最低放电近似值,则说明还有未记录的耗电量,将这部分耗电量记录在BatterySipper.DrainType.UNACCOUNTED类型的BatterySipper中。同理,如果通过耗电量计算器计算的总耗电量大于BatteryStatsService中的最高放电近似值,则说明还多计算了耗电量,将这部分多计算的耗电量记录在BatterySipper.DrainType.OVERCOUNTED类型的BatterySipper中。
第二部分是从调用removeHiddenBatterySippers()
开始。拿到所有的耗电量后,有一部分类型的BatterySipper是需要隐藏的,因此通过removeHiddenBatterySippers()
方法进行隐藏,而隐藏后,这些BatterySipper的耗电量则会按照比例分摊在其他未隐藏的BatterySipper上。下面我们来看看这个方法。
1.7.removeHiddenBatterySippers()
public double removeHiddenBatterySippers(List<BatterySipper> sippers) {
double proportionalSmearPowerMah = 0;
BatterySipper screenSipper = null;
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatterySipper sipper = sippers.get(i);
//是否需要隐藏这个BatterySipper
sipper.shouldHide = shouldHideSipper(sipper);
if (sipper.shouldHide) {
if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED
&& sipper.drainType != BatterySipper.DrainType.SCREEN
&& sipper.drainType != BatterySipper.DrainType.UNACCOUNTED
&& sipper.drainType != BatterySipper.DrainType.BLUETOOTH
&& sipper.drainType != BatterySipper.DrainType.WIFI
&& sipper.drainType != BatterySipper.DrainType.IDLE) {
// Don't add it if it is overcounted, unaccounted or screen
//对于需要隐藏的BatterySipper,除以上条件之外的BatterySipper需要累计下对应电量
proportionalSmearPowerMah += sipper.totalPowerMah;
}
}
//获取统计亮屏耗电量的BatterySipper
if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
screenSipper = sipper;
}
}
//根据每个应用进程前台活动时间,获取作为每个应用处于前台时的屏幕耗电量
smearScreenBatterySipper(sippers, screenSipper);
return proportionalSmearPowerMah;
}
在这个方法中,有两个作用:
- 1.标记需要隐藏的BatterySipper,并统计隐藏的BatterySipper的耗电量总和;
- 2.根据每个应用进程前台活动时间,获取每个应用处于前台时的屏幕耗电量。
其中第一点已经说过了,有些耗电量的BatterySipper可以进行隐藏,例如在Settings中,并没有将所有的统计的BatterySipper都列出来,而是将有的隐藏掉了。
再来说第二点,因为在之前统计屏幕耗电量时,统计的是屏幕亮屏时的耗电量,并没有单独对某一个应用在前台时屏幕耗电量进行统计,因此这里通过获取应用在前台的时间,得到每个应用对屏幕的耗电量,具体计算公式为:
单个应用对屏幕的耗电量 = 总的屏幕亮屏耗电量 * 单个应用处于前台的时长 / 所有应用处于前台的总时长。
我们看对应计算方法smearScreenBatterySipper()
:
public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
long totalActivityTimeMs = 0;
final SparseLongArray activityTimeArray = new SparseLongArray();
for (int i = 0, size = sippers.size(); i < size; i++) {
final BatteryStats.Uid uid = sippers.get(i).uidObj;
if (uid != null) {
//获取对应进程前台运行时间
final long timeMs = getProcessForegroundTimeMs(uid,
BatteryStats.STATS_SINCE_CHARGED);
//以<uid,timems>的形式进行存储
activityTimeArray.put(uid.getUid(), timeMs);
//累计所有的应用进程前台运行时间
totalActivityTimeMs += timeMs;
}
}
//如果所有应用的前台运行时间 >= 10分钟
if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
//得到DrainType.SCREEN的统计耗电量
final double screenPowerMah = screenSipper.totalPowerMah;
for (int i = 0, size = sippers.size(); i < size; i++) {
final BatterySipper sipper = sippers.get(i);
//每个BatterySipper的屏幕耗电量 = DrainType.SCREEN类BatterySipper中的总电量 * 对应前台时间/总时间
sipper.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0)
/ totalActivityTimeMs;
}
}
}
分析到这里,关于refreshStats()方法中的大部分流程就完毕了。接下来简单看看BatterySipper类。
2.BatterySipper类
这个类在前面的计算中数次用到,但是并没有进行说明,这里对它进行下说明。
BatterySipper是一个用来存放应用、系统服务、硬件的耗电量的一个类,它定义了多个表示电量相关的属性,其中还定义了一个枚举类,下面我们简单看下其内部逻辑。
2.1.构造方法
BatterySipper类构造方法如下:
public BatterySipper(DrainType drainType, Uid uid, double value) {
//总的耗电量,计算耗电量初始化时传入0
this.totalPowerMah = value;
//耗电类型
this.drainType = drainType;
//Uid对象
uidObj = uid;
}
2.2.BatterySipper.DrainType
DrainType是一个枚举类,定义了实例化BatterySipper时可选的类型:
public enum DrainType {
IDLE,//用于实例化统计待机耗电量的BatterySipper
CELL,//无线电
PHONE,//通话
WIFI,//WIFI
BLUETOOTH,
FLASHLIGHT,
SCREEN,
APP,//应用耗电
USER,//用于实例化其他用户的耗电量的BatterySipper,Android支持多用户
UNACCOUNTED,//未计算的
OVERCOUNTED,//多算了的
CAMERA,//相机
MEMORY//DDR
}
2.3.sumPower()
这个方法用来统计一个BatterySipper的总耗电量:
public double sumPower() {
//总的耗电量为所有耗电量计算器得到的耗电量之和
totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
flashlightPowerMah + bluetoothPowerMah;
//"涂抹"后的总耗电量,一些类型的BatterySipper需要隐藏,因此将这些隐藏BatterySipper的耗电量按比例分给其他BatterySipper
totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
return totalPowerMah;
}
其他方法不再说明了,使用的也比较少,其中还有一个add()
方法,这个方法我们在分析每个耗电量计算器时在说明。
3.总结
对于BatteryStatsHelper中耗电量统计已经进行了完整的分析,现在来对耗电量统计做个比较清晰的总结。
在Settings中的耗电量统计,就是来自于BatteryStatsHelper中的数据,在BatteryStatsHelper中,refreshStats()
方法用来更新耗电量统计,每调用该方法一次,都会获取新的耗电量。
统计耗电量时,以uid为单位(Android中每个应用都有唯一uid),通过八个PowerCalculator对象计算每个应用分别使用对应资源(如cpu、wakelock)的耗电量,然后存储到对应的BatterySipper对象中。
在BatterySipper中,定义了多个耗电量类型,如屏幕耗电、wifi耗电、……,在统计耗电量过程中,针对一个类型,实例化一个BatterySipper对象,如统计WIFI耗电量,会实例化一个BatterySipper.DrainType.WIFI类型的BatterySipper,然后将WIFI的耗电量存储到它中。最终,将每个BatterySipper加入到List中(mUsageList
)。
Settings中也是通过拿到这个mUsageList
,从而从BatterySipper中解析出所需的耗电量。
通过八个耗电量计算器得到总的耗电量后,这个耗电量并不是最终值,在BatteryStatsService中,也对耗电量做了一个记录:最低放电量近似值和最高放电量近似值(mLowDischargeAmountSinceCharge
和mHighDischargeAmountSinceCharge
),每放一个电,该值累计加1。这俩值会和计算后的值进行对比,如果计算的耗电量 < 最低放电近似值,则说明存在未计算的耗电量,因此会将这部分未计算的耗电量存储到BatterySipper.DrainType.UNACCOUNTED类型的BatterySipper中;如果计算的耗电量 > 最高放电量,则说明多算了耗电量,因此会将这部分多算的耗电量存储到BatterySipper.DrainType.OVERCOUNTED类型的BatterySipper中。
有时候一些设备在进入Settings后,耗电信息中出现“多算了的”或者“少算了的”项,就是由于这个原因。