digression

Time goes by so fast

I am sad that I have not been to the scene, but I always feel busy now. There are still many days ahead. How can I, a short-sighted animal, take the small probability of accidents into consideration

Kill unknown reason If it’s the era of rampant XD the since the media It’s a good time It the appearance of the world naked sit in front of you to allow you doubt thinking progress Is a bad time credibility became a false dichotomy Tore human that last point FIG leaf Believe that human nature is just beautiful’s courage If because ZF well I love the Chinese

preface

To solve the problem of Android UI not flowing smoothly, Google proposed Project Butter to reconstruct the Display system of Android. Three key points of this refactoring

  • VSynch vertical synchronization
  • Triple Buffer Triple cache
  • Choreographer choreographers

In this article we’ll focus on Choregrapher, and in the rest we’ll write about other things.

choreographer

The display of the interface will go through CPU calculation -> GPU synthesis rasterization -> display device display. We know that Android devices typically refresh at 60 Hz, which is 60 times a second, and if a drawing is done in about 16 millimews, that’s fine. But where there is incongruity there is a problem as shown below

  • In the first cycle, CPU calculation, GPU operation, and display device display have no problem.
  • The second cycle displays the image prepared by the previous cycle; CPU calculations start at the end of the cycle
  • In the third cycle, the CPU started to calculate late when entering the second cycle, so that the image should be displayed when entering the third cycle is not ready, resulting in the whole third cycle is still displayed in the image of the previous cycle, so it looks like it will be stuck, frame drop! Google engineers call the whole thing Jank Delayed Military aircraft.

It’s a big deal. How do we fix it? Vsync simply means that CPU computations start at the beginning of each cycle rather than being unplanned and random (see figure below). This has added the Choreographer class to Android4.1 and later, so let’s take a peek at how it works. (Choreographer this class position android. View. Choreographer)

An entry,

1.1 postCallbackDelayedInternal

ViewRoot has this line in the doTravle() method

mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
Copy the code

It means to initiate a measurement layout drawing operation on the entire View tree. There’s not much more to say about view Rule PL here.

The following methods

  • public void postCallback(…) ;
  • public void postCallbackDelayed(…) ;
  • public void postFrameCallback(FrameCallback callback);
  • public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis)

Eventually will be called postCallbackDelayedInternal (); So let’s look at what this method does.

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 {// If not, use handler to send a delayed message. The delayed message is executed when it expires. Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code

1.2 FrameHandler

In the previous section, execute the scheduleFrameLocked () method directly if it has expired, or send a Message with what value MSG_DO_SCHEDULE_CALLBACK using mHandler (FrameHandler type) if it has not. So what happens when it expires. It depends on how the FrameHandler is handled.

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(); break; Case MSG_DO_SCHEDULE_CALLBACK: / / postCallbackDelayedInternal () method when the outstanding send come over doScheduleCallback (MSG. Arg1); break; }}}Copy the code

If the whate attribute is MSG_DO_SCHEDULE_CALLBACK, the FramHandler will execute doScheduleCallback(msg.arg1); Method, follow in to have a look

1.3 Choreography# doScheduleCallback

void doScheduleCallback(int callbackType) { synchronized (mLock) { if (! mFrameScheduled) { final long now = SystemClock.uptimeMillis(); if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) { scheduleFrameLocked(now); }}}}Copy the code

The mFrameSceduled uled false and hasDueCallbacksLocked () returns true to determine whether the callback is due. Finally, scheduleFrameLocked() is called if the condition is met. Yes, that’s right, postCallbackDelayedInternal () method if expired direct execution of the method. It’s time to see what’s going on with this method.

1.4 scheduleFrameLocked ()

