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 =