[源碼分析]Android View繪製流程--從同步屏障説起

語言: CN / TW / HK

View的繪製過程主要都包含在ViewRootImpl#performTraversal方法內,這個方法內主要包括了measure、layout、draw這三個步驟,具體就不放在這裏講述了。我們主要關注下,每次繪製時Framework內是怎麼發起到調用的。

1. requestLayout請求繪製

app側不論是調用requestLayout或是調用invalidate方法來觸發重繪,最終都會調用schedueTraversal方法,開始繪製流程,只不過是在該流程中決定是否執行measure、layout或者draw。

ViewRootImpl.java ``` public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }

void invalidate() { mDirty.set(0, 0, mWidth, mHeight); if (!mWillDrawSoon) { scheduleTraversals(); } } ``` 我們重點看下scheduleTraversals方法做了哪些工作

``` void scheduleTraversals() { //mTraversalScheduled字段保證同時間內的多次修改只會執行一次渲染過程(如更新text時) if (!mTraversalScheduled) { mTraversalScheduled = true; // 代碼1.1 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 代碼1.2 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }

final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // 移除同步屏障 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); //執行繪製過程 包括:measure、layout、draw過程 performTraversals();

}

} ``` 這裏的mTraversalRunnable就一個包含調用doTraversal方法的線程,而doTraversal方法主要就是調用performTraversals來進行view的繪製過程

2.通過Handler放置同步屏障

首先這裏會通過handler獲取當前的messageQueue,並調用了postSyncBarrier方法 ``` public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); }

private int postSyncBarrier(long when) { // Enqueue a new sync barrier token. // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token;

    Message prev = null;
    Message p = mMessages;
    if (when != 0) {
        while (p != null && p.when <= when) {
            prev = p;
            p = p.next;
        }
    }
    if (prev != null) { // invariant: p == prev.next
        msg.next = p;
        prev.next = msg;
    } else {
        msg.next = p;
        mMessages = msg;
    }
    return token;
}

} ```

2.1 什麼是同步屏障

這裏的syncBarrier就是同步屏障,看着名字很唬人,其實他就是一個target為null的message 普通消息有target是因為它需要將消息分發給對應的target,而同步屏障不需要被分發。

MessageQueue內的,我們之前在學習Handler的時候,就應該記得通過handler發送message的時候會對target進行檢測,如果發現當前msg的target為null,會拋異常

正常通過sendMessage的時候調用流程如下:

sendMessage -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage ``` //將當前msg入隊 會檢測msg是否滿足條件 boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } synchronized (this) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); }

    if (mQuitting) {
        IllegalStateException e = new IllegalStateException(
                msg.target + " sending message to a Handler on a dead thread");
        Log.w(TAG, e.getMessage(), e);
        msg.recycle();
        return false;
    }

... } ``` 正常通過handler發送流程如上,而放置同步屏障是直接在postSyncBarrier方法內將當前target為null的msg放置在隊列頭部。表明當前系統需要開始進行繪製流程。

2.2 同步屏障作用

同步屏障的作用是保證當Vsync信號到來的時候,當前進程能夠第一時間執行渲染流程, 而不是再去執行別的線程拋給handler的同步任務。在MessageQueue的next方法中可以體現 synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } 上述的if判斷中,就是如果當前隊列頭部msg的target為null,那麼就説明該msg就是放置的一個同步屏障,後面的do-while循環就會找到第一個異步的msg,也就是包含繪製信息的msg。

2.3 向編舞者拋一個用於繪製的callback

在上述代碼1.2的地方又向編舞者mChoreographer發送了一個callback,傳遞的是當前用來繪製的線程mTraversalRunnable,他會在下一幀到來時候進行回調, 即在下一個 VSync 信號到來時會執行TraversalRunnable-->doTraversal()-->performTraversals()-->繪製流程

Choreographer,即編舞者,控制整個系統內的渲染繪製的操作,協調動畫、輸入以及繪製的時機,每個主線程(Looper)都會有自己的編舞者,他是線程單例的(基於ThreadLocal實現)...