private void scheduleFrameLocked(long now) { if (! mFrameScheduled) { mFrameScheduled = true; // Set the flag bit to indicate that the next frame is scheduled for rendering. 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. Vertical synchronization direct call for immediate attention, also is the main thread of the will send a message in the main thread as soon as possible to arrange a vertical synchronous * / if (isRunningOnLooperThreadLocked ()) {scheduleVsyncLocked (); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { 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."); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); }}}Copy the code
  • The purpose of this method is very clear: to schedule, to schedule vSYNC immediately and as soon as possible. Vsync is scheduled only if USE_VSYNC is true, which means the device supports vsync
  • If it is not vSYNC, the handler sends a Message with a delay of one period to arrange vsync. The Message whose what value is MSG_DO_FRAME will execute the doFrame () method on the Message whose what value is MSG_DO_FRAME according to the code block 1.2.
  • While mFrameScheduled uled to true, scheduleFrameLocked () would be executed and set to true. When was it set to false? See Appendix II for details

Arrange the realization of a vertical synchronization is FrameDisplayEventReceiver class he is used to receive DisplayEventReceiver vertical signal

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; public FrameDisplayEventReceiver(Looper looper, int vsyncSource) { super(looper, vsyncSource); } @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { mTimestampNanos = timestampNanos; mFrame = frame; Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); / / the Message set to asynchronous mHandler. SendMessageAtTime (MSG, timestampNanos/TimeUtils NANOS_PER_MS); } @Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); }}Copy the code

On receiving the vSYNC signal, the onVsync method is called. This method uses the handler to send a message with a callback (of type Runnable, which it inherits), and finally to call doFrame () in run (). For more information about the operation logic of this handler, see Appendix I handler distributing messages below

This message is set to asynchronous (msg.setasynchronous (true)). That means he has priority. How does he get priority? Refer to The asynchronous mode of Message in Appendix 3

To sum up, add the callback process

Second, to perform

doFrame

