5.3.2 PMS acquireWakeLock分析

这部分的代码如下:

[—>PowerManagerService.java:acquireWakeLock]


public void acquireWakeLock(int flags, IBinder lock, String tag, WorkSource ws){

int uid=Binder.getCallingUid();

int pid=Binder.getCallingPid();

if(uid!=Process.myUid()){

mContext.enforceCallingOrSelfPermission(//检查WAKE_LOCK权限

android.Manifest.permission.WAKE_LOCK, null);

}

if(ws!=null){

//如果ws不为空,需要检查调用进程是否有UPDATE_DEVICE_STATS的权限

enforceWakeSourcePermission(uid, pid);

}

long ident=Binder.clearCallingIdentity();

try{

synchronized(mLocks){//调用acquireWakeLockLocked函数

acquireWakeLockLocked(flags, lock, uid, pid, tag, ws);

}

}……

}


接下来分析acquireWakeLockLocked函数。由于此段代码较长,故分段来分析。

1.acquireWakeLockLocked分析之一

开始分析之前,有必要先介绍另外一个数据结构,它为PowerManagerService的内部类,名字也为WakeLock。其定义如下:

[—>PowerManagerService.java]


class WakeLock implements IBinder.DeathRecipient


PMS的WakeLock实现了DeathRecipient接口。根据前面Binder系统的知识可知,当Binder服务端死亡后,Binder系统会向注册了讣告接收的Binder客户端发送讣告通知,因此客户端可以做一些资源清理工作。在本例中,PM.WakeLock是Binder服务端,而PMS.WakeLock是Binder客户端。假如PM.WakeLock所在进程在调用release WakeLock函数之前死亡,PMS.WakeLock的binderDied函数就会被调用,这样,PMS也能及时进行释放(release)工作。对于系统的重要资源来说,采用这种安全保护措施很有必要。

回到acquireWakeLockLocked函数,先看第一段代码:

[—>PowerManagerService.java:acquireWakeLockLocked]


public void acquireWakeLockLocked(int flags, IBinder lock, int uid,

int pid, String tag, WorkSource ws){

……

//mLocks是一个ArrayList,保存PMS.WakeLock对象

int index=mLocks.getIndex(lock);

WakeLock wl;

boolean newlock;

boolean diffsource;

WorkSource oldsource;

if(index<0){

//创建一个PMS.WakeLock对象,保存客户端acquire传来的参数

wl=new WakeLock(flags, lock, tag, uid, pid);

switch(wl.flags&LOCK_MASK)

{//将flags转换成对应的minState

case PowerManager.FULL_WAKE_LOCK:

if(mUseSoftwareAutoBrightness){

wl.minState=SCREEN_BRIGHT;

}else{

wl. minState=(mKeyboardVisible?ALL_BRIGHT:SCREEN_BUTTON_BRIGHT);

}

break;

case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:

wl.minState=SCREEN_BRIGHT;

break;

case PowerManager.SCREEN_DIM_WAKE_LOCK:

wl.minState=SCREEN_DIM;

break;

case PowerManager.PARTIAL_WAKE_LOCK:

//PROXIMITY_SCREEN_OFF_WAKE_LOCK在SDK中并未输出,原因是有部分手机并没有接近

//传感器

case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:

break;

default:

return;

}

mLocks.addLock(wl);//将PMS.WakeLock对象保存到mLocks中

if(ws!=null){

wl.ws=new WorkSource(ws);

}

newlock=true;//设置几个参数信息,newlock表示新创建了一个PMS.WakeLock对象

diffsource=false;

oldsource=null;

}else{

//如果之前保存有PMS.WakeLock,则要判断新传入的WorkSource和之前保存的WorkSource

//是否一样。此处不讨论这种情况

……

}


在上面代码中,很重要的一部分就是将前面flags信息转成PMS.WakeLock的成员变量minState,下面是对转换关系的总结。

FULL_WAKE_LOCK:当启用mUseSoftwareAutoBrightness时,minState为SCREEN_BRIGHT(表示屏幕全亮),否则为ALL_BRIGHT(屏幕、键盘、按键全亮。注意,只有在打开键盘时才能选择此项)或SCREEN_BUTTON_BRIGHT(屏幕、按键全亮)。

SCREEN_BRIGHT_WAKE_LOCK:minState为SCREEN_BRIGHT,表示屏幕全亮。

SCREEN_DIM_WAKE_LOCK:minState为SCREEN_DIM,表示屏幕Dim。

对PARTIAL_WAKE_LOCK和PROXIMITY_SCREEN_OFF_WAKE_LOCK情况不做处理。

该做的准备工作都做了,下面来看第二阶段的工作。

2.acquireWakeLockLocked分析之二

这部分的代码如下:

[—>PowerManagerService.java:acqurieWakeLockLocked]


//isScreenLock用于判断flags是否和屏幕有关,除PARTIAL_WAKE_LOCK外,其他WAKE_LOCK

//都和屏幕有关

if(isScreenLock(flags)){

if((flags&LOCK_MASK)==PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK){

mProximityWakeLockCount++;//引用计数控制

if(mProximityWakeLockCount==1){

enableProximityLockLocked();//使能Proximity传感器

}

}else{

if((wl.flags&PowerManager.ACQUIRE_CAUSES_WAKEUP)!=0){

……//ACQUIRE_CAUSES_WAKEUP标志处理

}else{

//①gatherState返回一个状态,稍后分析该函数

mWakeLockState=(mUserState|mWakeLockState)&mLocks.gatherState();

}

//②设置电源状态

setPowerState(mWakeLockState|mUserState);

}

}


以上代码列出了两个关键函数,一个是gatherState,另外一个是setPowerState,下面来分析它们。

(1)gatherState分析

gatherState函数的代码如下:

[—>PowerManagerService.java:gatherState]


int gatherState()

{

int result=0;

int N=this.size();

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

WakeLock wl=this.get(i);

if(wl.activated)

if(isScreenLock(wl.flags))

result|=wl.minState;//对系统中所有活跃的PMS.WakeLock的状态进行“或”操作

}

return result;

}


