车载设置--自动关屏 PowerManagerService 源码浅析

上文分析了车载设置按键音的实现,对 Android 的源码进行了部分修改,此文趁热打铁,继续对 Android 源码进行分析,并了解 Anddroid 的休眠机制。想了解 Android 按键音的实现,可阅读上文 车载设置–按键提示音

使用场景
一般在车载应用设置中,有一个 自动关屏 的选项,通常有 30s,60s,2分钟等选项,当我们选择了 30s 选项后,在屏幕无操作后或者面板无操作 30s 后自动关屏,进入屏保界面。当点击屏保或者操作方控键,空调面板,屏保消失从而进入相应的界面。如果按照 Android 休眠机制来做,当屏幕无操作后黑屏后此时触摸无作用,除非按电源键解锁,此时做任何操作也无法点亮屏幕,基于这一点关屏后无触摸就不符合车载的需求,所以需要利用源码来达到类似的效果。

Android 休眠机制
通常我们在手机上观看视频时,无论我们多久没触摸,手机将一直操持亮屏状态,那么手机上这个不黑屏是怎么实现的呢?其实就是因为该播放界面持有 wakeLock ,wakeLock 通常的用法为:

PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG);
mWakeLock.acquire();

以此就来分析 Android 休眠的机制。
1. WakeLock 的初始化

 public WakeLock newWakeLock(int levelAndFlags, String tag) {
        validateWakeLockParameters(levelAndFlags, tag);
        return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName());
    }

 WakeLock(int flags, String tag, String packageName) {
            mFlags = flags;
            mTag = tag;
            mPackageName = packageName;
            mToken = new Binder();
}
可以看到,newWakeLock 方法首先检测 LevelAndFlags 和 Tag 的合法性,代码很简单,
其实就是:tag 不能为空,Level必须用 PowerManager 提供的几个Level。

/**
* 当在应用调用 acquire ,申请锁,执行的就是这个函数
*/
 public void acquire() {
            synchronized (mToken) {
                acquireLocked();
            }
}

 private void acquireLocked() {
            if (!mRefCounted || mCount++ == 0) {
                // Do this even if the wake lock is already thought to be held (mHeld == true)
                // because non-reference counted wake locks are not always properly released.
                // For example, the keyguard's wake lock might be forcibly released by the
                // power manager without the keyguard knowing.  A subsequent call to acquire
                // should immediately acquire the wake lock once again despite never having
                // been explicitly released by the keyguard.
                mHandler.removeCallbacks(mReleaser);
                try {
                    mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);
                } catch (RemoteException e) {
                }
                mHeld = true;
            }
}

从源码中可以看出在应用层调用 acquire ,调用的是mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource),而真正的执行是在 PowerManagerService 。那么就来深入了解 PowerManagerService 的实现。

 public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
            WorkSource ws) {
       //代码省略
        final int uid = Binder.getCallingUid();
        final int pid = Binder.getCallingPid();
        final long ident = Binder.clearCallingIdentity();
        try {
            acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
    private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
            WorkSource ws, int uid, int pid) {
        synchronized (mLock) {

            WakeLock wakeLock;
            int index = findWakeLockIndexLocked(lock);
            if (index >= 0) {
                wakeLock = mWakeLocks.get(index);
                if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
                    // Update existing wake lock.  This shouldn't happen but is harmless.
                    notifyWakeLockReleasedLocked(wakeLock);
                    wakeLock.updateProperties(flags, tag, packageName, ws, uid, pid);
                    notifyWakeLockAcquiredLocked(wakeLock);
                }
            } else {
                wakeLock = new WakeLock(lock, flags, tag, packageName, ws, uid, pid);
                try {
                    lock.linkToDeath(wakeLock, 0);
                } catch (RemoteException ex) {
                    throw new IllegalArgumentException("Wake lock is already dead.");
                }
                notifyWakeLockAcquiredLocked(wakeLock);
                mWakeLocks.add(wakeLock);
            }

            applyWakeLockFlagsOnAcquireLocked(wakeLock);
            mDirty |= DIRTY_WAKE_LOCKS;
            updatePowerStateLocked();
        }

updatePowerStateLocked 更新电源状态,该函数是 PMS 的核心代码。 当手指触摸时 mDirty = DIRTY_USER_ACTIVITY

 private void updatePowerStateLocked() {
        if (!mSystemReady || mDirty == 0) {  //未启动完毕,或者 mDirty 没有变化
            return;
        }

        // Phase 0: Basic state updates. // 更新基本状态
        updateIsPoweredLocked(mDirty);//只有 mDirty 为 RTY_BATTERY_STATE 才执行
        updateStayOnLocked(mDirty);   只有 mDirty 为 DIRTY_BATTERY_STATE | DIRTY_SETTINGS 才执行

        // Phase 1: Update wakefulness.
        // Loop because the wake lock and user activity computations are influenced
        // by changes in wakefulness.   // 2、更新 wakelock 和用户活动 
        final long now = SystemClock.uptimeMillis();
        int dirtyPhase2 = 0;
        for (;;) {
            int dirtyPhase1 = mDirty;   
            dirtyPhase2 |= dirtyPhase1;
            mDirty = 0;                   //mDirty 重新赋值为  0 

            updateWakeLockSummaryLocked(dirtyPhase1); //调用PMS的acquireWakeLock时,就会将dirty的DIRTY_WAKE_LOCKS位置1
            updateUserActivitySummaryLocked(now, dirtyPhase1); //要根据用户最后的活动来决定当前屏幕的状态 重点分析
            if (!updateWakefulnessLocked(dirtyPhase1)) {
                break;
            }
        }

        // Phase 2: Update dreams and display power state.
        updateDreamLocked(dirtyPhase2);           // 更新dream state
        updateDisplayPowerStateLocked(dirtyPhase2);

        // Phase 3: Send notifications, if needed.
        if (mDisplayReady) {
            sendPendingNotificationsLocked();
        }

        // Phase 4: Update suspend blocker.
        // Because we might release the last suspend blocker here, we need to make sure
        // we finished everything else first!
        updateSuspendBlockerLocked();      // 更新suspend blocker
        //updateBootFastSuspendBlockerLocked();
    }

updateUserActivitySummaryLocked 的分析:

// Update the status of the user activity timeout timer.
if ((dirty & (DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) {
        mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);

        long nextTimeout = 0;
        if (mWakefulness != WAKEFULNESS_ASLEEP) {
             //获取进入休眠状态的时间sleepTimeout
            //getSleepTimeoutLocked中会判断休眠时间和屏幕熄灭时间的关系
            //如果休眠时间sleepTimeout小于屏幕熄灭时间screenOfftime,  
            //则休眠时间被调整为屏幕熄灭时间,因为屏幕亮屏状态下,终端不能进入休眠
            //获取屏幕熄灭的时间
            final int screenOffTimeout = getScreenOffTimeoutLocked();

             //获取屏幕变暗的时间
            final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);

           //将当前的用户事件,转化为PMS可以处理的屏幕状态
            mUserActivitySummary = 0;
            //在唤醒的状态下,发生过用户事件
            if (mLastUserActivityTime >= mLastWakeTime) {
                nextTimeout = mLastUserActivityTime
                            + screenOffTimeout - screenDimDuration;
                if (now < nextTimeout) {
                    //如果没有到达需要变暗的时间,那么当前屏幕的状态为USER_ACTIVITY_SCREEN_BRIGHT(亮屏)
                    mUserActivitySummary |= USER_ACTIVITY_SCREEN_BRIGHT;
                } else {
                      //到达变暗的时间,则计算出屏幕熄灭的时间
                    nextTimeout = mLastUserActivityTime + screenOffTimeout;
                    if (now < nextTimeout) {
                    //还没到熄灭的时间,则当前屏幕的状态为USER_ACTIVITY_SCREEN_DIM(暗屏)
                       mUserActivitySummary |= USER_ACTIVITY_SCREEN_DIM;
                    }
                }
            }
            if (mUserActivitySummary == 0
                    && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) {
                nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
                if (now < nextTimeout
                        && mDisplayPowerRequest.screenState
                                != DisplayPowerRequest.SCREEN_STATE_OFF) {
                   mUserActivitySummary = mDisplayPowerRequest.screenState
                            == DisplayPowerRequest.SCREEN_STATE_BRIGHT ?
                            USER_ACTIVITY_SCREEN_BRIGHT : USER_ACTIVITY_SCREEN_DIM;
                }
            }

从上面的代码可以看出,在该函数中用 mUserActivitySummary 变量存储当前屏幕的状态。
一共有3中基本状态:
* USER_ACTIVITY_SCREEN_BRIGHT 点亮屏幕
* USER_ACTIVITY_SCREEN_DIM 屏幕变暗
* USER_ACTIVITY_SCREEN_DREAM 屏保状态
从代码可以看出,屏幕变化和 userActivity 活动有关,它根据最后的 userActivity 活动的时间决定点亮屏幕、调暗屏幕或熄灭屏幕 。

 private boolean updateWakefulnessLocked(int dirty) {
        boolean changed = false;
        if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
                | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
                | DIRTY_DOCK_STATE)) != 0) {
            if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
                if (DEBUG_SPEW) {
                    Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
                }
                final long time = SystemClock.uptimeMillis();
                //主要根据设置信息,判断是否满足进入Dream状态的条件
                if (shouldNapAtBedTimeLocked()) {
                      //将mWakefullness的值置为WAKEFULNESS_DREAMING,修改mDirty变量,并进行通知等
                    changed = napNoUpdateLocked(time);
                } else {
                     //将mWakefullness的值置为WAKEFULNESS_DOZING
                //如果系统设置了跳过Dozing态,则将mWakefullness置为WAKEFULNESS_ASLEEP
                //同时修改mDirty变量,并进行通知等
                changed = goToSleepNoUpdateLocked(time,
                    changed = goToSleepNoUpdateLocked(time,
                            PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
                }
            }
        }
        return changed;
    }

在用户发生触摸时,会调用 userActivityInternal:

public void userActivity(long eventTime, int event, int flags) {
        // 此处省略
        final int uid = Binder.getCallingUid();
        final long ident = Binder.clearCallingIdentity();
        try {
            userActivityInternal(eventTime, event, flags, uid);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

private void userActivityInternal(long eventTime, int event, int flags, int uid) {
        synchronized (mLock) {
            if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
                updatePowerStateLocked();
            }
        }
    }

继续往下跟:userActivityNoUpdateLocked 代码如下:

private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
        if (eventTime < mLastSleepTime || eventTime < mLastWakeTime
                || mWakefulness == WAKEFULNESS_ASLEEP || !mBootCompleted || !mSystemReady) {
            return false;
        }

        mNotifier.onUserActivity(event, uid);

        if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
            if (eventTime > mLastUserActivityTimeNoChangeLights
                    && eventTime > mLastUserActivityTime) {
                mLastUserActivityTimeNoChangeLights = eventTime;
                mDirty |= DIRTY_USER_ACTIVITY;
                return true;
            }
        } else {  //此时 flags=0x0
            if (eventTime > mLastUserActivityTime) {  //和上次触摸时间比较
                mLastUserActivityTime = eventTime;    //记住触摸时间
                mDirty |= DIRTY_USER_ACTIVITY;
                return true;
            }
        }
        return false;
    }

