1. 程式人生 > >Android 中保持螢幕喚醒的方法

Android 中保持螢幕喚醒的方法

最近在解一個 bug 時,用到了這個知識點。在這裡總結一下:

bug 是這樣描述的:

在 Camera 切換到攝像時,攝像過程大概持續2,3分鐘,就自動進鎖屏了

有時也會持續很長時間進鎖屏。

這是一個概率性的問題(即隨機出現)。

從原理上分析上來看,肯定是螢幕被鎖住了。

log上分析會看到下面的資訊:


07-01 13:56:24.144 284 315 I ActivityManager: goingToSleep
07-01 13:56:24.154 100 201 I SurfaceTexture: [SurfaceView] [virtual android::status_t android::SurfaceTexture::convertToAuxSlot(bool)] create dst buffer and image
07-01 13:56:24.168 100 201 I MDP : [MDP INFO](201): mHalMdp_RegisterLoopMemory(): client id:0 VA:0x48ACC000 size:0x384000 color:4 size:(1280x720) roi:(0 0 1280 720) rotate:0
07-01 13:56:24.168 100 201 I MDP : [MDP INFO](201): mHalMdp_RegisterLoopMemory():MVA Obj Create:client id:0 0x48ACC000(VA) 0x00000000(MVA) 0x00000001(M4U flag) 0x00000001(alloc MVA flag)
07-01 13:56:24.168 100 201 I SurfaceTexture: register MVA success, type:1, srcImgYAddr:0x48acc000, SFTexCnt:6
07-01 13:56:24.207 284 315 D ActivityManager: ACT-AM_PAUSE_ACTIVITY ActivityRecord{41b2ba28 com.android.camera/.VideoCamera}
07-01 13:56:24.208 4187 4187 I videocamera: onPause


和在攝像介面按鎖屏鍵進鎖屏的log是一樣的。but 我什麼都沒按。

要不是我自己碰到了我還真不相信會有這個bug,從log 上看我一直認為是測試人員誤按了鎖屏鍵導致此問題的。

再來了解一下VideoCamera 是怎麼保持螢幕喚醒的呢?

