• preface
  • View workflow
  • Choreographer
    • FrameDisplayEventReceiver
    • VSYNC
    • Triple Buffer
  • conclusion
    • reference

preface

In my last View workflow blog post, I analyzed the process of applying window measure, Layout and draw in ViewRootImpl class. Today’s article explores the origins from view Windows TO screen refreshes.


View workflow

//ViewRootImpl @Override public void requestLayout() { if (! mHandlingLayoutInLayoutRequest) { checkThread(); // Check whether it is on the main thread mLayoutRequested = true; //mLayoutRequested whether to measure and layout the information panel. scheduleTraversals(); } } void scheduleTraversals() { if (! MTraversalScheduled) {// mTraversalScheduled is not called multiple times within the same frame; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); / / intercept synchronous Message / / explore entrance mChoreographer postCallback (Choreographer. CALLBACK_TRAVERSAL mTraversalRunnable, null); } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void doTraversal() { mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); // Remove performTraversals(); // Start the process of drawing the View. }Copy the code

Analyze the following two main points

  • 1) postSyncBarrier: Handler synchronization barrier In simple terms, the synchronization barrier is used to block Looper from retrieving and distributing synchronous messages because Looper is constantly retrieving messages from a MessageQueue. After the synchronization barrier is added, Looper will only fetch and process asynchronous messages, and will block if there are no asynchronous messages. The reason for this is that the View drawing and screen refresh must have the highest priority (i.e., VIP, to prevent stalling), and all other messages can be set aside except for the View drawing and rendering operations (set to asynchronous messages). Protecting the rights and interests of vulnerable groups.

  • 【 典 型 范 例 2 】 Choreographer is about to start dancing. (System engineers believe that View rendering is art at the fingertips, and every interaction is a visual feast, adding some artistic conception in the creation process)


Choreographer

Choreographer is the product of the introduction of Project Butter in Jelly Bean (Android 4.1), which includes:

  • Choreographer: Is responsible for unifying animation, input, and drawing timing.
  • VSYNC: indicates the vertical synchronization signal.
  • Triple Buffer: Draw a third Buffer to reduce the delay in displaying the content.

In ViewRootImpl, the scheduleTraversals() method calls Choreographer’s postCallback() method to pass in the runnable that will perform the traversal drawing.

You can also say: The ViewRootImpl method of traversing and drawing doTraversal(), led by Choreographer Choreographer, which calls back to the callback method when the time is right, View starts traversing measure – > Layout – > Draw.

//Choreographer //Posts a callback to run on the next frame. Public void postCallback(int callbackType, Runnable Action, Object token) { postCallbackDelayed(callbackType, action, token, 0); } public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { `````` postCallbackDelayedInternal(callbackType, action, token, delayMillis); } 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) {delayMillis = 0 scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; MSG. SetAsynchronous (true); // setAsynchronous delay message, execute after dueTime (ignore synchronization barrier) MSG. SetAsynchronous (true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code

In postCallbackDelayedInternal () method, we noticed that:

  • Queues add actions (the runnable that triggers the doTraversal() method) based on time. When the time is right, mCallbackQueues call back these actions.

  • SetAsynchronous: asynchronous message. Because a synchronization barrier has been added to the messaging mechanism of the master thread, the Handler handles only asynchronous messages.

Immediately following the scheduleFrameLocked() method called above:

//Choreographer private void scheduleFrameLocked(long now) { if (! mFrameScheduled) { mFrameScheduled = true; If (USE_VSYNC) {// If running on the Looper thread, then schedule the VSYNC immediately, // otherwise post a message to schedule the vsync from the UI thread // as soon as possible. if (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); } else {Message MSG = mhandler. obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); // The asynchronous message is placed at the top of the Handler queue, currently with the highest priority. mHandler.sendMessageAtFrontOfQueue(msg); } } `````` } } private void scheduleVsyncLocked() { mDisplayEventReceiver.scheduleVsync(); } / / can only be used in the UI thread private final FrameDisplayEventReceiver mDisplayEventReceiver;Copy the code
//DisplayEventReceiver /** * Schedules a single vertical sync pulse to be delivered when the next * display frame */ public void scheduleVsync() {// Register a VSYNC, Call dispatchVsync method nativeScheduleVsync(mReceiverPtr) when the next pulse arrives; } private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); }Copy the code

As you can see from the above code, Choreographer is about to execute VSYNC signals. If you are currently on the main thread, call scheduleVsyncLocked() immediately, if not the main thread, switch to the UI thread via Handler (mainLooper). And the Message is the asynchronous Message that is placed first in the Message queue and is the highest priority thing to process.

FrameDisplayEventReceiver

DisplayEventReceiver FrameDisplayEventReceiver inheritance, is mainly used to receive VSYNC synchronous pulse signal.

The scheduleVsync() method registers with the SurfaceFlinger service through the underlying nativeScheduleVsync(), that is, the dispatchVsync() method of DisplayEventReceiver is called after the next pulse is received.

This is similar to the subscriber mode, but there is only one dispatchVsync() method callback for each call to the nativeScheduleVsync() method.

So what does the sync pulse signal VSYNC do?


VSYNC

VSYNC stands for Vertical Synchronization.

Due to the coordination between the human eye and the brain, it is generally impossible to perceive updates beyond 60FPS. If the frame rate is higher than 12 frames, it is considered to be consistent. If it reaches 24 frames, it is a smooth experience. This is the playback speed of film film (24FPS).

For screen display, the game experience, if the overall smooth 60FPS, the graphics update 60 times per second, 16.67ms refresh, most people will feel very smooth and silky visual experience.

The same goes for Android, which normally refreshes at 60FPS

