距離感應器下的休眠喚醒機制實現

前言

    從事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動不動就是上萬行代碼,需要靜下心來把裏面的流程梳理通,只要能做到這一步,很多需求也就沒那麼難了。


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