``` Choreographer.java

public void postCallback(int callbackType, Runnable action, Object token) { postCallbackDelayed(callbackType, action, token, 0); }

...

private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { ...

synchronized (mLock) {
    final long now = SystemClock.uptimeMillis();
    final long dueTime = now + delayMillis;
    //根據回調方法的類型,將callback放置在不同的隊列內
    mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

    if (dueTime <= now) {
        scheduleFrameLocked(now);  //正常繪製是進入這裏,開始請求下一次Vsync信號
    } else {
    //如果是想在後面某個時間繪製,會進入這裏
        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
        msg.arg1 = callbackType;
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, dueTime);
    }
}

}

//scheduleFrameLocked 會繼續調用scheduleVsyncLocked();方法 //mDisplayEventReceiver是FrameDisplayEventReceiver類型 //它繼承了FrameDisplayEventReceiver private void scheduleVsyncLocked() { mIsVsyncScheduled = true; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked"); mDisplayEventReceiver.scheduleVsync(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } //DisplayEventReceiver.java public void scheduleVsync() { if (mReceiverPtr == 0) { Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed."); } else { //這裏會JNI調用到native層,這裏具體後續再寫一篇文章總結下 nativeScheduleVsync(mReceiverPtr); } } ``` 請求Vsync信號的過程這裏不再闡述了,反正就是會在native層請求Vsync信號(requestNextVsync)

2.4 收到Vsync信號後的調用

當底層進行分發信號的時候,native層會去調用到DisplayEventReceiver的dispatchVsync方法 ```

private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame, long frameTimelineVsyncId, long frameDeadline, long frameInterval) { onVsync(timestampNanos, physicalDisplayId, frame, new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval)); } //然後進入FrameDisplayEventReceiver重寫的onVsync方法 //FrameDisplayEventReceiver.java 不僅繼承了DisplayEventReceiver, //同時它本身還是個runnable

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { ....

    public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
    VsyncEventData vsyncEventData) {
try {

      ...

    if (mHavePendingVsync) {
        Log.w(TAG, "Already have a pending vsync event.  There should only be "
                + "one at a time.");
    } else {
        mHavePendingVsync = true;
    }

    mTimestampNanos = timestampNanos;
    mFrame = frame;
    ScrollOptimizer.setVsyncTime(mTimestampNanos);
    mLastVsyncEventData = vsyncEventData;
    //將自身封裝成msg
    Message msg = Message.obtain(mHandler, this);
    //將消息設置為異步消息
    msg.setAsynchronous(true);
    //通過handler發送 代碼2.1
    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
} finally {
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

}

public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame, mLastVsyncEventData); }

} ``` 可以看到代碼2.1處FrameDisplayEventReceiver向handler發送了一個異步消息,由於之前設置了同步屏障,在取消息的時候會默認找到第一個異步消息也就是當前msg,執行其run方法