與下面幾個函式有關。

    private void resetScreenOn() {
        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }

    private void keepScreenOnAwhile() {
        Log.d(TAG, TAG + "\t call keepScreenOnAwhile() !");
        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);

    }

    private void keepScreenOn() {
        Log.d(TAG, TAG + "\t call keepScreenOn() !");
        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }
                 case CLEAR_SCREEN_DELAY: {
                    Log.i(TAG, "received CLEAR_SCREEN_DELAY");
                    getWindow().clearFlags(
                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                    break;

在開始錄影的函式 startVideoRecording 中呼叫 keepScreenOn() 來保持螢幕喚醒狀態。

在停止錄影的函式 stopVideoRecording 中呼叫 keepScreenOnAwhile() 來繼續保持螢幕喚醒2分鐘。2分鐘後清除掉 FLAG_KEEP_SCREEN_ON ,使螢幕進入正常鎖屏流程。

在videocamera activity 的 onPause 函式中呼叫 resetScreenOn() 來清除掉 FLAG_KEEP_SCREEN_ON ,使螢幕可以進入正常鎖屏流程。

我們知道 onPause 函式一般是當該 Activity 在切換到後臺的時候執行的。檢查程式碼,發現在流程上並沒有問題。後來瞭解到是系統維護的這個 FLAG_KEEP_SCREEN_ON 有問題才導致這種現象產生的。

為了解決問題,我瞭解到,還有一種保持螢幕喚醒的方法,即電源管理的 wakeLock 鎖。瞭解到其用法如下:

{@samplecode
  PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
  PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
  wl.acquire();
    ..screen will stay on during this section..
  wl.release();
  }


先產生一個 PowerManager 物件,再獲取一個 wakelock 鎖。在需要保持螢幕喚醒的地方加上該鎖,恢復的時候釋放該鎖即可。需要注意一下的是 pm.newWakeLock 的第一個引數。從程式碼註釋上可以看到

 * <p>The following flags are defined, with varying effects on system power.  <i>These flags are
 * mutually exclusive - you may only specify one of them.</i>
 * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
 *
 *     <thead>
 *     <tr><th>Flag Value</th> 
 *     <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
 *     </thead>
 *
 *     <tbody>
 *     <tr><th>{@link #PARTIAL_WAKE_LOCK}</th>
 *         <td>On*</td> <td>Off</td> <td>Off</td> 
 *     </tr>
 *     
 *     <tr><th>{@link #SCREEN_DIM_WAKE_LOCK}</th>
 *         <td>On</td> <td>Dim</td> <td>Off</td> 
 *     </tr>
 *
 *     <tr><th>{@link #SCREEN_BRIGHT_WAKE_LOCK}</th>
 *         <td>On</td> <td>Bright</td> <td>Off</td> 
 *     </tr>
 *     
 *     <tr><th>{@link #FULL_WAKE_LOCK}</th>
 *         <td>On</td> <td>Bright</td> <td>Bright</td> 
 *     </tr>
 *     </tbody>
 * </table>
 

 
 其中 Dim 是表示暗淡,即半亮的意思,這時我們選擇 SCREEN_BRIGHT_WAKE_LOCK ,即 CPU  開 和 SCREEN 全亮 Keyboard 關

 來自 Google 開發者的提醒: 
 Device battery life will be significantly affected by the use of this API.
 Do not acquire WakeLocks unless you really need them, use the minimum levels possible,
 and be sure to release it as soon as you can.
 
 在 resetScreenOn(), keepScreenOn(), keepScreenOnAwhile() 及 case CLEAR_SCREEN_DELAY 下面的程式碼中,
 將 addFlags 處改為wl.acquire(),clearFlags 處改為wl.release() 即可最終這個問題就這樣通過換鎖解決了。 
 
 亦可參考:http://android6.blog.51cto.com/2035380/382792

續:換鎖以後,又出現了諸如退出照相機以後,到待機介面不能進鎖屏等嚴重問題。後來又回退了該修改。

再繼續分析原來的問題,分析 log

Line 112274: 09-28 10:33:19.114  1046  1046 I videocamera: actualNextUpdateDelay: 905
 Line 112483: 09-28 10:33:20.026  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 112484: 09-28 10:33:20.027  1046  1046 I videocamera: actualNextUpdateDelay: 982
 Line 112694: 09-28 10:33:21.010  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 112695: 09-28 10:33:21.011  1046  1046 I videocamera: actualNextUpdateDelay: 999
 Line 112914: 09-28 10:33:22.010  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 112915: 09-28 10:33:22.011  1046  1046 I videocamera: actualNextUpdateDelay: 998
 Line 113185: 09-28 10:33:23.008  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 113186: 09-28 10:33:23.009  1046  1046 I videocamera: actualNextUpdateDelay: 1000
  Line 113444: 09-28 10:33:23.571  1046  1046 V videocamera: keepScreenOnAwhile
 Line 113445: 09-28 10:33:23.571  1046  1046 V videocamera: fulin resetScreenOn addFlags FLAG_KEEP_SCREEN_ON
 Line 113446: 09-28 10:33:23.571  1046  1046 W videocamera: >> onUserInteraction >> removeMessages EXIT_VIDEOCAMERA when go to gallery
 Line 113446: 09-28 10:33:23.571  1046  1046 W videocamera: >> onUserInteraction >> removeMessages EXIT_VIDEOCAMERA when go to gallery
 Line 113447: 09-28 10:33:23.572  1046  1046 V videocamera: onTouch
 Line 113454: 09-28 10:33:23.575   298   507 V WindowManager: fulin obscured false w.mSurface Surface(name=com.android.camera/com.android.camera.VideoCamera, identity=109) attrFlags 16844160 w Window{421399d8 com.android.camera/com.android.camera.VideoCamera paused=false}
 Line 113454: 09-28 10:33:23.575   298   507 V WindowManager: fulin obscured false w.mSurface Surface(name=com.android.camera/com.android.camera.VideoCamera, identity=109) attrFlags 16844160 w Window{421399d8 com.android.camera/com.android.camera.VideoCamera paused=false}
 Line 113462: 09-28 10:33:23.577   298   507 V WindowManager: Update reported visibility: AppWindowToken{422ab530 token=Token{41a8b198 ActivityRecord{41a2f8b0 com.android.camera/.VideoCamera}}}
 Line 113463: 09-28 10:33:23.577   298   507 V WindowManager: Win Window{421399d8 com.android.camera/com.android.camera.VideoCamera paused=false}: isDrawn=true, isAnimating=false
 Line 113494: 09-28 10:33:23.593   298   507 V WindowManager: VIS AppWindowToken{422ab530 token=Token{41a8b198 ActivityRecord{41a2f8b0 com.android.camera/.VideoCamera}}}: interesting=2 visible=2
 Line 113586: 09-28 10:33:24.056  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 113587: 09-28 10:33:24.058  1046  1046 I videocamera: actualNextUpdateDelay: 952
 Line 113801: 09-28 10:33:25.022  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 113805: 09-28 10:33:25.025  1046  1046 I videocamera: actualNextUpdateDelay: 987
 Line 113998: 09-28 10:33:26.012  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 113999: 09-28 10:33:26.013  1046  1046 I videocamera: actualNextUpdateDelay: 996
 Line 114255: 09-28 10:33:27.073  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 114275: 09-28 10:33:27.089  1046  1046 I videocamera: actualNextUpdateDelay: 936
 Line 114484: 09-28 10:33:28.025  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 114485: 09-28 10:33:28.026  1046  1046 I videocamera: actualNextUpdateDelay: 983

  Line 7573: 09-28 10:35:22.018  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 7574: 09-28 10:35:22.018  1046  1046 I videocamera: actualNextUpdateDelay: 991
 Line 7794: 09-28 10:35:23.033  1046  1046 E videocamera: -------------updateRecordingTime----------mCaptureTimeLapse= false
 Line 7796: 09-28 10:35:23.060  1046  1046 I videocamera: actualNextUpdateDelay: 976
 Line 7912: 09-28 10:35:23.572  1046  1046 V videocamera: fulin handleMessage clearFlags FLAG_KEEP_SCREEN_ON
 Line 7912: 09-28 10:35:23.572  1046  1046 V videocamera: fulin handleMessage clearFlags FLAG_KEEP_SCREEN_ON

分析此 log 發現,使用者在錄相的過程中點了螢幕,執行了 onUserInteraction 函式。而在此函式中又呼叫了 keepScreenOnAwhile 函式,進而開啟了一個延時去清除 FLAG_KEEP_SCREEN_ON 該 flag,最終導致攝像過程中滅屏鎖屏。

原因終於分析清楚了。

修改方法:

    @Override
    public void onUserInteraction() {
        super.onUserInteraction();
        Log.v(TAG,TAG + "\t call onUserInteraction isRecording = "+mMediaRecorderRecording);
        if(!isRecording()){
	   keepScreenOnAwhile();		
        }
        if (!mMediaRecorderRecording && !mGoToGallery){
            exitVideoCameraAwhile();
        } else {
            if (mHandler != null) {
                Log.w(TAG, ">> onUserInteraction >> removeMessages EXIT_VIDEOCAMERA when go to gallery");
                mHandler.removeMessages(EXIT_VIDEOCAMERA);
            }
        }
    }


就是在 onUserInteraction() 函式呼叫 keepScreenOnAwhile() 之前新增一個判斷條件,如果是正在錄影的話,那麼就不執行此函數了。