5.5.2 BatteryStatsService分析

BatteryStatsService(以后简称BSS)主要功能是收集系统中各模块和应用进程用电量情况。抽象地说,BSS就是一块“电表”,不过这块“电表”不只是显示总的耗电量,而是分门别类地显示耗电量,力图做到更为精准。

和其他服务不太一样的是,BSS的创建和注册是在ActivityManagerService中进行的,相关代码如下:

[—>ActivityManagerService.java:ActivityManagerService]


private ActivityManagerService(){

……//创建BSS对象,传递一个File对象,指向/data/system/batterystats.bin

mBatteryStatsService=new BatteryStatsService(new File(

systemDir,"batterystats.bin").toString());

}


BBS发布的代码如下:

[—>ActivityManagerService.java:main]


//调用BSS的publish函数,在内部将其注册到ServiceManager

m.mBatteryStatsService.publish(context);


下面来分析BSS的构造函数。

1.BatteryStatsService介绍

让人大跌眼镜的是,BSS其实只是一个壳,具体功能委托BatteryStatsImpl(以后简称BSImpl)来实现。BatteryStatsService的代码如下:

[—>BatteryStatsService.java:BatteryStatsService]


BatteryStatsService(String filename){

mStats=new BatteryStatsImpl(filename);

}


图5-2展示了BSS及BSImpl的家族图谱。

5.5.2 BatteryStatsService分析 - 图1

图 5-2 BSS及BSImpl家族图谱

由图5-2可知:

BSS通过成员变量mStats指向一个BSImpl类型的对象。

BSImpl从BatteryStats类派生。更重要的是,该类实现了Parcelable接口,由此可知,BSImpl对象的信息可以写到Parcel包中,从而可通过Binder在进程间传递。实际上,在Android手机的设置中查到的用电信息就是来自BSImpl的。

BSS的getStatistics函数提供了查询系统用电信息的接口,该函数的代码如下:

[—>BatteryStatsService:getStatistics]


public byte[]getStatistics(){

mContext.enforceCallingPermission(//检查调用进程是否有BATTERY_STATS权限

android.Manifest.permission.BATTERY_STATS, null);

Parcel out=Parcel.obtain();

mStats.writeToParcel(out,0);//将BSImpl信息写到数据包中

byte[]data=out.marshall();//序列化为一个buffer,然后通过Binder传递

out.recycle();

return data;

}


由此可以看出,电量统计的核心类是BSImpl,下面就来分析它。

2.初识BSImpl

BSImpl功能是进行电量统计,那么是否存在计量工具呢?答案是肯定的,并且BSImpl使用了不止一种计量工具。

(1)计量工具和统计对象介绍

BSImpl一共使用了4种计量工具,如图5-3所示。

5.5.2 BatteryStatsService分析 - 图2

图 5-3 计量工具图例

由图5-3可知:

一共有两大类计量工具,Counter用于计数,Timer用于计时。

BSImpl实现了StopwatchTimer(秒表)、SamplingTimer(抽样计时)、Counter(计数器)和SamplingCounter(抽样计数)等4个具体的计量工具。

BSImpl中定义了一个Unpluggable接口。当手机插上USB线充电(不论是由交流电还是由USB供电)时,该接口的plug函数都被调用。反之,当拔去USB线时,该接口的unplug函数被调用。设置这个接口的目的是为了满足BSImpl对各种情况下系统用电量的统计要求。关于Unpluggable接口的作用,在后续内容中能见到。

虽然只有4种计量工具(笔者觉得已经相当多了),但是可以在很多地方使用它们。下面先来认识部分被挂牌要求统计用电量的对象,如表5-5所示。

表5-5中的电量统计项已经够多了吧?还不止这些,为了做到更精确,Android还希望能统计每个进程在各种情况下的耗电量。这是一项庞大的工程,怎么做到的呢?来看下一节的内容。

(2)BatteryStats. Uid介绍

在Android 4.0中,和进程相关的用电量统计并非以单个PID为划分单元,而是以Uid为组,相关类结构如图5-4所示。

5.5.2 BatteryStatsService分析 - 图3

由图5-4可知:

Wakelock用于统计该Uid对应进程使用WakeLock的用电情况。

Proc用于统计Uid中某个进程的电量使用情况。

Pkg用于统计某个特定Package的使用情况,其内部类Serv用于统计该Pkg中Service的用电情况。

Sensor用于统计传感器用电情况。

5.5.2 BatteryStatsService分析 - 图4

图 5-4 BatteryStats.Uid家族

基于以上的了解,以后分析将会轻松很多,下面来分析它的代码。

3.BSImpl流程分析

(1)构造函数分析

先分析构造函数,代码如下:

[—>BatteryStatsImpl.java:BatteryStatsImpl]


public BatteryStatsImpl(String filename){

//JournaledFile为日志文件对象,内部包含两个文件,原始文件和临时文件。目的是双备份,

//以防止在读写过程中文件信息丢失或出错

mFile=new JournaledFile(new File(filename),new File(filename+".tmp"));

mHandler=new MyHandler();//创建一个Handler对象

mStartCount++;

//创建表5-5中的用电统计项对象

mScreenOnTimer=new StopwatchTimer(null,-1,null, mUnpluggables);

for(int i=0;i<NUM_SCREEN_BRIGHTNESS_BINS;i++){

mScreenBrightnessTimer[i]=new StopwatchTimer(null,-100-i, null,

mUnpluggables);

}

mInputEventCounter=new Counter(mUnpluggables);

……

mOnBattery=mOnBatteryInternal=false;//设置这两位成员变量为false

initTimes();//①初始化统计时间

mTrackBatteryPastUptime=0;

mTrackBatteryPastRealtime=0;

mUptimeStart=mTrackBatteryUptimeStart=

SystemClock.uptimeMillis()*1000;

mRealtimeStart=mTrackBatteryRealtimeStart=

SystemClock.elapsedRealtime()*1000;

mUnpluggedBatteryUptime=getBatteryUptimeLocked(mUptimeStart);

mUnpluggedBatteryRealtime=getBatteryRealtimeLocked(mRealtimeStart);

mDischargeStartLevel=0;

mDischargeUnplugLevel=0;

mDischargeCurrentLevel=0;

initDischarge();//②初始化和电池level有关的成员变量

clearHistoryLocked();//③删除用电统计的历史记录

}


要看懂这段代码比较困难,主要原因是变量太多,并且没有注释说明。只能根据名字来推测了。在以上代码中除了计量工具外,还出现了三大类成员变量:

用于统计时间的成员变量,例如mUptimeStart、mTrackBatteryPastUptime等。这些参数的初始化函数为initTimes。注意,系统时间分为uptime和realtime。uptime和realtime的时间起点都从系统启动开始算(since the system was booted),但是uptime不包括系统休眠时间,而realtime包括系统休眠时间[1]

用于记录各种情况下用于表电池电量的成员变量,如mDischargeStartLevel、mDischarge-CurrentLevel等,这些成员变量的初始化函数为initDischarge。

用于保存历史记录的HistroryItem,在clearHistoryLocked函数中初始化,主要有mHistory、mHistoryEnd等成员变量(这些成员变量在clearHistoryLocked函数中出现)。

上述这些成员变量的具体作用,只有通过后文的分析才能弄清楚。这里先介绍StopwacherTimer。


//调用方式

mPhoneSignalScanningTimer=new StopwatchTimer(null,-200+1,

null, mUnpluggables);

//mUnpluggables类型为ArrayList<Unpluggable>,用于保存插拔USB线时需要对应更新用电

//信息的统计对象

//StopwatchTimer的构造函数

StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer>timerPool,

ArrayList<Unpluggable>unpluggables){

//在本例中,uid为0,type为负数,timerPool为空,unpluggables为mUnpluggables

super(type, unpluggables);

mUid=uid;

mTimerPool=timerPool;

}

//Timer的构造函数

Timer(int type, ArrayList<Unpluggable>unpluggables){

mType=type;

mUnpluggables=unpluggables;

unpluggables.add(this);

}


在StopwatchTimer中比较难理解的就是unpluggables,根据注释说明,当插拔USB线时,需要更新用电统计的对象,应该将其加入到mUnpluggables数组中。