void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (! mFrameScheduled) { return; // no work to do} // current time startNanos = system.nanotime (); // Current time and vSYNC time final Long jitterNanos = startNanos-frameTimenanos; If (jitterNanos >= mFrameIntervalNanos) {final Long lastFrameOffset = final Long lastFrameOffset = jitterNanos % mFrameIntervalNanos; FrameTimeNanos = startNanos-lastFrameoffset; If (frameTimeNanos < mLastFrameTimeNanos) {scheduleVsyncLocked(); return; } mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; } 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); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (DEBUG_FRAMES) { final long endNanos = System.nanoTime(); Log.d(TAG, "Frame " + frame + ": Finished, took "+ (Endnanos-startnanos) * 0.00000f +" ms, Latency "+ (startNanos-frameTimenanos) * 0.00000f +" Ms. "); }}Copy the code
  1. The first step is to revise your judgment
  • StartNanos = system.nanotime ();

  • Calculate the difference between current time and vSYNC time: jitterNanos = startNanos-frameTimenanos;

  • The difference between vSYNC time and current time is corrected if it is greater than one cycle (jitterNanos >= mFrameIntervalNanos)

    • Take the remainder of the interpolation and always period: lastFrameOffset = jitterNanos % mFrameIntervalNanos;
    • The current time minus the remainder of the previous step as the latest always signal time: frameTimeNanos = startNanos-lastFrameoffset;
  • The last time the vSYNC time was too short, schedule the next render: frameTimeNanos < mLastFrameTimeNanos, return directly

  1. The second step executes the callback

Callback is executed in the following order:

  • CALLBACK_INPUT The input time has the highest priority
  • CALLBACK_ANIMATION takes second place
  • CALLBACK_TRAVERSAL UI rendering layout again
  • CALLBACK_COMMIT animation correction related last.

2.2 doCallbacks ();

  • Queues in CallbackQueue[] mCallbackQueues are unidirectional queues that take specific types (input, animation, layout, Commit)
  • The expired Callback/Runable is then fetched and executed

Retrieve the Actions that need to be executed

Actions are wrapped in a CallbackRecord and are a one-way list, arranged in chronological order. Out to perform the Actions through CallBackQueue extractDueCallbacksLocked () method, which can put the CallBackQueue as CallBack management class, This also includes adding Action addCallbackLocked (), removing Action removeCallbacksLocked (), and whether there are Anction hasDueCallbacksLocked () methods.

Private final class CallbackQueue {// private CallbackRecord mHead; Action public Boolean hasDueCallbacksLocked(long now) {return mHead! = null && mHead.dueTime <= now; } / / to get expired Action public CallbackRecord extractDueCallbacksLocked (long) {... return callbacks; } // Add Action public void addCallbackLocked(long dueTime, Object Action, Object token) {... } // Remove Action public void removeCallbacksLocked(Object Action, Object token) {... }}Copy the code

To perform the Action

for (CallbackRecord c = callbacks; c ! = null; c = c.next) { c.run(frameTimeNanos); }Copy the code

Iterate through the CallBcakRecord from callback, executing it one by one.

Third, summary

  • Choreographer provides the methods of postCallback, ultimately they are internal by calling postCallbackDelayedInternal () will implement this method is mainly to do two things
    • Store the Action
    • Request vSYNC, vsync
  • The vSYNC CallBack executes the Action (CallBack/Runnable) immediately.
  • Action The type of an Action content can be
    • CALLBACK_INPUT The input time has the highest priority
    • CALLBACK_ANIMATION takes second place
    • CALLBACK_TRAVERSAL UI rendering layout again
    • CALLBACK_COMMIT animation correction related last.
  • Reviewing the Hanlder mechanism, WHICH I think is the end of the big engine that runs Android, focus on the handler’s execution of message distribution, and “asynchronous mode.”

The attached

FuEr,About handler executing Message

Below is the handler distributed logic, which in the MessageQueue to perform message then I will go to the message of the target (handler type) attribute processing MSG. Target. DispatchMessage (MSG);;

Public void dispatchMessage(MSG) {public void dispatchMessage(MSG) {if (MSG. Callback! = null) { handleCallback(msg); } else {// Then pass the handler to mCallBack, which is a property of handler. // When you create a handler, you can pass it to a CallBack object. // When handleMessage returns true, it means: True if no further handling is desired if (mCallback! = null) { if (mCallback.handleMessage(msg)) { return; } // The handleMessage(MSG) method is executed when the mCallback returns false; }}Copy the code

The key logic has been commented out, but the handler performs the Message distribution logic

  1. If message’s callback (runnable) attribute is not null, the runable’s run () method is called to execute
 private static void handleCallback(Message message) {
        message.callback.run();
    }

Copy the code

When we use handler.post(Runnable r) we set r to the message callback

  1. If this condition is not met, handlerMessage () is passed to mCallback if the handler’s own mCallback is not empty. This property is passed in when the handler is created. MCallback is of type CallBack, which is an internal interface to handler.
    public interface Callback {
         boolean handleMessage(Message msg);
    }
Copy the code

3. When the Messaga callBak is null and the Handler’s mCallBack is null, the handlerMessage () method executes. We can override this method to operate on message when we customize the handler.

FuEr,MFrameScheduled uled properties

  • Callcack will determine whether the mFrameScheduled uled attribute is false, indicating that the next frame is not scheduled to be rendered.
void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (! mFrameScheduled) { return; // no work to do } ... . mFrameScheduled = false; . }Copy the code
  • In the scheduleFrameLocked() method, setting the mFrameScheduled uled value to true indicates that a request to render the next frame is scheduled. If mFrameScheduled uled to true, the next frame has been scheduled.

With three,Asynchronous mode of the Handler mechanism

role

Asynchronous mode takes precedence, and an asynchronous message has precedence in asynchronous mode.

usage

MessageQueue uses postSyncBarrier () to add barriers and removeSyncBarrier () to remove barriers. The two methods are used in pairs.

Realize the principle of

  • The postSyncBarrier method of messageQueued adds a message with a null target attribute to the header of the Messagequeue
  • When next () of messageQueue hits a message whose target is null, it simply removes the “asynchronous message” from the message list, ignoring the normal message and handing it over to Looper for further distribution.
Message next() { ... for (;;) { if (nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); 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 messagin the queue. do { prevMsg = msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); }... return msg; }...Copy the code

Finish, the level is limited, you are not stingy criticism.