Display Display = getWindowManager().getDefaultdisplay (); float refreshRate = display.getRefreshRate();Copy the code
//Display public float getRefreshRate() { return mRefreshRate; } //Display public static final class Mode implements Parcelable {private final float mRefreshRate; public Mode(``````, float refreshRate) { mRefreshRate = refreshRate; }Copy the code

In the VirtualDisplayDevice class, Mode is assigned and refreshRate is the final constant value of int, i.e. 60HZ

//VirtualDisplayDevice

        private static final float REFRESH_RATE = 60.0f;Copy the code

The screen refresh rate of 60 frames per second is 1000/60 ≈ 16.67ms.




In the absence of VSYNC signal pulse: (Jank means the same frame appears on the screen more than 2 times)

  1. Time starts from 0, enter the first 16ms: Display displays frame 0, CPU, GPU process the first frame.
  2. Time enters the second 16ms: as early as in the previous 16ms time, the first frame has been processed by CPU and GPU. Display displays frame 1 normally. However, during the current 16ms, the CPU and GPU did not draw frame 2 in time (note the blank space in front), and the CPU/GPU did not process frame 2 until the end of the cycle.
  3. In the third 16ms, Display should Display the data of frame 2, but the CPU and GPU have not finished processing the data of frame 2, so Display can only continue to Display the data of frame 1. As a result, frame 1 is drawn more than once (the corresponding time period is marked with a Jank).




So starting with Android 4.1Jelly Bean, Project Buffer introduced VSYNC, and the system will immediately start rendering the next frame upon receiving the VSYNC Pulse. The results are shown below:

Therefore, the key point is that the CPU must start to process the next frame when each VSYNC signal arrives, and then hand it over to the GPU for processing, and finally display it by the screen. The consistency of the whole pipeline operation is the basis to ensure the smoothness of the display system. And most of the UI operations of the program are performed in 16.67ms.

If an operation on the main thread takes 24ms, the user will see the same frame for 32ms



So time-consuming operations in the main thread can affect the smoothness of the UI.

For more on Project Butter, see The Android Project Butter analysis, and our next article will look at the CPU GPU and screen rendering mechanism.


Returning to the body, we continue with the DisplayEventReceiver, which calls back to the dispatchVsync() method when the VSYNC signal arrives after it has subscribed to the next VSYNC signal, as mentioned above.

//DisplayEventReceiver /** * Schedules a single vertical sync pulse to be delivered when the next * display frame */ public void scheduleVsync() {// Register a VSYNC, Call dispatchVsync method nativeScheduleVsync(mReceiverPtr) when the next pulse arrives; } private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); }Copy the code
//Choreographer private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { `````` mTimestampNanos = timestampNanos; // mFrame = frame; Message msg = Message.obtain(mHandler, this); // This is the current run method msg.setasynchronous (true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } @override public void run() {doFrame(mFrame); }}Copy the code

Receives the VSYNC subscription after the event, the callback onVsync () method, because the handle UI events must be in the main thread, so FrameDisplayEventReceiver implement Runnable run () method, Asynchronous messages are sent via Handler, and UI events are executed by Run ().

//Choreographer void doFrame(long frameTimeNanos, int frame) { try { mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); //scheduleTraversals postCallback doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } } void doCallbacks(int callbackType, long frameTimeNanos) { `````` try { for (CallbackRecord c = callbacks; c ! = null; c = c.next) { c.run(frameTimeNanos); }}Copy the code

At this point we can return to the View wrootimpl class

//ViewRootImpl void scheduleTraversals() { if (! MTraversalScheduled) {// In the next frame, only one doTraversal operation will be performed! mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); MChoreographer. PostCallback (Choreographer CALLBACK_TRAVERSAL, / / callback CALLBACK_TRAVERSAL logo mTraversalRunnable doCallbacks, null); `````` } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); }} Final TraversalRunnable mTraversalRunnable = new TraversalRunnable();Copy the code

Coming full circle back to ViewRootImpl, which Outlines our current traversal, the preparation for the next frame is that when we are done traversing ViewRootImpl, we present the drawing result to the screen for display. If the drawing result of the View is not submitted, the screen will always display the current picture.

Triple Buffer

Before Android 4.1, the dual-buffer technology has been used, that is, two display buffers, the back buffer for the CPU/GPU drawing preparation of the next frame, and the other frame buffer for screen display. When the back buffer is ready, they swap.

However, if we take too long to prepare, the back buffer may not be ready because the main thread is blocked, the XML layout file has too many layers of redundancy, and the drawing operation is not correct (objects are frequently created in onDraw). Then the frame Buffer will always be displayed on the screen. Create caton vision.

  1. If it takes too long for CPU/GPC to prepare THE B Buffer content, the back Buffer cannot be delivered when the first VSYNC signal arrives, then the PRE Buffer displayed on the screen is still the previous one, and after the B Buffer content is ready, You also need to wait for the next VSYNC signal before delivery.
  2. When the second VSYNC signal arrives, both buffers are already occupied (one for display and one for B Buffer preparation), so a delay in the next drawing will also cause a chain lag. (Display the same frame twice or more)

The solution to the above problem is to introduce A third Buffer, which can be used to prepare C when rendering B times out and Buffer A is used for screen display, thus reducing A subsequent Jank occurrence.

In most cases, two buffers are used for display, and a third Buffer is used only when a frame takes more than two VSYNC cycles.


conclusion

Finally, a rough sequence diagram of the whole process of this paper is added. The next article will continue to explore the knowledge related to CPU/GPU rendering.



To view a larger version

reference

Android Project Butter analyzes the Android screen refresh display mechanism

Android Choreographer source Analysis /