Before joining Choreographer we should know a few things about Views:

The reason why the interface changes is that the view data changes. The reasons for the change of view data are as follows:

  1. A new Window is created. Creating a new Window generally means starting a new Activity and loading an XML layout into the decorView. The source code interpretation
  2. A new View is added. Add a new View to the ViewGroup by calling the ViewGroup#addView method
  3. Updated the existing View.

All of the above causes a requestLayout call to ViewRootImpl.

Choreographer source parsing

The following source code is from the Android API: 29

Start with requestLayout in ViewRootImpl:

@Override public void requestLayout() { if (! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; // Is it measure and layout scheduleTraversals(); } } @UnsupportedAppUsage void scheduleTraversals() { if (! MTraversalScheduled) {// mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // Note 1 //Choreographer callback, Perform drawing operations mChoreographer. PostCallback (Choreographer. CALLBACK_TRAVERSAL mTraversalRunnable, null); }}Copy the code

PostSyncBarrier: A postSyncBarrier that blocks all but asynchronous messages from the handler, meaning that other UI messages cannot be processed.

MChoreographer sends a callback function. MChoreographer is initialized within the ViewRootImpl constructor

public ViewRootImpl(Context context, Display display) { mChoreographer = Choreographer.getInstance(); 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 SF choreographer. private static final ThreadLocal<Choreographer> sSfThreadInstance = new ThreadLocal<Choreographer>() { @Override protected Choreographer initialValue() { Looper looper = Looper.myLooper();  if (looper == null) { throw new IllegalStateException("The current thread must have a looper!" ); } return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER); }};Copy the code

Each Looper thread has an independent Choreographer, and each Choreographer handles events for its thread.

    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver =  
                ? 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));
    }

Copy the code

Three things are initialized:

  • MHandler: Used to process delayed messages
  • MDisplayEventReceiver: Handles Vsync signal pulses (request and receive)
  • Queues (mCallbackQueues) – An array of mCallbackQueues size 5, with the first of the linked queues, (array + linked list)
    1. CALLBACK_INPUT Input events
    2. CALLBACK_INSETS_ANIMATION Illustration animation
    3. CALLBACK_ANIMATION animation
    4. CALLBACK_TRAVERSAL view rendering
    5. CALLBACK_COMMIT submitted

Choreographer’s postCallback flow chart is as follows:

Both the callback callbacks to postCallbackDelayedInternal:

private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { ... synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) { scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code

This method does two things :1. Joins events in a linked list headed with the corresponding type in CallbackQueues. 2. Time it

private void scheduleFrameLocked(long now) { if (! mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { ... if (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { .... mHandler.sendMessageAtTime(msg, nextFrameTime); }}}Copy the code

It is up to the system to decide whether to use USE_VSYNC. Only the case of Vsync is analyzed here: Request VSYNC signal pulse, this step will call back to native layer, the native will UI metadata SurfaceFlinger service for data processing, the final will be callback to FrameDisplayEventReceiver onVsync method, Then call back to the doFrame method.

@UnsupportedAppUsage void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (! mFrameScheduled) { return; // no work to do } ... long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); final long jitterNanos = startNanos - frameTimeNanos; If (jitterNanos >= mFrameIntervalNanos) {if (jitterNanos < mLastFrameTimeNanos) { // Less than one screen refresh time}... } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); }... }Copy the code

DoFrame does two things:

  1. Determine the time difference between VSYNC signal pulse return and transmission:
    • If large and screen refresh rate frame alignment;
    • If the screen refresh time is shorter than one time, apply the VSYNC pulse again.
  2. Callback the various callbacks recorded at the beginning.

Performing the doCallback event is essentially performing the mTraversalRunnable.

conclusion

  1. ViewRootImpl#requestLayout sends a callback event to Choreographer, which processes the callback event by requesting the VSYNC signal pulse.
  2. The system sends a VSYNC signal pulse every 16ms, between two calls to ViewRootImpl#requestLayout

View drawing process overview