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电源管理系统的交互。