在启动秒表时,调用它的startRunningLocked函数,并传入BSImpl实例,代码如下:

[—>BatteryStatsImpl.java:StopwatchTimer.startRuningLocked]


void startRunningLocked(BatteryStatsImpl stats){

if(mNesting++==0){//嵌套调用控制

//getBatteryRealtimeLocked函数返回总的电池使用时间

mUpdateTime=stats.getBatteryRealtimeLocked(

SystemClock.elapsedRealtime()*1000);

if(mTimerPool!=null){//不讨论这种情况

}

mCount++;

mAcquireTime=mTotalTime;//计数控制,请读者阅读相关注释说明

}

}


当停用秒表时,调用它的stopRunningLocked函数,代码如下:

[—>BatteryStatsImpl.java:StopwatchTimer.stopRunningLocked]


void stopRunningLocked(BatteryStatsImpl stats){

if(mNesting==0){

return;//嵌套控制

}

if(—mNesting==0){

if(mTimerPool!=null){//不讨论这种情况

}else{

final long realtime=SystemClock.elapsedRealtime()*1000;

//计算此次启动/停止周期的时间

final long batteryRealtime=stats.getBatteryRealtimeLocked(realtime);

mNesting=1;

//mTotalTime代表从启动开始该秒停表一共记录的时间

mTotalTime=computeRunTimeLocked(batteryRealtime);

mNesting=0;

}

if(mTotalTime==mAcquireTime)mCount—;

}

}


在StopwatchTimer中定义了很多的时间参数,这些参数用于记录各种时间,例如总耗时、最近一次工作周期的耗时等。如果不是工作需要(例如研究Settings应用中和BatteryInfo相关的内容),读者仅需了解它的作用即可。

(2)ActivityManagerService和BSS交互

ActivityManagerService创建BSS后,还要进行几项操作,具体代码分别如下:

[—>ActivityManagerService. java:ActivityManagerService构造函数]


mBatteryStatsService=new BatteryStatsService(new File(

systemDir,"batterystats.bin").toString());

//操作通过BSImpl创建JournaledFile文件

mBatteryStatsService.getActiveStatistics().readLocked();

mBatteryStatsService.getActiveStatistics().writeAsyncLocked();

//BSImpl的getIsOnBattery返回mOnBattery变量,初始化值为false

mOnBattery=DEBUG_POWER?true

:mBatteryStatsService.getActiveStatistics().getIsOnBattery();

//设置回调,该回调也用于信息统计,留到介绍ActivityManagerService时再来分析

mBatteryStatsService.getActiveStatistics().setCallback(this);


[—>ActivityManagerService.java:main]


m.mBatteryStatsService.publish(context);


[—>BatteryStatsService.java:publish]


public void publish(Context context){

mContext=context;

//注意,BSS服务称为batteryinfo,而BatteryService服务称为battery

ServiceManager.addService("batteryinfo",asBinder());

//PowerProfile见下文解释

mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps());

//设置通信信号扫描超时时间

mStats.setRadioScanningTimeout(mContext.getResources().getInteger(

com.android.internal.R.integer.config_radioScanningTimeout)

*1000L);

}


在以上代码中,比较有意思的是PowerProfile类,它将解析Android 4.0源代码/frameworks/base/core/res/res/xml/power_profile.xml文件。此XML文件存储的是各种操作(和硬件相关)的耗电情况,如图5-5所示。

5.5.2 BatteryStatsService分析 - 图5

图 5-5 PowerProfile文件示例

由图5-5可知,该文件保存了各种操作的耗电情况,以mA h(毫安时)为单位。Power-Profile的getNumSpeedSteps将返回CPU支持的频率值,目前在该XML中只定义了一个值,即400MHz。

注意 在编译时,各厂家会将特定硬件平台的power_profile.xml复制到输出目录。此处展示的power_profile.xml和硬件平台无关。

(3)BatteryService和BSS交互

BatteryService在它的processValues函数中和BSS交互,processValues函数的代码如下:

[—>BatteryService.java:processValues]


private void processValues(){

……

mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth, mPlugType,

mBatteryLevel, mBatteryTemperature, mBatteryVoltage);

}