由以上代码可知,gatherState将统计当前系统内部活跃的WakeLock的minState。这里为什么要使用或操作呢?举个例子,假如WakeLock A的minState为SCREEN_DIM,而WakeLock B的minState为SCREEN_BRIGHT,二者共同作用,最终的屏幕状态显然应该是SCREEN_BRIGHT。

下面来分析setPowerState函数。

(2)setPowerState分析

setPowerState用于设置电源状态,先来看其在代码中的调用:


setPowerState(mWakeLockState|mUserState);


在以上代码中除了mWakeLockState外,还有一个mUserState。根据前面对gatherState函数的介绍可知,mWakeLockState的值来源于系统当前活跃的WakeLock的minState。那么mUserState代表什么呢?

mUserState代表用户触发事件导致的电源状态。例如,按Home键后,将该值设置为SCREEN_BUTTON_BRIGHT(假设手机没有键盘)。很显然,此时系统的电源状态应该是mUserState和mWakeLockState的组合。

提示“一个小小的变量背后代表了一个很重要的case”,读者能体会到吗?

下面来看setPowerState的代码,这段代码较长,也适合分段来看。第一段代码如下:

[—>PowerManagerService.java:setPowerState]


private void setPowerState(int state)

{//调用另外一个同名函数

setPowerState(state, false, WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT);

}

//setPowerState

private void setPowerState(int newState, boolean noChangeLights, int reason)

