🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
**PowerManagerService.java::acquireWakeLock** ~~~ public void acquireWakeLock(int flags, IBinderlock, String tag, WorkSource ws) { intuid = Binder.getCallingUid(); intpid = 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); } longident = 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, Stringtag,WorkSource ws) { ...... //mLocks是一个ArrayList,保存PMS.WakeLock对象 int index= mLocks.getIndex(lock); WakeLockwl; booleannewlock; booleandiffsource; WorkSourceoldsource; if (index< 0) { //创建一个PMS.WakeLock对象,保存客户端acquire传来的参数 wl = new WakeLock(flags, lock, tag, uid, pid); switch(wl.flags & LOCK_MASK) { //将flags转换成对应的minState casePowerManager.FULL_WAKE_LOCK: if(mUseSoftwareAutoBrightness) { wl.minState = SCREEN_BRIGHT; }else { wl.minState = (mKeyboardVisible ? ALL_BRIGHT: SCREEN_BUTTON_BRIGHT); } break; casePowerManager.SCREEN_BRIGHT_WAKE_LOCK: wl.minState = SCREEN_BRIGHT; break; casePowerManager.SCREEN_DIM_WAKE_LOCK: wl.minState = SCREEN_DIM; break; case PowerManager.PARTIAL_WAKE_LOCK: //PROXIMITY_SCREEN_OFF_WAKE_LOCK在SDK中并未输出,原因是有部分手机并没有接近 //传感器 casePowerManager.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分析之二 代码如下: ~~~ //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() { intresult = 0; int N =this.size(); for (inti=0; i<N; i++) { WakeLock wl = this.get(i); if(wl.activated) if(isScreenLock(wl.flags)) result |= wl.minState;//对系统中所有活跃PMS.WakeLock的状态进行或操作 } returnresult; } ~~~ 由以上代码可知,gatherState将统计当前系统内部活跃WakeLock的minState。这里为什么要“使用”或“操作”呢?举个例子,假如WakeLock A的minState为SCREEN_DIM,而WakeLock B的minState为SCREEN_BRIGHT,二者共同作用,最终的屏幕状态显然应该是SCREEN_BRIGHT。 提示读者也可参考PowerManagerService中SCREEN_DIM等变量的定义。 下面来看setPowerState,本章前面曾两次对该函数避而不谈,现在该见识见识它了。 (2) setPowerState分析 setPowerState用于设置电源状态,先来看其在代码中的调用: setPowerState(mWakeLockState | mUserState); 在以上代码中除了mWakeLockState外,还有一个mUserState。根据前面对gatherState函数的介绍可知,mWakeLockState的值来源于系统当前活跃WakeLock的minState。那么mUserState代表什么呢? mUserState代表用户触发事件导致的电源状态。例如,按Home键后,将该值设置为SCREEN_BUTTON_BRIGHT(假设手机没有键盘)。很显然,此时系统的电源状态应该是mUserState和mWakeLockState的组合。 >[info] **提示 **“一个小小的变量背后代表了一个很重要的case”,读者能体会到吗? 下面来看setPowerState的代码,这段代码较长,也适合分段来看。第一段代码如下: **PowerManagerService.java::setPowerState** ~~~ private void setPowerState(int state) {//调用另外一个同名函数 setPowerState(state, false,WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT); } //setPowerState private void setPowerState(int newState, booleannoChangeLights, 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; booleanoldScreenOn = (mPowerState & SCREEN_ON_BIT) != 0; boolean newScreenOn = (newState &SCREEN_ON_BIT) != 0; finalboolean stateChanged = mPowerState != newState; ~~~ 第一段代码主要用于得到一些状态值,例如在新状态下屏幕是否需要点亮(newScreenOn)等。再来看第二段代码,它将根据第一段的状态值完成对应的工作。 **PowerManagerService::setPowerState** ~~~ if(oldScreenOn != newScreenOn) { if(newScreenOn) { if(mStillNeedSleepNotification) { //对sendNotificationLocked函数的分析见后文 sendNotificationLocked(false, WindowManagerPolicy.OFF_BECAUSE_OF_USER); }// mStillNeedSleepNotification判断 booleanreallyTurnScreenOn = 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。 * * * * * Google怎么会写这种代码?还好,代码开发者也意识到这是一个很难看的方法,只是目前还没有一个比较完美的解决方案而已。 继续看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的状态有可能变化,所以 updateLightsLocked(newState, 0);//单独更新light的状态 } 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 = newRunnable() { publicvoid run() { while(true) {//此处是一个while循环 intvalue; 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); }...... }elseif (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); } }elsebreak; } }; ~~~ mNotificationTask比较复杂,但是它对mBroadcastQueue的处理比较有意思,每次取出第一个元素值后,将后续元素往前挪一位。这种处理方式能解决之前提出的那个问题吗? 说实话,目前笔者也没找到能解释index为1时,屏幕一定处于OFF的证据。如果有哪位读者找到证据,不妨分享一下。 另外,mNotificationTask和ActivityManagerService及WindowManagerService都有交互。因为这两个服务内部也使用了WakeLock,所以需要通知它们释放WakeLock,否则会导致不必要的电力资源消耗。具体内容只能留待以后分析相关服务时再来讨论了。 (4) acquireWakeLocked第二阶段工作总结 acquireWakeLocked第二阶段工作是处理和屏幕相关的WAKE_LOCK方面的工作(isScreenLock返回为true的情况)。其中一个重要的函数就是setPowerState,该函数将根据不同的状态设置屏幕光、键盘灯等硬件设备。注意,和硬件交互相关的工作是通过Power类提供的接口完成的。 3. acquireWakeLocked分析之三 acquireWakeLocked处理WAKE_LOCK为PARTIAL_WAKE_LOCK的情况。来看以下代码: **PowerManagerService.java::acquiredWakeLockLocked** ~~~ 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有三个阶段的工作,总结如下: - 如果对应的WakeLock不存在,则创建一个WakeLock对象,同时将WAKE_LOCK标志转换成对应的minState;否则,从mLocks中查找对应的WakeLock对象,然后更新其中的信息。 - 当WAKE_LOCK标志和屏幕有关时,需要做相应的处理,例如点亮屏幕、打开按键灯等。实际上这些工作不仅影响电源管理,还会影响到用户感受,所以其中还穿插了一些和用户体验有关的处理逻辑(如上面注释的mPreventScreenOn变量)。 - 当WAKE_LOCK和PARTIAL_WAKE_LOCK有关时,仅简单调用Power的acquireWakeLock即可,其中涉及和Linux Kernel电源管理系统的交互。