距离感应器下的休眠唤醒机制实现

前言

    从事Android framework开发已经两年了,今天起决定把工作上遇到的问题、做过的需求都用博客的形式记录一下,特地新开一个系列---Android framework开发工作记录,有需要的朋友可以参考参考,当然,不保证完全正确,有错误的地方欢迎指出,本系列完全是根据工作上的需求来的,看起来肯定是杂乱无章的,请包涵。之前的快应用系列也会接着更新。废话不多说,下面就进入正题。


需求提出

    通过距离感应器实现靠近唤醒,远离休眠(跟接电话的灭屏亮屏功能正好相反)。拿开之后要求可以延迟休眠,延迟的时间可以动态去配,就像手机上的休眠时间可以在设置中动态选择一样15s、30s、1min ......


需求分析

    乍一看这个需求很简单的嘛,但是由于我们公司做的系统的特殊性,就产生了诸多问题,这里面的细节我就不多说了。对于这个需求我第一个想到的就是,直接监听一下P-Sensor,靠近的时候发一个power键键值,远离的时候再发一个,然后延迟的话就用Handler发一个延迟的消息来做,看起来好像是解决了,but too young, too simple.  测试之后才发现一堆问题,很难受,比如由于人为因素导致的休眠唤醒就不可控了,像主动按power键,连接充电器什么的亮灭屏。然后就开始尝试第二种方案,就是借用一下Android原生的休眠机制来做,当然这也是最终的实现方式,下面我们就来具体分析。


需求实现

第一步,对距离感应器进行监听,这里我就不详细解释了,毕竟重点不在这里,代码贴出来:

        SensorManager sensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);
        Sensor pSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
        sensorManager.registerListener(new SensorEventListener() {
            @Override
            public void onSensorChanged(SensorEvent event) {
                //value值在距感里面只有两个,0和另外一个,表示靠近和远离,不同机器对应关系可能不一样
                float value = event.values[0];
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {

            }
        }, pSensor, SensorManager.SENSOR_DELAY_NORMAL);

主要是在onSensorChanged这个方法里面对value的值进行监听。


第二步,利用系统休眠的api来设置休眠时间,代码如下:

Settings.System.putInt(getContentResolver(),android.provider.Settings.System.SCREEN_OFF_TIMEOUT,15*1000);

当然,我们这个需求是在系统服务中做的 ,普通应用没有权限的。Settings.System.SCREEN_OFF_TIMEOUT这个值就是表示休眠时间,单位毫秒。所以我们这个需求的实现就是,监听到距感靠近的时候就发一个WakeUp键值唤醒屏幕并且将SCREEN_OFF_TIMEOUT这个值设成很大,远离的时候就将SCREEN_OFF_TIMEOUT这个值设成我们想要延迟的时间。到这里基本功能就实现了,但是测试的时候发现连续休眠唤醒会导致延迟的时间不对,跟我们设置的休眠时间有出入,这个时候我们就需要研读一下系统源码了,梳理一下系统的休眠机制,来看看到底什么地方出了问题。


第三步,阅读源码,找到上面问题的原因

代码路径:frameworks\base\services\core\com\android\server\power\PowerManagerService.java

提到PowerManagerService我们不得不说一个很重要的方法updatePowerStateLocked()  ,代码还是贴一下:

    private void updatePowerStateLocked() {  
        if (!mSystemReady || mDirty == 0) {  
            return;  
        }  
        if (!Thread.holdsLock(mLock)) {  
            Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked");  
        }  
  
        Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState");  
        try {  
            // Phase 0: Basic state updates.  
            updateIsPoweredLocked(mDirty);  
            updateStayOnLocked(mDirty);  
            updateScreenBrightnessBoostLocked(mDirty);  
  
            // Phase 1: Update wakefulness.  
            // Loop because the wake lock and user activity computations are influenced  
            // by changes in wakefulness.  
            final long now = SystemClock.uptimeMillis();  
            int dirtyPhase2 = 0;  
            for (;;) {  
                int dirtyPhase1 = mDirty;  
                dirtyPhase2 |= dirtyPhase1;  
                mDirty = 0;  
  
                updateWakeLockSummaryLocked(dirtyPhase1);  
                updateUserActivitySummaryLocked(now, dirtyPhase1);  
                if (!updateWakefulnessLocked(dirtyPhase1)) {  
                    break;  
                }  
            }  
  
            // Phase 2: Update display power state.  
            boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);  
  
            // Phase 3: Update dream state (depends on display ready signal).  
            updateDreamLocked(dirtyPhase2, displayBecameReady);  
  
            // Phase 4: Send notifications, if needed.  
            if (mDisplayReady) {  
                finishWakefulnessChangeLocked();  
            }  
  
            // Phase 5: Update suspend blocker.  
            // Because we might release the last suspend blocker here, we need to make sure  
            // we finished everything else first!  
            updateSuspendBlockerLocked();  
        } finally {  
            Trace.traceEnd(Trace.TRACE_TAG_POWER);  
        }  
    }  

这个方法的作用就是更新电源的状态,一会儿再说这个跟我们上面的问题的关系。

因为原生设置的休眠流程是好的,所以这个时候就需要参考一下了,于是我就去看了设置的源码,发现它在设置休眠时间的时候也只是调用了我们上面的那个api,这就奇怪了,为什么使用的相同的api会出现不同的结果,为这个问题我也是耗了很长时间,最后终于找到问题的关键了,这里面的不同之处在于我们是直接在系统服务里面进行的,而设置是用户操作的,我们的那个操作会导致电源状态未更新,导致休眠时间和我们设置的有出入,有点小坑额。


第四步,解决bug,完成需求

    要解决以上问题我们就需要找到一个能够更新电源状态的地方,通过分析我们也是找到了,就在下面这个方法里面:

private final class SettingsObserver extends ContentObserver {  
        public SettingsObserver(Handler handler) {  
            super(handler);  
        }  
  
        @Override  
        public void onChange(boolean selfChange, Uri uri) {  
            synchronized (mLock) {  
                handleSettingsChangedLocked();  
            }  
        }  
    }  

这个onChange方法就能监听到Settings.System.SCREEN_OFF_TIMEOUT这个值得改变,所以我们在里面手动调用userActivityInternal()这个方法更新一下电源状态就行,代码如下:

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

参数传入当前时间就行,

userActivityNoUpdateLocked(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);

ok,到此就完美解决了所有问题。由于公司有保密协议,我这里就不把所有代码全都贴出来了,有兴趣的可以下一份Android源码自己试试。


总结

    其实framework开发中,最难的莫过于阅读系统源码,Android源码中很多service动不动就是上万行代码,需要静下心来把里面的流程梳理通,只要能做到这一步,很多需求也就没那么难了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章