一、什么是屏幕刷新機制
屏幕的刷新包括三個步驟:
- CPU 計算屏幕數據
- GPU 進一步處理和緩存
- Display 將緩存中(buffer)的屏幕數據顯示出來。
-
View 發起刷新的操作時,最終是走到了 ViewRootImpl 的 scheduleTraversals() 里去,然后這個方法會將遍歷繪制 View 樹的操作 performTraversals() 封裝到 Runnable 里,傳給 Choreographer,以當前的時間戳放進一個 mCallbackQueue 隊列里,然后調用了 native 層的方法向底層注冊監聽下一個屏幕刷新信號事件。
-
當下一個屏幕刷新信號發出的時候,如果對這個事件進行監聽,那么底層會回調 onVsync() 方法來通知。當 onVsync() 被回調時,會發一個 Message 到主線程,將后續的工作切到主線程來執行。切到主線程的工作就是去 mCallbackQueue 隊列里根據時間戳將之前放進去的 Runnable 取出來執行,而這些 Runnable 就是遍歷繪制 View 樹的操作 performTraversals()。遍歷操作完成后,就會去繪制那些需要刷新的 View。
-
當我們調用了 invalidate(),requestLayout(),等之類刷新界面的操作時,並不是馬上就會執行這些刷新的操作,而是通過 ViewRootImpl 的 scheduleTraversals() 先向底層注冊監聽下一個屏幕刷新信號事件,然后等下一個屏幕刷新信號來的時候,才會去通過 performTraversals() 遍歷繪制 View 樹來執行這些刷新操作。
-
導致界面刷新丟幀的原因有兩類:一是遍歷繪制 View 樹計算屏幕數據的時間超過了 16.6ms;二是,主線程一直在處理其他耗時的消息,導致遍歷繪制 View 樹的工作遲遲不能開始,從而超過了 16.6 ms 底層切換下一幀畫面的時機。第一個原因是因為我們寫的布局有問題,需要進行優化了。而第二個原因則是我們常說的避免在主線程中做耗時的任務。針對第二個原因,系統已經引入了同步屏障消息的機制,盡可能的保證遍歷繪制 View 樹的工作能夠及時進行,但仍沒辦法完全避免,所以我們還是得盡可能避免主線程耗時工作。
二、Choreographer機制
Choreographer機制,用於同Vsync機制配合,統一動畫、輸入和繪制的時機。
本節講解Choreographer機制主要是從繪制方面做闡述,界面的繪制要從ViewRootImpl的requestLayout開始說起,其源碼如下:
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); // 檢查是否在UI線程 mLayoutRequested = true; // 是否進行measure和layout布局 scheduleTraversals(); } } void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; // 攔截同步消息 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 執行繪制操作 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
這里需要注意的地方有兩點:
- postSyncBarrier()方法:Handler 的同步屏障,作用是可以攔截 Looper 對同步消息的獲取和分發,加入同步屏障之后Looper 只會獲取和處理異步消息,如果沒有異步消息那么就會進入阻塞狀態。通過同步屏障,就為UI繪制渲染處理操作的優先處理提供了基礎。
- Choreographer:編舞者,統一動畫、輸入和繪制時機。
1. Choreographer 啟動邏輯
Choreographer的創建是在ViewRootImpl的構造函數中。
public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); ... mChoreographer = Choreographer.getInstance(); ... }
下面是Choreographer的getInstance執行的代碼:
/** * Gets the choreographer for the calling thread. Must be called from * a thread that already has a {@link android.os.Looper} associated with it. * * @return The choreographer for this thread. * @throws IllegalStateException if the thread does not have a looper. */ public static Choreographer getInstance() { return sThreadInstance.get(); } // Thread local storage for the choreographer. private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() { @Override protected Choreographer initialValue() { Looper looper = Looper.myLooper(); if (looper == null) { throw new IllegalStateException("The current thread must have a looper!"); } Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); if (looper == Looper.getMainLooper()) { mMainInstance = choreographer; } return choreographer; } }; private static volatile Choreographer mMainInstance;
可以看到每一個Looper線程都有自己的Choreographer,其他線程發送的回調只能運行在對應Choreographer所屬的Looper線程上。
這里我們再看一下Choreographer的構造函數:
private Choreographer(Looper looper, int vsyncSource) { mLooper = looper; mHandler = new FrameHandler(looper); mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null; mLastFrameTimeNanos = Long.MIN_VALUE; mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); } // b/68769804: For low FPS experiments. setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1)); }
Choreographer類中有一個Looper和一個FrameHandler變量。變量USE_VSYNC用於表示系統是否是用了Vsync同步機制,該值是通過讀取系統屬性debug.choreographer.vsync來獲取的。如果系統使用了Vsync同步機制,則創建一個FrameDisplayEventReceiver對象用於請求並接收Vsync事件,最后Choreographer創建了一個大小為3的CallbackQueue隊列數組,用於保存不同類型的Callback。
不同類型的Callback包括如下4種:
CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT。
CallbackQueue是一個容量為4的數組,每一個元素作為頭指針,引出對應類型的鏈表,4種事件就是通過這4個鏈表來維護的。而FrameHandler中主要處理三類消息:
private final class FrameHandler extends Handler { public FrameHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DO_FRAME: doFrame(System.nanoTime(), 0); break; case MSG_DO_SCHEDULE_VSYNC: doScheduleVsync(); // 請求VSYNC信號 break; case MSG_DO_SCHEDULE_CALLBACK: doScheduleCallback(msg.arg1); break; } } }
2. Choreographer執行流程
執行流程就要需要從下面的代碼開始說起:
// 執行繪制操作 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
最終會調到 postCallbackDelayedInternal 方法:
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { synchronized (mLock) { // 當前時間 final long now = SystemClock.uptimeMillis(); // 回調執行時間,為當前時間加上延遲的時間 final long dueTime = now + delayMillis; // obtainCallbackLocked會將傳入的3個參數轉換為CallbackRecord,然后CallbackQueue根據回調類型將CallbackRecord添加到鏈表上。 mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime >= now) { // 如果delayMillis=0的話,dueTime=now,則會馬上執行 scheduleFrameLocked(now); } else { // 如果dueTime > now,則發送一個what為MSG_DO_SCHEDULE_CALLBACK類型的定時消息,等時間到了再處理,其最終處理也是執行scheduleFrameLocked(long now)方法 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }
mCallbackQueues先把對應的callback添加到鏈表上來,然后判斷是否有延遲,如果沒有則會馬上執行scheduleFrameLocked,如果有,則發送一個what為MSG_DO_SCHEDULE_CALLBACK類型的定時消息,等時間到了再處理,其最終處理也是執行scheduleFrameLocked(long now)方法。
private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { // 如果使用了VSYNC,由系統值確定 if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame on vsync."); } if (isRunningOnLooperThreadLocked()) { // 請求VSYNC信號,最終會調到Native層,Native處理完成后觸發FrameDisplayEventReceiver的onVsync回調,回調中最后也會調用doFrame(long frameTimeNanos, int frame)方法 scheduleVsyncLocked(); } else { // 在UI線程上直接發送一個what=MSG_DO_SCHEDULE_VSYNC的消息,最終也會調到scheduleVsyncLocked()去請求VSYNC信號 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { // 沒有使用VSYNC final long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); } // 直接發送一個what=MSG_DO_FRAME的消息,消息處理時調用doFrame(long frameTimeNanos, int frame)方法 Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } } }
判斷USE_VSYNC,如果使用了VSYNC,走scheduleVsyncLocked,即請求VSYNC信號,最終調用doFrame;如果沒使用VSYNC,則通過異步Message執行doFrame。
下面我們看一下doFrame的代碼:
void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (!mFrameScheduled) { return; // mFrameScheduled=false,則直接返回。 } long intendedFrameTimeNanos = frameTimeNanos; //原本計划的繪幀時間點 startNanos = System.nanoTime();//保存起始時間 //由於Vsync事件處理采用的是異步方式,因此這里計算消息發送與函數調用開始之間所花費的時間 final long jitterNanos = startNanos - frameTimeNanos; //如果線程處理該消息的時間超過了屏幕刷新周期 if (jitterNanos >= mFrameIntervalNanos) { //計算函數調用期間所錯過的幀數 final long skippedFrames = jitterNanos / mFrameIntervalNanos; //當掉幀個數超過30,則輸出相應log if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; frameTimeNanos = startNanos - lastFrameOffset; //對齊幀的時間間隔 } //如果frameTimeNanos小於一個屏幕刷新周期,則重新請求VSync信號 if (frameTimeNanos < mLastFrameTimeNanos) { scheduleVsyncLocked(); return; } mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); //分別回調CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL事件 mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
doCallBacks里的run方法執行了,也就是真正執行了View的繪制流程了。
3.Choreographer總結
1). Choreographer支持4種類型事件:輸入、繪制、動畫、提交,並通過postCallback在對應需要同步vsync進行刷新處進行注冊,等待回調。
2). Choreographer監聽底層Vsync信號,一旦接收到回調信號,則通過doFrame統一對java層4種類型事件進行回調。
三、屏幕繪制過程的流程圖
參考資料: