This paper mainly solves the following problems:

  • We all know that Android refreshes at 60 frames per second, does that mean that the onDraw method is called every 16ms?
  • If the interface does not need to be redrawn, will the screen refresh after 16ms?
  • Will the screen refresh happen immediately after we call invalidate()?
  • We say frame loss because the main thread has done a time-consuming operation. Why does the main thread lose frames when it has done a time-consuming operation?
  • Will I lose frames if I draw OnDraw () just before the screen refreshes?

Well, with the above questions, let’s go into the source code to find the answer.

I. Screen drawing process

The basic principles of the screen drawing mechanism can be summarized as follows:

The basic process of screen drawing is as follows:

  • The application applies for a buffer from the system service
  • The system service returns buffer
  • After drawing the application, the buffer is submitted to the system service

If put into Android, then it is:

In Android, a Surface corresponds to a piece of memory. After the memory application is successful, there will be a place for drawing on the App end. Since Android view drawing is not the focus of today, I will stop here

Second, screen refresh analysis

The screen refreshes when Vsync signal arrives, as shown in the following figure:

On Android, who controls the creation of Vsync? Who tells us to refresh our app? In Android, the generation of Vysnc signals is handled by low-level HWComposer, and Choreographer, the Java layer for notifying applications of refreshes, is at the heart of Android’s entire screen refresh.

Let’s look at it with some code.

RequestLayout () is called every time we do a UI redraw, so we’ll start with this method:

2.1 requestLayout ()

---- Class name: viewrootimpl@override public voidrequestLayout() {
        if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; / / key scheduleTraversals (); }}Copy the code

2.2 scheduleTraversals ()

---- Class name :ViewRootImpl voidscheduleTraversals() {
        if(! mTraversalScheduled) { mTraversalScheduled =true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); . }}Copy the code

As you can see, instead of redrawing immediately, two things have been done:

  • Inserts a message queueSyncBarrier(Synchronization barrier)
  • A callback was posted via Cherographer

Let’s take a quick look at the SyncBarrier.

Synchronization barriers work by:

  • Prevents the execution of synchronous messages
  • Asynchronous message execution is preferred

Why design a SyncBarrier? The main reason is that on Android, some messages are urgent and need to be executed immediately, and if there are too many ordinary messages in the message queue, it may be too late to execute them.

At this point, some of you might think, like me, why not just put a priority in Message and sort by priority? Why don’t we just make a PriorityQueue?

My own understanding is that in Android, the design of message queue is a single linked list, and the sorting of the whole linked list is sorted according to time. If a sorting rule of priority is added at this time, on the one hand, the sorting rule will be complicated, and on the other hand, the message will be uncontrollable. Because the priority can be filled out by the user, wouldn’t that be a mess? If the user always enters the highest priority, the system message will take a long time to consume, the whole system will fail, and the user experience will be affected. Therefore, I think Android’s synchronization barrier design is quite clever

Ok, to sum up, scheduleTraversals() inserts a barrier to ensure that asynchronous messages are executed first.

Insert a quick thought question: If we call requestLayout() multiple times in a row in a method, will the system insert multiple barriers or post multiple callbacks? The answer is no, why not? You see the variable mTraversalScheduled? It is the answer

2.3 Choreographer. PostCallback ()

A few words about Choreographer, Choreographer in Chinese is mainly about system coordination. (You can Google the actual work of choreography, this class name really aptly ~)

How does Choreographer class get initialized? The getInstance() method is used:

    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;
            }
            returnchoreographer; }};Copy the code

This is posted here to remind you that Choreographer is not a singleton, but a separate one for each thread.

Ok, back to our code:

---- class name :Choreographer //1 public void postCallback(int callbackType, Runnable Action, Object token) { postCallbackDelayed(callbackType, action, token, 0); } //2 public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { .... postCallbackDelayedInternal(callbackType, action, token, delayMillis); } //3 private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { ... mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else{... }}Copy the code

Choreographerpost’s callback will be placed in the CallbackQueue, which is a single linked list.

A CallbackQueue single linked list is obtained based on the callbackType, and the callback is inserted into the single linked list in chronological order.

2.4 scheduleFrameLocked ()

---- "class name :Choreographer private void scheduleFrameLocked(long Now) {... // 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); mHandler.sendMessageAtFrontOfQueue(msg); }}else{... }}}Copy the code

ScheduleFrameLocked is used to:

  • If the current thread isCherographerFor the worker thread, then execute directlyscheduleVysnLocked
  • Otherwise, an asynchronous message is sent to the message queue, which is not affected by the synchronization barrier, and the message is inserted to the head of the message queue, so the message is very urgent

Tracing the source code, we find that the MSG_DO_SCHEDULE_VSYNC message actually executes scheduleFrameLocked, so we directly trace scheduleVsyncLocked().

2.5 scheduleVsyncLocked ()

---- Class name :Choreographer private voidscheduleVsyncLocked() { mDisplayEventReceiver.scheduleVsync(); } ---- class name :DisplayEventReceiver public voidscheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else{/ / mReceiverPtr of a class is the Native pointer address / / this class here refers to the underlying NativeDisplayEventReceiver this class //nativeScheduleVsync calls requestNextVsync () to request the next Vsync. NativeScheduleVsync (mReceiverPtr) also involves a variety of descriptor listening and cross-process data transmission; }}Copy the code

Here we can see a new class: DisplayEventReceiver, which registers the listener for the Vsync signal and notifies the DisplayEventReceiver when the next Vsync signal arrives.

Where can I notify you? Source code comments written very clear:

---- "class name :DisplayEventReceiver // Called from native code. <-- private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); }Copy the code

When the next Vysnc signal arrives, the onVsync method is finally called:

    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
    }
Copy the code

Point in a look, and the implementation is empty, back to the class definition, the original is an abstract class, its implementation class is: FrameDisplayEventReceiver, defined in Cherographer:

- the name of the class: Choreographer private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { . }Copy the code

2.6 FrameDisplayEventReceiver. OnVysnc ()

- the name of the class: 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); msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {...doFrame(mTimestampNanos, mFrame); }}Copy the code

The onVsync method sends a message to the message queue of the thread on which Cherographer is working, which is itself (it implements Runnable), so the doFrame() method will eventually be called.

2.7 doFrame (mTimestampNanos, mFrame)

DoFrame () is processed in two stages:

   void doFrame(long frameTimeNanos, int frame) { final long startNanos; Synchronized (mLock) {//1, synchronized (mLock); startNanos = System.nanoTime(); final long jitterNanos = startNanos - frameTimeNanos;if (jitterNanos >= mFrameIntervalNanos) {
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames! "
                            + "The application may be doing too much work on its main thread."); }... }... }Copy the code

FrameTimeNanos is the current timestamp. Subtract the current time from the start time to get how long it took to process this frame. If it is larger than mFrameIntervalNano, it means that the processing took too long. The application may be doing too much work on its main thread.

Stage 2:

 void doFrame(long frameTimeNanos, int frame) { ... 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);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); }... }Copy the code

The second stage of doFrame() is to process the various callbacks from the CallbackQueue.

Here’s a reminder of the postCallback() operation:

The Callback is just a mTraversalRunnable, which is a Runnable that will call the run() method to refresh the interface:

---- Class name :ViewRootImpl Final class TraversalRunnable implements Runnable {@override public voidrun() {
            doTraversal();
        }
    }
    
    void doTraversal() {
        if(mTraversalScheduled) { ... performTraversals(); . } } private voidperformTraversals() {... // Start the actual interface drawing performDraw(); . }Copy the code

Third, summary

After a long code tracking, the whole interface refresh process is tracked, let’s summarize:

Iv. Problem solving

  • We all know that Android refreshes at 60 frames per second, does that mean that the onDraw method is called every 16ms?

Here 60 frames per second is the screen refresh rate, but whether the onDraw() method is called depends on whether the application calls requestLayout() to register a listener.

  • If the interface does not need to be redrawn, will the screen be refreshed after 16ms?

If you do not need to redraw, the application will not receive the Vsync signal, but will still be refreshed, but the drawn data will remain the same;

  • Will the screen refresh happen immediately after we call invalidate()?

No, wait until the next Vsync signal arrives

  • We say frame loss because the main thread has done a time-consuming operation. Why does the main thread lose frames when it has done a time-consuming operation

The reason is that if you do a time-consuming operation on the main thread, it will affect the drawing of the next frame, causing the interface to be unable to refresh at this Vsync time, resulting in frame loss.

  • Will I lose frames if I draw OnDraw () just before the screen refreshes?

It doesn’t really matter, because Vsync signals are periodic, and when we initiate onDraw () doesn’t affect the interface refresh;

5. Reference documents

  • Cherographer Principles of gityuan
  • Lesson for video