BSS的工作由BSImpl来完成,BsImpl的setBatteryState函数的代码如下:

[—>BatteryStatsImpl.java:setBatteryState]


public void setBatteryState(int status, int health, int plugType, int level,

int temp, int volt){

synchronized(this){

boolean onBattery=plugType==BATTERY_PLUGGED_NONE;//判断是否为电池供电

int oldStatus=mHistoryCur.batteryStatus;

……

if(onBattery){

//mDischargeCurrentLevel记录当前使用电池供电时的电池电量

mDischargeCurrentLevel=level;

mRecordingHistory=true;//mRecordingHistory表示需要记录一次历史值

}

//此时,onBattery为当前状态,mOnBattery为历史状态

if(onBattery!=mOnBattery){

mHistoryCur.batteryLevel=(byte)level;

mHistoryCur.batteryStatus=(byte)status;

mHistoryCur.batteryHealth=(byte)health;

……//更新mHistoryCur中的电池信息

setOnBatteryLocked(onBattery, oldStatus, level);

}else{

boolean changed=false;

if(mHistoryCur.batteryLevel!=level){

mHistoryCur.batteryLevel=(byte)level;

changed=true;

}

……//判断电池信息是否发生变化

if(changed){//如果发生变化,则需要增加一次历史记录

addHistoryRecordLocked(SystemClock.elapsedRealtime());

}

}

if(!onBattery&&status==BatteryManager.BATTERY_STATUS_FULL){

mRecordingHistory=false;

}

}

}


setBatteryState函数的工作如下:判断当前供电状态是否发生变化,由onBattery和mOnBattery进行比较决定。其中onBattery用于判断当前是否为电池供电,mOnBattery为上次调用该函数时得到的判断值。如果供电状态发生变化(其实就是经历一次USB拔插过程),则调用setOnBatteryLocked函数。如果供电状态未发生变化,则需要判断电池信息是否发生变化,例如电量和电压等。如果发生变化,则调用addHistoryRecordLocked。该函数用于添加一次历史记录。

接下来看setOnBatteryLocked函数的代码:

[—>BatteryStatsImpl.java:setOnBatteryLocked]


void setOnBatteryLocked(boolean onBattery, int oldStatus, int level){

boolean doWrite=false;

//发送一个消息给mHandler,将在内部调用ActivityManagerService设置的回调函数

Message m=mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);

m.arg1=onBattery?1:0;

mHandler.sendMessage(m);

mOnBattery=mOnBatteryInternal=onBattery;

long uptime=SystemClock.uptimeMillis()*1000;

long mSecRealtime=SystemClock.elapsedRealtime();

long realtime=mSecRealtime*1000;

if(onBattery){

//关于电量信息统计,有一个值得注意的地方:当oldStatus为满电状态,或当前电量

//大于90,或mDischargeCurrentLevel小于20并且当前电量大于80时,要清空统计

//信息,以开始新的统计。也就是说在满足特定条件的情况下,电量使用统计信息会清零并重

//新开始。读者不妨用自己手机试一试

if(oldStatus==BatteryManager.BATTERY_STATUS_FULL||level>=90

||(mDischargeCurrentLevel<20&&level>=80)){

doWrite=true;

resetAllStatsLocked();

mDischargeStartLevel=level;

}

//读取/proc/wakelock文件,该文件反映了系统Wakelock的使用状态,

//感兴趣的读者可自行研究

updateKernelWakelocksLocked();

mHistoryCur.batteryLevel=(byte)level;

mHistoryCur.states&=~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;

//添加一条历史记录

addHistoryRecordLocked(mSecRealtime);

//mTrackBatteryUptimeStart表示使用电池的开始时间,由uptime表示

mTrackBatteryUptimeStart=uptime;

//mTrackBatteryRealtimeStart表示使用电池的开始时间,由realtime表示

mTrackBatteryRealtimeStart=realtime;

//mUnpluggedBatteryUptime记录总的电池使用时间(不论中间插拔多少次)

mUnpluggedBatteryUptime=getBatteryUptimeLocked(uptime);

//mUnpluggedBatteryRealtime记录总的电池使用时间

mUnpluggedBatteryRealtime=getBatteryRealtimeLocked(realtime);

//记录电量

mDischargeCurrentLevel=mDischargeUnplugLevel=level;

if(mScreenOn){

mDischargeScreenOnUnplugLevel=level;

mDischargeScreenOffUnplugLevel=0;

}else{

mDischargeScreenOnUnplugLevel=0;

mDischargeScreenOffUnplugLevel=level;

}

mDischargeAmountScreenOn=0;

mDischargeAmountScreenOff=0;

//调用doUnplugLocked函数

doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);

}else{

……//处理使用USB充电的情况,请读者在上面讨论的基础上自行分析

}