我們來看下doFrame方法 ```

void doFrame(long frameTimeNanos, int frame, DisplayEventReceiver.VsyncEventData vsyncEventData) { final long startNanos; final long frameIntervalNanos = vsyncEventData.frameInterval; try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + vsyncEventData.id); } synchronized (mLock) { mIsVsyncScheduled = false; if (!mFrameScheduled) { traceMessage("Frame not scheduled"); return; // no work to do }

        if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
            mDebugPrintNextFrameTimeDelta = false;
            Log.d(TAG, "Frame time delta: "
                    + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
        }

        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
        final long jitterNanos = startNanos - frameTimeNanos;
        //是否當前執行的時候已經超過一幀的時間, 即使存在同步屏障,如果當前有正在執行的任務
        //就有可能導致doFrame被延遲執行
        if (jitterNanos >= frameIntervalNanos) {
            final long skippedFrames = jitterNanos / frameIntervalNanos;
            //掉幀數如果超過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 % frameIntervalNanos;
            if (DEBUG_JANK) {
                Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                        + "which is more than the frame interval of "
                        + (frameIntervalNanos * 0.000001f) + " ms!  "
                        + "Skipping " + skippedFrames + " frames and setting frame "
                        + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
            }
            frameTimeNanos = startNanos - lastFrameOffset;
        }

        if (frameTimeNanos < mLastFrameTimeNanos) {
            if (DEBUG_JANK) {
                Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                        + "previously skipped frame.  Waiting for next vsync.");
            }
            traceMessage("Frame time goes backward");
            scheduleVsyncLocked(); //當前掉幀了,繼續請求vsync信號
            return;
        }

        if (mFPSDivisor > 1) {
            long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
            if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                traceMessage("Frame skipped due to FPSDivisor");
                scheduleVsyncLocked();
                return;
            }
        }

        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
        mFrameScheduled = false;
        mLastFrameTimeNanos = frameTimeNanos;
        mLastFrameIntervalNanos = frameIntervalNanos;
        mLastVsyncEventData = vsyncEventData;
    }

    AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);


    ScrollOptimizer.setUITaskStatus(true);
    mFrameInfo.markInputHandlingStart();
    //開始處理不同的callback方法
    //輸入事件的回調(最先執行)
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

    mFrameInfo.markAnimationsStart();
    //普通動畫的回調
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
    //Insets動畫的回調
    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
            frameIntervalNanos);

    mFrameInfo.markPerformTraversalsStart();
    //這個是用來繪製的callback也就是ViewRootImpl剛才post的callback
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
    //用來處理當前幀繪製完後的一些操作 運行在traversal之後
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
    ScrollOptimizer.setUITaskStatus(false);
} finally {
    AnimationUtils.unlockAnimationClock();
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);

    if (getMainThreadInstance() == Choreographer.this) {
        TurboSchedMonitor.getInstance().releaseAppToken();
    }
}
...

mIsDoFrameProcessing = false;

} 繼續看doCallback方法 void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) { CallbackRecord callbacks; synchronized (mLock) { // We use "now" to determine when callbacks become due because it's possible // for earlier processing phases in a frame to post callbacks that should run // in a following phase, such as an input event that causes an animation to start. final long now = System.nanoTime(); //從對應callback隊列內取出到達執行時間的callback ,封裝成CallbackRecord callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true;

   ...

try {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
    //遍歷所有的callback
    for (CallbackRecord c = callbacks; c != null; c = c.next) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "RunCallback: type=" + callbackType
                    + ", action=" + c.action + ", token=" + c.token
                    + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
        }
        //執行callback的run方法,也就是可以執行mTraversalRunnable的run方法來繪製了
        c.run(frameTimeNanos);

    }
} finally {
    synchronized (mLock) {
        mCallbacksRunning = false;
        do {
            final CallbackRecord next = callbacks.next;
            recycleCallbackLocked(callbacks);
            callbacks = next;
        } while (callbacks != null);
    }
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

} ``` 到這裏,一個完整的繪製線程的調用就結束了,然後就開始可以執行真正的繪製操作了。

這裏就開始執行ViewRootImpl內的mTraversalRunnable線程,在該線程內的doTraversal方法內,會首先移除掉先前放置的同步屏障,保證後續handler的同步任務能夠正常執行。 ```

final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); }

void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

    if (mProfile) {
        Debug.startMethodTracing("ViewAncestor");
    }
  //真正繪製流程
    performTraversals();

    ...
}

} ```

3 同步屏障可能導致的問題

同步屏障沒用好會導致很嚴重的問題,如果某個子線程主動通過Choreographer來進行更新UI,但是由於某種原因導致放置的同步屏障未能remove掉,就會導致後續的所有同步任務都無法執行。

之前就接觸過一例問題,就是由於系統主線程的同步屏障未能正常被remove,從而導致了所有的系統功能(依賴於handler的同步任務)都無法正常調用,排查了好久才定位到是由於同步屏障的原因。

因此,還是需要加深對同步屏障的理解。

下面用一張圖來總結一下 Android繪製流程是如何藉助同步屏障以及Choreographer 來完成繪製流程:

總結

  1. 同步屏障SyncBarrier的作用是保證當Vsync信號到來時,能夠第一時間執行Choreographer向handler發送的異步消息,但是這不能保證當前的繪製任務一定能夠在一幀內結束(可能是前一個同步任務事件較長或者當前的繪製任務耗時較長...)
  2. 當View請求刷新頁面時,不會馬上開始,他需要先請求Vsync信號,等待Vsync信號到來時才會開始進行計算屏幕數據、layout、measure以及draw等流程;這些流程結束後,新頁面也不會立馬顯示到屏幕上。需要等下一個Vsync信號到來時通過buffer緩存交換才能顯示到屏幕上
  3. 如果當前屏幕沒有任何改變的話,底層也會進行每16.6ms進行一次頁面的刷新過程(通過Vsync信號來實現,但是app不會接收Vsync事件);也就是説,界面不變時屏幕也會保持每16.6ms的刷新頻率,但是CPU/GPU不會執行繪製流程
  4. App側可以通過postFrameCallback方法來監聽界面的幀率,判斷當前的丟幀情況

Ps: 本文參考了:https://juejin.cn/post/6894206842277199880#heading-11