移植流程:
1.参考源码自定义一个变量,用来存储休眠时间 mScreenOffTimeoutXX,初始化的位置与休眠时间一致:

 mScreenOffTimeoutSetting = Settings.System.getIntForUser(resolver,
                Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT,
                UserHandle.USER_CURRENT);
  mScreenOffTimeoutSoling = Settings.System.getIntForUser(resolver,"XX_timeout", -1,UserHandle.USER_CURRENT);
 if(mNotifier!=null)
    mNotifier.onUserActivity(mScreenOffTimeoutSoling);

2.监听关屏时间的改变,同监听休眠时间 一样:

 resolver.registerContentObserver(Settings.System.getUriFor(Settings.System.SCREEN_OFF_TIMEOUT),
                     false, mSettingsObserver, UserHandle.USER_ALL);
 resolver.registerContentObserver(Settings.System.getUri"XX_timeout"),false, mSettingsObserver, UserHandle.USER_ALL);

3.当在用户申请锁时,存储包名,方便过滤本地应用,即 acquireWakeLockInternal 发送通知

 if(mNotifier!=null)
 mNotifier.onWakeLockAcquired(wakeLock.mPackageName);

4.当用户发生触摸操作时,重新计时,即在 userActivityNoUpdateLocked 开头添加如下代码:

 int timeout = mScreenOffTimeoutSoling;
mNotifier.onUserActivity(timeout);
//后面为源码部分,不在后面添加,避免被 return
 if (eventTime < mLastSleepTime || eventTime < mLastWakeTime
                || mWakefulness == WAKEFULNESS_ASLEEP || !mBootCompleted || !mSystemReady) {
            return false;
}

5.当锁被释放时,不在对相应的包名过滤,即在 releaseWakeLockInternal 添加代码:

f(mNotifier!=null)
   mNotifier.onWakeLockReleased(wakeLock.mPackageName,mScreenOffTimeoutXX);
mWakeLocks.remove(index);
notifyWakeLockReleasedLocked(wakeLock);
wakeLock.mLock.unlinkToDeath(wakeLock, 0);

这个代码的移植都是参照源码,而真正发送消息的地方是在 Notifier 类中,在此类中,做延迟操作,发送广播通知。对对事件的处理分为如下几种:
1. 当本地应用如视频,申请锁时,取消计时,永不黑屏;
2. 当用户有点击事件时,重新计时,同时判断本地应用是否申请了锁,如果申请了,仍然不计时;
3. 当本地应用释放锁时,重新计时。
基于上述分析,开始一直 Notifier 代码。

private static final int MSG_USER_ACTIVITY_XX = 6;  用于 handler 用于发送的 MSG
private String[] mXXApps = {,"com.xx.call,"com.xx.video"};  //用于过滤包名,包名相同,就不黑屏

/**
*  当应用申请锁时,判断是不是本地应用,是本地应用,就存储起来,同时取消计时
*/
public void onWakeLockAcquired(String pkgName){
          if(isXXApp(pkgName)){
                   mLockPkgName = pkgName;
                   mHandler.removeMessages(MSG_USER_ACTIVITY_SOLING);
            }
}

/**
* 当触摸发生时,如果是本地应用,则不处理,
* 不是本地应用,重新计时
*/
public void onUserActivity(int timeout){
     synchronized (mLock){
          if(!isXXApp(mLockPkgName)){
          mHandler.removeMessages(MSG_USER_ACTIVITY_XX);
          if(timeout!=-1&&timeout!=0)
                 mHandler.sendEmptyMessageDelayed(MSG_USER_ACTIVITY_XX,timeout);
          ,  }
     }
}

/**
* 释放锁时,重新计时
*/
public void onWakeLockReleased(String pkgName,int timeout){
           if(isSolingApp(pkgName)){
                   mLockPkgName = "";
                   mHandler.removeMessages(MSG_USER_ACTIVITY_SOLING);
                   if(timeout!=-1&&timeout!=0)
                    mHandler.sendEmptyMessageDelayed(MSG_USER_ACTIVITY_SOLING,
           }
}
/**
* 在 timeout 时间到了之后,发送通知,在接受通知中,处理逻辑:比如关闭背光
*/
private void sendUserActivityToSoling() {
            Intent intent = new Intent();
           intent.setAction("com.action.xx.screen.off");
           if(mContext!=null)
             mContext.sendBroadcast(intent);
 }
/**
* 白名单 
*/
private boolean isXXApp(String pkgName){
         for(int i = 0;i<mSolingApps.length;i++){
             if(mSolingApps[i].equals(pkgName))
               return true;               
         }
         return false;
}

总结
其实自己本身对 WakeLock 机制也并不是很了解,也是通过不断的打印 log 分析而来,在分析 log 中才有了看似可行的思路:
1. 当本地应用申请锁时,取消计时;
2. 当用户有操作时,会调用 userActivity 方法,在此方法中,重新计时;
3. 当本地应用释放锁时,重新计时;
4. 监听 timeout 的改变,重新计时。
有了上面的思路,整个移植过程看起来就很清晰明了,同时配合 log 分析,基本就能达到预期效果。后续如果有 bug ,再来分析,可能会对 wakeLock 机制有更深的了解,到时再来更新博客。

猜你喜欢

转载自blog.csdn.net/liqianwei1230/article/details/78306480