……//记录信息到文件

}

}


doUnplugLocked函数将更新对应信息,该函数比较简单,无须赘述。另外,addHistory-RecordLocked函数用于增加一条历史记录(由HistoryItem表示),读者也可自行研究。

从本节的分析可知,Android将电量统计分得非常细,例如由电池供电的情况要统计,由USB/AC充电的情况也要统计,因此有了setBatteryState函数。

(4)PowerManagerService和BSS交互

PMS和BSS交互是最多的,此处以noteScreenOn和noteUserActivity为例,来介绍BSS到底是如何统计电量的。

先来看noteScreenOn函数。当开启屏幕时,PMS会调用BSS的noteScreenOn以通知屏幕开启,该函数在内部调用BSImpl的noteScreenOnLocked,其代码如下:

[—>BatteryStatsImpl. java:noteScreenOnLocked]


public void noteScreenOnLocked(){

if(!mScreenOn){

mHistoryCur.states|=HistoryItem.STATE_SCREEN_ON_FLAG;

//增加一条历史记录

addHistoryRecordLocked(SystemClock.elapsedRealtime());

mScreenOn=true;

//启动mScreenOnTime秒停表,其工作就是记录时间,读者可自行研究其内部实现

mScreenOnTimer.startRunningLocked(this);

if(mScreenBrightnessBin>=0)//启动对应屏幕亮度的秒停表(参考表5-5)

mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this);

//屏幕开启也和内核WakeLock有关,所以这里一样要更新WakeLock的用电统计

noteStartWakeLocked(-1,-1,"dummy",WAKE_TYPE_PARTIAL);

if(mOnBatteryInternal)

updateDischargeScreenLevelsLocked(false, true);

}

}


再来看noteUserActivity,当有输入事件触发PMS的userActivity时,该函数被调用,代码如下:

[—>BatteryStatsImpl.java:noteUserActivityLocked]


//BSS的noteUserActivity将调用BSImpl的noteUserActivityLocked

public void noteUserActivityLocked(int uid, int event){

getUidStatsLocked(uid).noteUserActivityLocked(event);

}


先是调用getUidStatsLocked以获取一个Uid对象,如果该Uid是首次出现的,则要在内部创建一个Uid对象。下面直接介绍Uid的noteUserActivityLocked函数:

[—>BatteryStatsImpl.java:Uid:noteUserActivityLocked]


public void noteUserActivityLocked(int type){

if(mUserActivityCounters==null){

initUserActivityLocked();

}

if(type<0)type=0;

else if(type>=NUM_USER_ACTIVITY_TYPES)

type=NUM_USER_ACTIVITY_TYPES-1;

//noteUserActivityLocked只是调用对应type的Counter的stepAtomic函数

//每个Counter内部都有个计数器,stepAtomic使该计数器增1

mUserActivityCounters[type].stepAtomic();

}


mUserActivityCounters为一个7元Counter数组,该数组对应7种不同的输入事件类型,在代码中,由BSImpl的成员变量USER_ACTIVITY_TYPES表示,如下所示:


static final String[]USER_ACTIVITY_TYPES={

"other","cheek","touch","long_touch","touch_up","button","unknown"

};


另外,在LocalPowerManager中,也定义了相关的type值,如下所示:

[—>LocalPowerManager.java]


public interface LocalPowerManager{

public static final int OTHER_EVENT=0;

public static final int BUTTON_EVENT=1;

public static final int TOUCH_EVENT=2;//目前只使用这3种事件

……

}


[1]读者可阅读SDK文档中关于SystemClock类的说明。