BatteryStasService的主要功能是收集系统中各模块和应用进程的用电情况。
因此,我们可以认为BatteryStatsService是Android中的“电表”。
只不过这个电表比较智能,不是单纯地统计整体的耗电,而是分门别类的统计每个部分的耗电情况。
接下来我们就分析一下BatteryStatsService的主要流程。为了方便叙述,后文中我们将BatteryStatsService简称为BSS。
一、BSS启动
1、构造函数
与一般的系统服务不太一样,BSS的创建和发布是在ActivityManagerService中进行的,相关代码如下:
public ActivityManagerService(Context systemContext) {
..........
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
systemDir.mkdirs();
//创建BSS对象,传入/data/system目录,同时传入ActivityManagerService的handler
mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
//调用BSS中BatteryStatsImpl对象的readLocked方法
mBatteryStatsService.getActiveStatistics().readLocked();
//将初始化得到的信息写入disk
mBatteryStatsService.scheduleWriteToDisk();
..........
}
接下来我们先看看BatteryStatsService的构造函数:
BatteryStatsService(File systemDir, Handler handler) {
// Our handler here will be accessing the disk, use a different thread than
// what the ActivityManagerService gave us (no I/O on that one!).
//创建一个本地服务线程,用于访问硬盘数据
final ServiceThread thread = new ServiceThread("batterystats-sync",
Process.THREAD_PRIORITY_DEFAULT, true);
thread.start();
mHandler = new BatteryStatsHandler(thread.getLooper());
// BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
//创建BatteryStatsImpl类
mStats = new BatteryStatsImpl(systemDir, handler, mHandler, this);
}
从代码可以看出,BSS维护的主要变量为一个BatteryStatsImpl对象,这个对象才是承担BSS实际工作的主体。
上文中的readLocked函数就是由BatteryStatsImpl实际执行。
BSS与BatteryServiceImpl的关系如上图所示。
从图中可以看出,BatteryStatsImpl继承自BatteryStats,实现了Parcelable接口,因此可以通过Binder通信在进程间传递。
实际上,从设置中查到的用电信息就是来自BatteryStatsImpl。
BSS的getStatistics函数提供了查询系统用电的接口,该接口的代码如下:
public byte[] getStatistics() {
...............
Parcel out = Parcel.obtain();
//更新系统中其它组件的耗电情况,记录于BatteryStatsImpl中
updateExternalStatsSync("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
synchronized (mStats) {
//将BatteryServiceImpl中的信息写入到数据包中
mStats.writeToParcel(out, 0);
}
byte[] data = out.marshall();
out.recycle();
return data;
}
由此可以看出,电量统计的核心类是BatteryStatsImpl,后文中简写为BSImpl。
2、BSS的发布
BSS创建完毕后,在ActivityManagerService的start函数中,BSS完成发布的工作:
private void start() {
..........
mBatteryStatsService.publish(mContext);
..........
}
跟进一下BatteryStatsService的publish函数:
public void publish(Context context) {
mContext = context;
//BSImpl设置mPhoneSignalScanningTimer的超时时间(用于统计搜索手机信号消耗的时间)
mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout)
* 1000L);
//创建衡量硬件耗电能力的PowerProfile,并交给BSImpl使用
mStats.setPowerProfile(new PowerProfile(context));
//将自己注册到service manager进程中
ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
}
BSS发布相关的代码比较简单,但BSImpl做了两件与发布服务无关的事。
首先,BSImpl设置了mPhoneSignalScanningTimer的超时时间。mPhoneSignalScanningTimer是BSImpl的一个统计工具,实际类型为StopwatchTimer。BSImpl作为统计电量的实际类,定义了许多工具分别统计终端不同部分、场景的耗电情况。后文再详细分析这一部分。
此外,BSImpl设置了PowerProfile相关的内容。
/**
* Reports power consumption values for various device activities. Reads values from an XML file.
* Customize the XML file for different devices.
* /
public PowerProfile(Context context) {
// Read the XML file for the given profile (normally only one per
// device)
if (sPowerMap.size() == 0) {
//从XML中得到设备硬件的配置情况
readPowerValuesFromXml(context);
}
//得到CPU的性能指标
initCpuClusters();
}
实际使用的XML应该是和硬件相关的文件,存储各种操作的耗电情况(以mA.h为单位),此处具体的解析过程不做赘述。
我们回过头来看一下BSImpl的setPowerProfile函数:
public void setPowerProfile(PowerProfile profile) {
synchronized (this) {
mPowerProfile = profile;
// We need to initialize the KernelCpuSpeedReaders to read from
// the first cpu of each core. Once we have the PowerProfile, we have access to this
// information.
//以下是构造每个CPU集群对应的KernelCpuSpeedReader
final int numClusters = mPowerProfile.getNumCpuClusters();
mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
int firstCpuOfCluster = 0;
//轮询每个集群
for (int i = 0; i < numClusters; i++) {
//得到CPU支持的频率值
final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
//创建KernelCpuSpeedReader
mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
numSpeedSteps);
//当前CPU集群的核数
firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
}
//得到当前评估出的平均电量
if (mEstimatedBatteryCapacity == -1) {
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
}
}
}
至此我们知道了,在BSS发布之前,它持有的BSImpl对象已经得到整个终端电量相关的硬件信息。
3、initPowerManagement
BSS发布后,SystemServer调用了ActivityManagerService的initPowerManagement函数:
..............
// Now that the power manager has been started, let the activity manager
// initialize power management features.
mActivityManagerService.initPowerManagement();
.............
在ActivityManagerService中:
public void initPowerManagement() {
................
mBatteryStatsService.initPowerManagement();
................
}
我们进入到BSS的initPowerManagement函数:
/**
* At the time when the constructor runs, the power manager has not yet been
* initialized. So we initialize the low power observer later.
*/
//SystemServer先启动ActivityManagerService,在其中创建出BSS
//后面才创建PowerManagerService
public void initPowerManagement() {
final PowerManagerInternal powerMgr = LocalServices.getService(PowerManagerInternal.class);
//向PowerManagerService注册低电模式的回调接口,即进入或退出低电模式时
//调用BSS的onLowPowerModeChanged函数,在该函数中将调用BSImpl的notePowerSaveMode函数
powerMgr.registerLowPowerModeObserver(this);
//进入低电模式时启动mPowerSaveModeEnabledTimer
//离开低电模式时停止mPowerSaveModeEnabledTimer
//并记录对应的信息 (mPowerSaveModeEnabledTimer也是BSImpl定义的统计工具)
mStats.notePowerSaveMode(powerMgr.getLowPowerModeEnabled());
//启动WakeupReasonThread
(new WakeupReasonThread()).start();
}
在这一部分的最后,我们看一下WakeupReasonThread的主要部分:
final class WakeupReasonThread extends Thread {
........
public void run() {
..........
try {
String reason;
//等待终端被唤醒
while ((reason = waitWakeup()) != null) {
synchronized (mStats) {
//记录终端变为唤醒状态的原因
mStats.noteWakeupReasonLocked(reason);
}
}
} catch (RuntimeException e) {
Slog.e(TAG, "Failure reading wakeup reasons", e);
}
}
private String waitWakeup() {
..........
int bytesWritten = nativeWaitWakeup(mUtf8Buffer);
..........
// Create a String from the UTF-16 buffer.
return mUtf16Buffer.toString();
}
}
从上面的代码,我们可以看出对于BSS而言,WakeupReasonThread就是负责监控终端的状态,当终端变为唤醒状态时,利用BSImpl记录唤醒的原因。
利用这个机会,我们看看BSS底层是如何监控终端是否唤醒的。
在com_android_server_am_BatteryStatsService中:
static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) {
...........
// Register our wakeup callback if not yet done.
if (!wakeup_init) {
wakeup_init = true;
ALOGV("Creating semaphore...");
//创建旗语
int ret = sem_init(&wakeup_sem, 0, 0);
..........
ALOGV("Registering callback...");
//从挂起变为唤醒时,wakeup_callback将调用sem_post向wakeup_sem写入数据
set_wakeup_callback(&wakeup_callback);
}
// Wait for wakeup.
ALOGV("Waiting for wakeup...");
//唤醒时,结束等待
int ret = sem_wait(&wakeup_sem);
.........
//打开文件
FILE *fp = fopen(LAST_RESUME_REASON, "r");
.........
//读取内容
while (fgets(reasonline, sizeof(reasonline), fp) != NULL) {
..........
}
//关闭文件
if (fclose(fp) != 0) {
...........
}
//return reason
........
}
现在我们明白了,对于BSS而言,initPowerManagement的工作主要包括两个:
一是监控终端低电模式的状态,当发生改变时进行相应的记录;
二是监控终端从挂起状态变为唤醒状态,并记录唤醒的原因。
BSS启动时主要的流程基本结束,接下来我们看一下BSImpl相关的内容。
二、BSImpl的构造函数
BSImpl的构造函数内容较多,我们分段来看:
public BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
ExternalStatsSync externalSync, PlatformIdleStateCallback cb) {
init(clocks);
..........
1、init初始化
首先BSImpl在构造函数中,调用init进行初始化工作:
private void init(Clocks clocks) {
mClocks = clocks;
mMobileNetworkStats = new NetworkStats[] {
new NetworkStats(mClocks.elapsedRealtime(), 50),
new NetworkStats(mClocks.elapsedRealtime(), 50),
new NetworkStats(mClocks.elapsedRealtime(), 50)
};
mWifiNetworkStats = new NetworkStats[] {
new NetworkStats(mClocks.elapsedRealtime(), 50),
new NetworkStats(mClocks.elapsedRealtime(), 50),
new NetworkStats(mClocks.elapsedRealtime(), 50)
};
}
NetworkStats是实现Parcelable接口的对象,用于统计网络相关的数据,例如接口、uid、发送数据、接收数据等。
从这个架势来看,我们目前可以推测出BSImpl在统计电量时,还需要考虑每个网络具体的信息。
2、创建统计所需要的文件
..........
if (systemDir != null) {
mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
new File(systemDir, "batterystats.bin.tmp"));
} else {
mFile = null;
}
//mCheckinFile存储BSImpl的snapShot;
//当充电状态从充电变为未充电,且电池累积耗电量足够大时,会将snapShot写入"batterystats-checkin.bin"
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
..........
上述代码首先创建了一个JournaledFile,其内部包含两个创建于data/system/目录下的文件,分别作为原始文件和临时文件。这么做的目的是进行备份,
防止在读写过程中文件信息丢失或出错。
这部分文件用于保存BatteryStats电量的统计信息。系统会不定时的将电量统计信息BatteryStats写入到文件中。
从代码中操作mDailyFile的接口来看,mDailyFile应该是用于记录每天的电量统计信息。
3、创建各模块电量的统计工具
mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
..............
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
}
.............
mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS);
............
BSImpl需要统计各个模块的耗电情况,甚至需要统计一个模块在不同场景下的耗电情况。
为此,BSImpl在其构造函数中创建了许多对象进行分类统计。
如上面的代码示例,BSImpl在构造函数中创建的工具类就有三种,即StopwatchTimer、LongSamplingCounter和ControllerActivityCounterImpl。
这三种工具类的关系如上图所示,其中ControllerActivityCounterImpl包含了多个LongSamplingCounter对象。
从上图可以看出工具类实际上一共分为两种,其中Counter相关的主要用于计数,Timer相关的用于计时。
目前自己从代码来看,电量的统计主要依赖于Timer;Counter相关的对象,多进行统计发包数量、分组数量这样的工作。
这是因为:
在Android系统中,手机的电压是一定的,具体操作的耗电量在构造PowerProfile时已经得到了,因此手机中各模块的电量计算,就是利用模块使用时间*模块对应操作的单位耗电量。
这些工具类均实现了TimeBaseObs接口定义的onTimeStarted函数和onTimeStopped。
具体的用法,在具体的流程中分析比较好,此处再不作说明。
4、初始化剩余变量
.............
mOnBattery = mOnBatteryInternal = false;
long uptime = mClocks.uptimeMillis() * 1000;
long realtime = mClocks.elapsedRealtime() * 1000;
//初始化统计时间
initTimes(uptime, realtime);
mStartPlatformVersion = mEndPlatformVersion = Build.ID;
mDischargeStartLevel = 0;
mDischargeUnplugLevel = 0;
mDischargePlugLevel = -1;
mDischargeCurrentLevel = 0;
mCurrentBatteryLevel = 0;
initDischarge();
//删除用电统计的记录
clearHistoryLocked();
updateDailyDeadlineLocked();
mPlatformIdleStateCallback = cb;
.............
BSImpl的构造函数最后会初始化大量的变量,只有结合具体的流程,才能知道具体的含义,此处不做进一步的分析。
我们仅需要注意的是,系统时间分为uptime和realtime。uptime和realtime的时间起点都从系统启动开始算,
但是uptime不包括系统休眠时间,而realtime包括系统休眠时间。
三、BSS及BSImpl主要流程分析
1、BSImpl的readLocked
前面已经提到过,ActivityManagerService创建出BatteryStatsService后,执行了以下代码:
.........
//getActiveStatistics返回BSImpl
mBatteryStatsService.getActiveStatistics().readLocked();
.........
我们跟进一下BSImpl的readLocked函数:
public void readLocked() {
if (mDailyFile != null) {
//读取BSImpl维护的mDailyFile,解析其中的数据保存到mDailyItems中
readDailyStatsLocked();
}
//mFile为"batterystats.bin"对应的JournaledFile
if (mFile == null) {
Slog.w("BatteryStats", "readLocked: no file associated with this instance");
return;
}
//mUidStats保存了BatteryStatsImpl.Uid
//BSImpl定义的数据结构,继承BSS.Uid,记录每个uid对应的耗电情况
mUidStats.clear();
try {
//从JournaledFile选择实际文件和备份文件中的一个读取
File file = mFile.chooseForRead();
.........
FileInputStream stream = new FileInputStream(file);
byte[] raw = BatteryStatsHelper.readFully(stream);
Parcel in = Parcel.obtain();
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
stream.close();
//利用从"batterystats.bin"中读取的数据初始化BSImpl的变量
readSummaryFromParcel(in);
} catch(Exception e) {
Slog.e("BatteryStats", "Error reading battery statistics", e);
resetAllStatsLocked();
}
mEndPlatformVersion = Build.ID;
//开始记录历史信息
...............
//当时间满足条件时,记录每日的信息
recordDailyStatsIfNeededLocked(false);
}
从上面的代码可以看出,readLocked主要的功能是读取之前的统计信息,然后初始化BSImpl对象。
这里值的一提的是BSImpl中定义的内部类Uid。
BSImpl中的Uid类继承自BatteryStats的Uid类。
其中:
WakeLock用于统计该Uid对应进程使用某个WakeLock的用电情况;
Proc用于统计该Uid对应的某个进程的用电情况;
Pkg用于统计某个Pacakge的电量使用情况,其内部类Serv用于统计该Package内某个服务的用电情况;
Sensor用于统计传感器的用电情况。
2、BSS的scheduleWriteToDisk
调用readLocked完成对BSImpl的初始化后,ActivityManagerService调用了如下代码:
.........
mBatteryStatsService.scheduleWriteToDisk();
.........
在BSS的scheduleWriteToDisk函数中:
/**
* Schedules a write to disk to occur. This will cause the BatteryStatsImpl
* object to update with the latest info, then write to disk.
*/
public void scheduleWriteToDisk() {
mHandler.sendEmptyMessage(BatteryStatsHandler.MSG_WRITE_TO_DISK);
}
MSG_WRITE_TO_DISK对应的处理流程为:
..........
case MSG_WRITE_TO_DISK:
//进行信息更新
updateExternalStatsSync("write", UPDATE_ALL);
synchronized (mStats) {
//完成信息写入
mStats.writeAsyncLocked();
}
break;
..........
2.1 updateExternalStatsSync信息更新
void updateExternalStatsSync(final String reason, int updateFlags) {
//SynchronousResultReceiver用于接收消息
//继承RunReceiver实现了Parcel接口,可以跨进程传递
//定义了回调接口,通过回调接口得到返回消息
//在接收到回复的消息前,可以阻塞一段时间,整个的实现使得单独拿出来分析一下
SynchronousResultReceiver wifiReceiver = null;
SynchronousResultReceiver bluetoothReceiver = null;
SynchronousResultReceiver modemReceiver = null;
synchronized (mExternalStatsLock) {
...............
if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
................
try {
wifiReceiver = new SynchronousResultReceiver();
//获取WiFi的信息,传入SynchronousResultReceiver对象
mWifiManager.requestActivityInfo(wifiReceiver);
} catch (RemoteException e) {
// Oh well.
}
}
if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
bluetoothReceiver = new SynchronousResultReceiver();
//获取蓝牙的信息,传入SynchronousResultReceiver对象
adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
}
}
if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
..............
if (mTelephony != null) {
modemReceiver = new SynchronousResultReceiver();
//获取modem的信息,传入SynchronousResultReceiver对象
//对于Telephony而言,就是通过RIL发送消息给modem,获取返回结果
mTelephony.requestModemActivityInfo(modemReceiver);
}
}
WifiActivityEnergyInfo wifiInfo = null;
BluetoothActivityEnergyInfo bluetoothInfo = null;
ModemActivityInfo modemInfo = null;
try {
//awaitControllerInfo内部调用SynchronousResultReceiver对象的awaitResult函数
//在获得消息前,会等待一段时间直到超时
//获取到结果时,退出等待
wifiInfo = awaitControllerInfo(wifiReceiver);
} catch (TimeoutException e) {
Slog.w(TAG, "Timeout reading wifi stats");
}
try {
bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
} catch (TimeoutException e) {
Slog.w(TAG, "Timeout reading bt stats");
}
try {
modemInfo = awaitControllerInfo(modemReceiver);
} catch (TimeoutException e) {
Slog.w(TAG, "Timeout reading modem stats");
}
synchronized (mStats) {
//更新相关的信息
mStats.addHistoryEventLocked(
SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis(),
BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
reason, 0);
mStats.updateCpuTimeLocked();
mStats.updateKernelWakelocksLocked();
if (wifiInfo != null) {
if (wifiInfo.isValid()) {
//更新WiFi的耗电情况
mStats.updateWifiStateLocked(extractDelta(wifiInfo));
} else {
Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
}
}
if (bluetoothInfo != null) {
if (bluetoothInfo.isValid()) {
//更新蓝牙的耗电情况
mStats.updateBluetoothStateLocked(bluetoothInfo);
} else {
Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
}
}
if (modemInfo != null) {
if (modemInfo.isValid()) {
mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime(),
modemInfo);
} else {
Slog.e(TAG, "modem info is invalid: " + modemInfo);
}
}
}
}
}
从上面的代码,我们知道updateExternalStatsSync函数用于同步当前终端的信息,
主要是获取WiFi、BT和modem的耗电情况,并保存到BSImpl中。
2.2 BSImpl的writeAsyncLocked函数
public void writeAsyncLocked() {
//传入的值为false,表示异步
writeLocked(false);
}
void writeLocked(boolean sync) {
........
Parcel out = Parcel.obtain();
//将BSImpl中的信息写入到Parcel对象
writeSummaryToParcel(out, true);
.............
//Parcel对象保存到mPendingWrite
mPendingWrite = out;
//同步时用主线程写
if (sync) {
commitPendingDataToDisk();
} else {
//异步时利用后台线程写
BackgroundThread.getHandler().post(new Runnable() {
@Override public void run() {
commitPendingDataToDisk();
}
});
}
}
从上面的代码易知,commitPendingDataToDisk才进行实际的写操作。
public void commitPendingDataToDisk() {
...........
//写入的文件还是"batterystats.bin"
FileOutputStream stream = new FileOutputStream(mFile.chooseForWrite());
//next为mPendingWrite,即当前待写入的内容
stream.write(next.marshall());
stream.flush();
FileUtils.sync(stream);
stream.close();
mFile.commit();
.............
}
至此,我们知道scheduleWriteToDisk的作用就是将手机中最新的信息保存到硬盘中。
3、BSImpl耗电时间统计
了解BSS和BSImpl初始的一些流程后,我们来看看BSImpl到底是如何统计和计算电量的。
3.1 楔子
在BSS中定义了许多以note开头的方法,这些方法就是不同模块用于通知BSS记录用电信息的。
接下来,我们以数据业务为例,看看统计电量的过程。
在ConnectivityService的构造函数中:
public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
.............
//创建出DataConnectionStats,该类继承自BroadcastReceiver
mDataConnectionStats = new DataConnectionStats(mContext);
mDataConnectionStats.startMonitoring();
.............
}
跟进一下DataConnectionStats类:
public class DataConnectionStats extends BroadcastReceiver {
.................
public DataConnectionStats(Context context) {
mContext = context;
//获取到BatteryStatsService的代理对象
mBatteryStats = BatteryStatsService.getService();
}
public void startMonitoring() {
TelephonyManager phone =
(TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
//listen phone状态的变化
phone.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_SERVICE_STATE
| PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
| PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
| PhoneStateListener.LISTEN_DATA_ACTIVITY);
IntentFilter filter = new IntentFilter();
//监听以下广播
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
mContext.registerReceiver(this, filter);
}
..................
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
updateSimState(intent);
//注意notePhoneDataConnectionState
notePhoneDataConnectionState();
} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
notePhoneDataConnectionState();
}
}
.................
private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
mSignalStrength = signalStrength;
}
@Override
public void onServiceStateChanged(ServiceState state) {
mServiceState = state;
//同样会有notePhoneDataConnectionState
notePhoneDataConnectionState();
}
@Override
public void onDataConnectionStateChanged(int state, int networkType) {
mDataState = state;
notePhoneDataConnectionState();
}
@Override
public void onDataActivity(int direction) {
notePhoneDataConnectionState();
}
};
...............
}
从上面的代码可以看出,DataConnectionStats监控了整个终端中与数据相关的比较重要的状态,例如SIM State、Service State等。
当状态发生变化时,就会调用notePhoneDataConnectionState函数。
我们跟进一下DataConnectionStats中的notePhoneDataConnectionState:
private void notePhoneDataConnectionState() {
..............
boolean simReadyOrUnknown = mSimState == IccCardConstants.State.READY
|| mSimState == IccCardConstants.State.UNKNOWN;
//基本上可以简单的认为有服务并且数据脸上时,visible为true
boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
&& hasService()
&& mDataState == TelephonyManager.DATA_CONNECTED;
int networkType = mServiceState.getDataNetworkType();
................
try {
//进入到BSS中
mBatteryStats.notePhoneDataConnectionState(networkType, visible);
} catch (RemoteException e) {
Log.w(TAG, "Error noting data connection state", e);
}
}
3.2 notePhoneDataConnectionState函数
顺着流程,我们看看BSS的notePhoneDataConnectionState函数:
public void notePhoneDataConnectionState(int dataType, boolean hasData) {
enforceCallingPermission();
synchronized (mStats) {
//委托给BSImpl处理
mStats.notePhoneDataConnectionStateLocked(dataType, hasData);
}
}
跟进BSImpl的notePhoneDataConnectionStateLocked函数:
public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData) {
int bin = DATA_CONNECTION_NONE;
//数据连接上时,hasData为true
if (hasData) {
//dataType对应与networkType
switch (dataType) {
case TelephonyManager.NETWORK_TYPE_EDGE:
//根据NetworkType, 给bin赋予不同的值
bin = DATA_CONNECTION_EDGE;
break;
...............
}
}
//数据网络的类型发生改变时
if (mPhoneDataConnectionType != bin) {
//实际消逝的时间
final long elapsedRealtime = mClocks.elapsedRealtime();
//系统处于唤醒的状态消逝的时间
final long uptime = mClocks.uptimeMillis();
..............
//记录信息
addHistoryRecordLocked(elapsedRealtime, uptime);
if (mPhoneDataConnectionType >= 0) {
//旧networkType对应的StopWatchTime停止记录时间
mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
elapsedRealtime);
}
mPhoneDataConnectionType = bin;
//新networkType对应的StopWatchTimer开始记录时间
mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtime);
}
}
对于数据业务而言,BSImpl初始化时创建了一个StopWatchTimer数组mPhoneDataConnectionsTimer,用于记录不同NetworkType下,数据业务的耗电时间。
因此,从上面的代码我们就可以看出,每次NetworkType发生改变时,就停止旧有的NetworkType对应StopWatchTimer,这样就可以得到旧有NetworkType下耗电的时间。同时,启动新的NetworkType的StopWatchTimer,这样当下次NetworkType发生改变时,就可以得到数据业务在这个NetworkType下耗电的时间。
3.2.1 StopWatchTimer的startRunningLocked
public void startRunningLocked(long elapsedRealtimeMs) {
//mNesting可以认为是类似于引用数
if (mNesting++ == 0) {
final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
//mUpdateTime保存着本次启动的起始时间
mUpdateTime = batteryRealtime;
//mTimerPool是保存一堆TimerWatcher的ArrayList,不用考虑,当它是null
if (mTimerPool != null) {
...........
}
// Increment the count
mCount++;
//mTotalTime是这个StopWatchTimer运行过的累积总时间
mAcquireTime = mTotalTime;
}
}
在startRunningLocked函数中,我们只需要知道StopWatchTimer中的mUpdateTime记录者本次的启动时间。
3.2.2 StopWatchTimer的stopRunningLocked
public void stopRunningLocked(long elapsedRealtimeMs) {
// Ignore attempt to stop a timer that isn't running
if (mNesting == 0) {
return;
}
if (--mNesting == 0) {
final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
if (mTimerPool != null) {
........
} else {
mNesting = 1;
//computeRunTimeLocked进行实际的计算
mTotalTime = computeRunTimeLocked(batteryRealtime);
mNesting = 0;
}
................
}
}
protected long computeRunTimeLocked(long curBatteryRealtime) {
//mTimeout是调用StopWatchTimer接口进行设置的
//从逻辑来看,表示StopWatchTimer的指定生存期,一般没使用
if (mTimeout > 0 && curBatteryRealtime > mUpdateTime + mTimeout) {
curBatteryRealtime = mUpdateTime + mTimeout;
}
//简化来看,正常情况下:
//mTotalTime + (curBatteryRealtime - mUpdateTime)
//curBatteryRealtime为本次结束的时间,mUpdateTime被本次启动的时间
return mTotalTime + (mNesting > 0
? (curBatteryRealtime - mUpdateTime)
/ (mTimerPool != null ? mTimerPool.size() : 1)
: 0);
}
从代码来看,当调用stopRunningLocked函数后,就会更新StopWatchTimer的总运行时间。
与数据业务统计耗电时间的方式类似,其它模块也会利用对应的StopWatchTimer统计耗电时间。
因此,我们大概可以认为BSS中整个耗电统计的架构,大概如下图所示:
每个模块都会监控自己的关键状态。
当状态发生改变时,直接或间接地通知到BBS。
BSS收到通知后,通过各种以note开头的函数,将通知递交给BSImpl进行实际的处理。
BSImpl完成启动或停止对应模块的StopWatchTimer,完成对该模块耗电时间的统计。
4、电量计算
Android中计算耗电量离不开BatteryStatsHelper这个类,它主要用于计算所有应用和服务的用电量。
在Settings设置中显示电量相关信息,就是通过调用BatteryStatsHelper的接口来实现的。
从BatteryStatsHelper类的注释来看,当需要使用BatteryStatsHelper时:
必须在Activity或者Fragment初始化时,立即调用BatteryStatsHelper的create();
同时在activity或者Fragment销毁时,调用BatteryStatsHelper的destroy()。
不过在Android N中BatteryStatsHelper似乎已经没有destroy函数了。
4.1 用法示例
我们以设置中的代码为例,看看BatteryStatsHelper的使用方式。
在Android的原生代码,packages/apps/Settings/src/com/android/settings/fuelgauge/PowerBase.java中:
public abstract class PowerUsageBase extends SettingsPreferenceFragment {
..................
public void onAttach(Activity activity) {
super.onAttach(activity);
mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);
//创建
mStatsHelper = new BatteryStatsHelper(activity, true);
}
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
//初始化
mStatsHelper.create(icicle);
setHasOptionsMenu(true);
}
................
}
BatteryStatsHelper的构造函数比较简单,就是初始化一些变量。
我们看看BatteryStatsHelper的create函数:
public void create(Bundle icicle) {
...........
//获取BSS的服务端代理
mBatteryInfo = IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME));
//读取PowerProfile
mPowerProfile = new PowerProfile(mContext);
}
PowerProfile在前文分析BSImpl的构造函数时已经提到过了,主要记录不同硬件模块在不同状态下的耗电量。
在PowerBase.java中定义了:
protected void refreshStats() {
mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, mUm.getUserProfiles());
}
从代码可以看出,更新用电量时主要依赖于BatteryStatsHelper的refreshStats函数。
4.2 BatteryStatsHelper的refreshStats函数
public void refreshStats(int statsType, SparseArray asUsers, long rawRealtimeUs,
long rawUptimeUs) {
// Initialize mStats if necessary.
//确保获取了BSImp的统计信息
getStats();
//初始化基本的状态
mMaxPower = 0;
.......
//重置相关的列表
mUsageList.clear();
..........
//初始化相关的电量计算工具,各种计算工具都继承自PowerCalculator类,用于计算不同模块的耗电量
if (mCpuPowerCalculator == null) {
mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
mCpuPowerCalculator.reset();
............
//利用BSImpl中的信息,初始化一些变量
mStatsType = statsType;
mRawUptimeUs = rawUptimeUs;
mRawRealtimeUs = rawRealtimeUs;
mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs);
..........
//1、计算各个应用的耗电量
processAppUsage(asUsers);
..........
//2、计算各个模块,除去APP之外消耗的电量
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++) {
mComputedPower += mUsageList.get(i).totalPowerMah;
}
}
...........
mTotalPower = mComputedPower;
//这个函数没有深入研究
//看注释觉得的含义应该是:保证终端在上一次充电结束后,处于未充电状态的时间超过一个门限
if (mStats.getLowDischargeAmountSinceCharge() > 1) {
//mMinDrainedPower是统计出的电池的实际消耗电量
//电池耗电量大于统计的总耗电量
if (mMinDrainedPower > mComputedPower) {
//计算差异
double amount = mMinDrainedPower - mComputedPower;
//更新耗电量
mTotalPower = mMinDrainedPower;
//利用差异量,构造一个耗电对象,type为UNACCOUNTED
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.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
} else if (mMaxDrainedPower < mComputedPower) {
//电池耗电量小于统计的总耗电量
//多算了也没有更正mTotalPower
double amount = mComputedPower - mMaxDrainedPower;
// Insert the BatterySipper in its sorted position.
//同样插入一个耗电对象,type为OVERCOUNTED
BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
}
}
}
从上面的代码可以看出,refreshStats函数的主要作用就是重新统计终端的耗电量,然后更新自身的存储变量。
进行更新后,创建BatteryStatsHelper的类,就可以调用BatteryStatsHelper的getTotalPower、getUsageList等接口,获取终端耗电信息。
4.2.1 processAppUsage
refreshStats函数中比较重要的函数是processAppUsage:
private void processAppUsage(SparseArray<UserHandle> asUsers) {
.................
//得到BSImpl中的BatteryStatsImpl.Uid对象组成的列表,统计每个uid的耗电信息(主要是时间)
final SparseArray extends Uid> uidStats = mStats.getUidStats();
final int NU = uidStats.size();
//循环计算每个应用的耗电量
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
//得到该app的占用CPU消耗的电量
mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
//得到该app的持有WakeLock消耗的电量
mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
//得到该app的使用射频消耗的电量
mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType)
//利用其它工具得到对应模块消耗的电量
..........................
//计算这个应用耗电量的总和
final double totalPower = app.sumPower();
..........
// 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.
//如果该应用是单独某个用户下的应用则添加的userSippers列表
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) {
//android系统
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.
// 计算os消耗的电量,除去app消耗
mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtimeUs,
mRawUptimeUs, mStatsType);
osSipper.sumPower();
}
}
4.2.2 processMiscUsage
现在我们看看refreshStats中另一个比较重要的函数processMiscUsage:
private void processMiscUsage() {
addUserUsage();
addPhoneUsage();
addScreenUsage();
addWiFiUsage();
addBluetoothUsage();
addIdleUsage(); // Not including cellular idle power
// Don't compute radio usage if it's a wifi-only device
if (!mWifiOnly) {
addRadioUsage();
}
}
processMiscUsage中使用的几个函数,也是用于计算工具统计每个模块的耗电量。
但与processAppUsage统计的侧重点不一样。
我们以addWiFiUsage为例:
private void addWiFiUsage() {
BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
//注意此处调用的是calculateRemaining;processAppUsage调用的是calculateApp
mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType);
aggregateSippers(bs, mWifiSippers, "WIFI");
if (bs.totalPowerMah > 0) {
mUsageList.add(bs);
}
}
个人认为可以这样理解:
Android将一个模块的电量粗略分为两种。
一种是app在实际使用模块,引起的耗电量;一种是没有app调用,只要开启就会消耗的电量。
例如:WiFi只要开启,不论是否有app利用WiFi上网,WiFi都需要进行CSMA,因此会有基础的电量消耗。
4.2.3 计算方法举例
上面多次提到了计算工具,现在我们以数据业务相关的MobileRadioPowerCalculator为例,看看计算工具到底是如何工作的。
public class MobileRadioPowerCalculator extends PowerCalculator {
.............
public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) {
//从PowerProfile下,得到各种场景的单位耗电量
//radio on时的单位耗电量
mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE);
//各种信号强度下的单位耗电量
for (int i = 0; i < mPowerBins.length; i++) {
mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
}
//搜网时的单位耗电量
mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING);
mStats = stats;
}
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
// Add cost of mobile traffic.
//得到APP利用mobile发送分组的数量
app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
statsType);
//得到APP在mobile active时的运行时间
app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
//得到APP利用mobile发送字节的数量
app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
statsType);
if (app.mobileActive > 0) {
// We are tracking when the radio is up, so can use the active time to
// determine power use.
//总体时间
mTotalAppMobileActiveMs += app.mobileActive;
//运行时间 * 单位耗电量 得到整体的耗电情况
app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60);
} else {
// We are not tracking when the radio is up, so must approximate power use
// based on the number of packets.
//mobile处于active时,app并没有运行
//利用整体的收发包数量得到耗电量(不进一步详细分析,近似估值,这也是前面提及的refreshStats存在统计误差的原因之一)
app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
* getMobilePowerPerPacket(rawRealtimeUs, statsType);
}
.................
}
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
double power = 0;
long signalTimeMs = 0;
long noCoverageTimeMs = 0;
for (int i = 0; i < mPowerBins.length; i++) {
long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
/ 1000;
//计算处于每个信号强度下的耗电量
final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
.........
//累加
power += p;
//整体的统计时间
signalTimeMs += strengthTimeMs;
if (i == 0) {
//无信号时间
noCoverageTimeMs = strengthTimeMs;
}
}
//计算出搜网的耗电量
final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
/ 1000;
final double p = (scanningTimeMs * mPowerScan) / (60*60*1000);
............
power += p;
//计算出radio on,但app未使用时段的基础耗电量
long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
if (remainingActiveTimeMs > 0) {
power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);
}
//将计算的结果保存在app对应的BatterySipper中
if (power != 0) {
if (signalTimeMs != 0) {
app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
}
app.mobileActive = remainingActiveTimeMs;
app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
app.mobileRadioPowerMah = power;
}
}
.............
}
至此,我们终于对Android计算模块电量的方法有了基本的了解。
上面的代码其实也证实了我们之前的推测:
Android将一个模块的电量粗略分为两种。
一种是app在实际使用模块,引起的耗电量;一种是没有app调用,只要开启就会消耗的电量。
对于数据业务而言:
app在进行实际的收发数据,那么这部分电量就是app引起的耗电量。
终端维持一定的信号强度消耗的电量;搜网消耗的电量;打开数据开关,但没有数据收发时消耗的电量,统称为基础耗电量。
5、BSS的setBatteryState函数
BSS统计和计算电量的流程,我们已经分析完毕。
最后,分析一下以前博客的遗留问题:
之前的博客在分析BatteryService时,介绍过processValues函数的作用。
该函数在获取电池信息,进行更新、通知等操作时,调用过BSS的setBatteryState函数进行信息记录。
我们看一看setBatteryState函数:
public void setBatteryState(final int status, final int health, final int plugType,
final int level, final int temp, final int volt, final int chargeUAh) {
............
mHandler.post(new Runnable() {
public void run() {
synchronized (mStats) {
//判断是否充电
final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
if (mStats.isOnBattery() == onBattery) {
// The battery state has not changed, so we don't need to sync external
// stats immediately.
//充电状态未改变,仅做记录
mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
chargeUAh);
return;
}
}
//充电状态改变,则先同步WIFI、BT等的信息
updateExternalStatsSync("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
synchronized (mStats) {
//然后进行记录
mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
chargeUAh);
}
}
}
}
从代码可以看到,实际的记录工作还是依赖于BSImpl的setBatteryStateLocked函数:
public void setBatteryStateLocked(int status, int health, int plugType, int level,
int temp, int volt, int chargeUAh) {
//判断当前是否是电池供电(即是否没有插着充电器)
final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
..............
int oldStatus = mHistoryCur.batteryStatus;
if (onBattery) {
//如果是电池供电,则记录下当前的未充电的电量
mDischargeCurrentLevel = level;
if (!mRecordingHistory) {
mRecordingHistory = true;
//将当前的电池状态添加到历史记录
startRecordingHistory(elapsedRealtime, uptime, true);
}
} else if (level < 96) {
//充电但电量低于96%,同时没有记录过
if (!mRecordingHistory) {
mRecordingHistory = true;
startRecordingHistory(elapsedRealtime, uptime, true);
}
}
............
if (onBattery != mOnBattery) {
//充电状态发生改变,进行一些历史信息记录
..............
//setOnBatteryLocked执行充电状态发生变化的逻辑
setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
} else {
boolean changed = false;
//判断电池信息是否发生变化,变化时将changed置为true
.............
if (changed) {
//如果电量信息发生变化,就将当前的状态添加到历史记录中
addHistoryRecordLocked(elapsedRealtime, uptime);
}
}
.............
}
从上面的代码可以看出,setBatteryStateLocked函数:
首先判断供电状态是否发生了变化,如果发生变化,则调用setOnBatteryLocked进行处理;
如果供电状态没有发生变化,则判断当前电池的各种信息是否发生变化。如果发生了变化,就将当前的电池信息添加到历史记录中。
接下来,我们看看setOnBatteryLocked相关的代码:
void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
final int oldStatus, final int level, final int chargeUAh) {
boolean doWrite = false;
//触发回调
Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
m.arg1 = onBattery ? 1 : 0;
mHandler.sendMessage(m);
..........
if (onBattery) {
// We will reset our status if we are unplugging after the
// battery was last full, or the level is at 100, or
// we have gone through a significant charge (from a very low
// level to a now very high level).
boolean reset = false;
//对应上面的注释
//如果拔去充电器的时候电量是满的,或当前电量大于90,或从一个很低的电量充电到很高的电量,或电量历史记录的数据太大
if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
|| level >= 90
|| (mDischargeCurrentLevel < 20 && level >= 80)
|| (getHighDischargeAmountSinceCharge() >= 200
&& mHistoryBuffer.dataSize() >= MAX_HISTORY_BUFFER))) {
//变化的信息的数据足够时(累积耗电量足够大),生成一个snapshot,写入到mCheckinFile中
if (getLowDischargeAmountSinceCharge() >= 20) {
........
}
doWrite = true;
//重置电量统计的相关信息,清除当前的电量统计
resetAllStatsLocked();
}
............
//由充电变为不充电,记录下变成不充电状态时的电量
mDischargeCurrentLevel = mDischargeUnplugLevel = level;
............
//更新TimeBase,和电量统计计时器有关
updateTimeBasesLocked(true, !screenOn, uptime, realtime);
} else {
..........
//记录变成充电状态时的电量
mDischargeCurrentLevel = mDischargePlugLevel = level;
..........
//当前电量低于断开电源时的电量,表示电量消耗了
if (level < mDischargeUnplugLevel) {
//记录下电池用电时的电量消耗
mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
}
..........
//更新TimeBase,和电量统计计时器有关
updateTimeBasesLocked(false, !screenOn, uptime, realtime);
..........
}
//从充电变为未充电,或写入的间隔够大
if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
if (mFile != null) {
//将信息写入到BatteryStats.bin中
writeAsyncLocked();
}
}
}
总结
在这篇博客中,我们介绍了BatteryStatsService的主要流程。
从代码可以看出,BSS还是比较复杂的,原因是Android试图对系统耗电量做非常细致统计,导致统计项非常繁琐。
许多细节我们不需要去死记它,个人觉得知道电源统计的通知原理和统计方法就行了。