{

synchronized(mLocks){

int err;

if(noChangeLights)//在这种情况中,noChangeLights为false

newState=(newState&~LIGHTS_MASK)|(mPowerState&LIGHTS_MASK);

if(mProximitySensorActive)//如果打开了接近感应器,就不需要在这里点亮屏幕了

newState=(newState&~SCREEN_BRIGHT);

if(batteryIsLow())//判断是否处于低电状态

newState|=BATTERY_LOW_BIT;

else

newState&=~BATTERY_LOW_BIT;

……

//如果还没启动完成,则需要将newState置为ALL_BRIGHT。细心的读者有没有发现,在手机开机过程中键

盘、屏幕、按键等都会全部点亮一会儿呢?

if(!mBootCompleted&&!mUseSoftwareAutoBrightness)

newState|=ALL_BRIGHT;

boolean oldScreenOn=(mPowerState&SCREEN_ON_BIT)!=0;

boolean newScreenOn=(newState&SCREEN_ON_BIT)!=0;

final boolean stateChanged=mPowerState!=newState;


第一段代码主要用于得到一些状态值,例如在新状态下屏幕是否需要点亮(new-ScreenOn)等。再来看第二段代码,它将根据第一段的状态值完成对应的工作。

[—>PowerManagerService:setPowerState]


if(oldScreenOn!=newScreenOn){

if(newScreenOn){

if(mStillNeedSleepNotification){

//对sendNotificationLocked函数的分析见后文

sendNotificationLocked(false,

WindowManagerPolicy.OFF_BECAUSE_OF_USER);

}//if(mStillNeedSleepNotification)判断结束

boolean reallyTurnScreenOn=true;

if(mPreventScreenOn)//mPreventScreenOn是何方神圣?见下分的分析

reallyTurnScreenOn=false;

if(reallyTurnScreenOn){

err=setScreenStateLocked(true);//点亮屏幕

……//通知mBatteryStats做电量统计

mBatteryStats.noteScreenBrightness(getPreferredBrightness());

mBatteryStats.noteScreenOn();

}else{//reallyTurnScreenOn为false

setScreenStateLocked(false);//关闭屏幕

err=0;

}

if(err==0){

sendNotificationLocked(true,-1);

if(stateChanged)

updateLightsLocked(newState,0);//点亮按键灯或者键盘灯

mPowerState|=SCREEN_ON_BIT;

}

}


以上代码看起来比较简单,就是根据情况点亮或关闭屏幕。事实果真的如此吗?还记得前面说的“一个小小的变量背后代表一个很重要的case”这句话吗?是的,这里也有一个很重要的case,由mPreventScreenOn表达。这是什么意思呢?

PMS提供了一个函数叫preventScreenOn,该函数(在SDK中未公开)使应用程序可以阻止屏幕点亮。为什么会有这种操作呢?根据该函数的解释,在两个应用之间进行切换时(尤其是正在启动一个Activity却又接到来电通知时),很容易出现闪屏现象,会严重影响用户体验。因此提供了此函数,由应用来调用并处理它。

注意 闪屏的问题似乎解决了,但事情还没完,这个解决方案还引入了另外一个问题:假设应用忘记重新使屏幕点亮,手机岂不是一直就黑屏了?为此,在代码中增加了一段处理逻

辑,即如果5秒后应用还没有使屏幕点亮,PMS将设置mPreventScreenOn为false。

继续看setPowerState最后的代码:

else{//newScreenOn为false的情况

……//更新键盘灯、按键灯的状态

//从mHandler中移除mAutoBrightnessTask,这和光传感器有关。此处不讨论

mHandler.removeCallbacks(mAutoBrightnessTask);

mBatteryStats.noteScreenOff();//通知BatteryStatsService,屏幕已关

mPowerState=(mPowerState&~LIGHTS_MASK)|(newState&LIGHTS_MASK);

updateNativePowerStateLocked();

}

}//if(oldScreenOn!=newScreenOn)判断结束

else if(stateChanged){//屏幕的状态不变,但是light的状态有可能变化,所以单独更新light的状态

updateLightsLocked(newState,0);

}

mPowerState=(mPowerState&~LIGHTS_MASK)|(newState&LIGHTS_MASK);

updateNativePowerStateLocked();

}//setPowerState完毕


setPowerState函数是在PMS中真正设置屏幕及Light状态的地方,其内部将通过Power类与这些硬件交互。相关内容见5.3.3节。

(3)sendNotificationLocked函数分析

sendNotificationLocked函数用于触发SCREEN_ON/OFF广播的发送,来看以下代码:

[—>PowerManagerService.java:sendNotificationLocked]


private void sendNotificationLocked(boolean on, int why){

……

if(!on){

mStillNeedSleepNotification=false;

}

int index=0;

while(mBroadcastQueue[index]!=-1){

index++;

}

//mBroadcastQueue和mBroadcastWhy均定义为int数组,成员个数为3,它们有什么作用呢?

mBroadcastQueue[index]=on?1:0;

mBroadcastWhy[index]=why;

/*mBroadcastQueue数组一共有3个元素,根据代码中的注释,其作用如下:

当取得的index为2时,即0,1元素已经有值,由于屏幕ON/OFF请求是配对的,所以在这种情况

下只需要处理最后一次的请求。例如0元素为ON,1元素为OFF,2元素为ON,则可以去掉0,

1的请求,直接处理2的请求,即屏幕ON。对于那种频繁按Power键的操作,通过这种方式可以

节省一次切换操作

*/

if(index==2){

if(!on&&mBroadcastWhy[0]>why)mBroadcastWhy[0]=why;

//处理index为2的情况,见上文的说明

mBroadcastQueue[0]=on?1:0;

mBroadcastQueue[1]=-1;

mBroadcastQueue[2]=-1;

mBroadcastWakeLock.release();

index=0;

}

/*

如果index为1,on为false,即屏幕发出关闭请求,则无须处理。根据注释中的说明,

在此种情况,屏幕已经处于OFF状态,所以无须处理。为什么在此种情况下屏幕已经关闭了呢?

*/

if(index==1&&!on){

mBroadcastQueue[0]=-1;

mBroadcastQueue[1]=-1;

index=-1;

mBroadcastWakeLock.release();

}

if(mSkippedScreenOn){

updateLightsLocked(mPowerState, SCREEN_ON_BIT);

}

//如果index不为负数,则抛送mNotificationTask给mHandler处理

if(index>=0){

mBroadcastWakeLock.acquire();

mHandler.post(mNotificationTask);

}

}


sendNotificationLocked函数相当“诡异”,主要是mBroadcastQueue数组的使用让人感到困惑。其目的在于减少不必要的屏幕切换和广播发送,但是为什么index为1时,屏幕处于OFF状态呢?下面来分析mNotificationTask,希望它能回答这个问题。

[—>PowerManagerService.java:mNotificationTask]


private Runnable mNotificationTask=new Runnable()

{

public void run()

{

while(true){//此处是一个while循环

int value;

int why;

WindowManagerPolicy policy;

synchronized(mLocks){

value=mBroadcastQueue[0];//取mBroadcastQueue第一个元素

why=mBroadcastWhy[0];

for(int i=0;i<2;i++){//将后面的元素往前挪一位

mBroadcastQueue[i]=mBroadcastQueue[i+1];

mBroadcastWhy[i]=mBroadcastWhy[i+1];

}

policy=getPolicyLocked();//policy指向PhoneWindowManager

if(value==1&&!mPreparingForScreenOn){

mPreparingForScreenOn=true;

mBroadcastWakeLock.acquire();

}

}//synchronized结束

if(value==1){//value为1,表示发出屏幕ON请求

mScreenOnStart=SystemClock.uptimeMillis();

//和WindowManagerService交互,和锁屏界面有关

//mScreenOnListener为回调通知对象

policy.screenTurningOn(mScreenOnListener);

ActivityManagerNative.getDefault().wakingUp();//和AMS交互

if(mContext!=null&&ActivityManagerNative.isSystemReady()){

//发送SCREEN_ON广播

mContext.sendOrderedBroadcast(mScreenOnIntent, null,

mScreenOnBroadcastDone, mHandler,0,null, null);

}……

}else if(value==0){

mScreenOffStart=SystemClock.uptimeMillis();

policy.screenTurnedOff(why);//通知WindowManagerService

ActivityManagerNative.getDefault().goingToSleep();//和AMS交互

if(mContext!=null&&ActivityManagerNative.isSystemReady()){

//发送屏幕OFF广播

mContext.sendOrderedBroadcast(mScreenOffIntent, null,

mScreenOffBroadcastDone, mHandler,0,null, null);

}

}else break;

}

};


mNotificationTask比较复杂,但是它对mBroadcastQueue的处理比较有意思,每次取出第一个元素值后,将后续元素往前挪一位。这种处理方式能解决之前提出的那个问题吗?

说实话,目前笔者也没找到能解释index为1时,屏幕一定处于OFF的证据。如果有哪位读者找到证据,不妨分享一下。

另外,mNotificationTask和ActivityManagerService及WindowManagerService都有交互。因为这两个服务内部也使用了WakeLock,所以需要通知它们释放WakeLock,否则会导致不必要的电力资源消耗。具体内容只能留待以后分析相关服务时再来讨论了。

(4)acquireWakeLocked第二阶段工作总结

acquireWakeLocked第二阶段工作是处理和屏幕相关的WAKE_LOCK方面的工作(isScreenLock返回为true的情况)。其中一个重要的函数就是setPowerState,该函数将根据不同的状态设置屏幕光、键盘灯等硬件设备。注意,和硬件交互相关的工作是通过Power类提供的接口完成的。

3.acquireWakeLockLocked分析之三

这部分acquireWakeLocked主要处理WAKE_LOCK为PARTIAL_WAKE_LOCK的情况,来看以下代码:

[—>PowerManagerService.java:acquireWakeLockLocked]


else if((flags&LOCK_MASK)==PowerManager.PARTIAL_WAKE_LOCK){

if(newlock){

mPartialCount++;

}

//获取kernel层的PARTIAL_WAKE_LOCK,该函数后续再分析

Power.acquireWakeLock(Power.PARTIAL_WAKE_LOCK, PARTIAL_NAME);

}//else if判断结束

if(diffsource){

noteStopWakeLocked(wl, oldsource);

}

if(newlock||diffsource){

noteStartWakeLocked(wl, ws);//通知BatteryStatsService做电量统计

}


当客户端使用PARTIAL_WAKE_LOCK时,PMS会调用Power.acquireWakeLock申请一个内核的WakeLock。

4.acquireWakeLock总结

acquireWakeLock有3个阶段的工作,总结如下:

如果对应的WakeLock不存在,则创建一个WakeLock对象,同时将WAKE_LOCK标志转换成对应的minState;否则,从mLocks中查找对应的WakeLock对象,然后更新其中的信息。

当WAKE_LOCK标志和屏幕有关时,需要做相应的处理,例如点亮屏幕、打开按键灯等。实际上这些工作不仅影响电源管理,还会影响到用户感受,所以其中还穿插了一些和用户体验有关的处理逻辑(如上面注释的mPreventScreenOn变量)。

当WAKE_LOCK和PARTIAL_WAKE_LOCK有关时,仅简单调用Power的acquire-WakeLock即可,其中涉及和Linux Kernel电源管理系统的交互。