Why is Android not stuck? Choreographer

Why is Android not stuck? Choreographer

Long press the picture to save and share it to Moments

Micro-channel public number: [ Gu Linhai ] questions or suggestions, please focus on public message number; [If you think you have help, please a look at ] Author: Drummor link: https://juejin.im/post/6844903818044375053


In response to the problem of the Android UI not being smooth, Google proposed Project Butter to reconstruct the Android display system. 3.key points of this refactoring

  • VSynch vertical synchronization

  • Triple Buffer

  • Choreographer Choreographer

In this article, we mainly talk about Choregrapher, and we will write about others in the follow-up.


The display of the interface will generally be calculated by the CPU -> GPU synthesis rasterization -> display device display. We know that the refresh rate of Android devices is generally 60HZ, which is 60 times a second. If a drawing is completed within about 16 millimeters, there is no problem. But once there is an inconsistency, there will be problems as shown below

  • There is no problem with cpu calculation, GPU operation, and display device display in the first cycle

  • In the second cycle, the image prepared in the previous cycle is displayed without any problem. CPU calculation starts when the cycle is about to end

  • In the third cycle, the CPU s manual calculation was delayed when entering the second cycle, which led to subsequent follow-ups. When entering the third cycle, the image that should have been displayed was not ready, causing the previous one to be displayed in the entire third cycle. Periodic images, so it looks stuck and dropped frames! Google engineers called the entire Jank delayed military aircraft.

This matter is not small, how can it be solved? To put it simply, vertical synchronization is to let the CPU calculate without planning and irregularity, but start the calculation at the beginning of each cycle, and then proceed in an orderly and orderly manner (as shown in the figure below). The Choreographer class is added to android4.1 and later versions. Let's take a look at how it is implemented.

1. Entrance

1.1 postCallbackDelayedInternal

There is such a line of code in ViewRoot's doTravle() method

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

It means to initiate a measurement layout drawing operation for the entire View tree. More content about ViewRootImpl will not be introduced here.

The following method

  • public void postCallback( );

  • public void postCallbackDelayed( );

  • public void postFrameCallback(FrameCallback callback);

  • public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis)

Eventually postCallbackDelayedInternal(); will be called, so let's take a look at the function of this method.

   private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {synchronized (mLock) {final long now = SystemClock.uptimeMillis();//Get the current time final long dueTime = now + delayMillis;//Expiration time//Place the execution action in the mCallback array mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);//If it has expired, register to request the vertical synchronization signal if (dueTime <= now) {scheduleFrameLocked(now);} else {//If it has not expired, use the handler to send a delayed message. This delayed message will be executed when it expires. Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime);}}}
Copy code

1.2 FrameHandler

In the previous section, if it has expired, execute the scheduleFrameLocked() method directly. If it is not executed, use mHandler (FrameHandler type) to send a Message with the what value MSG_DO_SCHEDULE_CALLBACK. How to execute it after expiration. It depends on how FrameHandler handles it.

 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://doScheduleCallback(msg.arg1); break;}}}
Copy code

We can see this in the above code. When FramHandler gets the whate attribute value is MSG_DO_SCHEDULE_CALLBACK, it will execute doScheduleCallback(msg.arg1); method, follow up and see

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 code

In this method, some judgments are made first, mFrameSceduled is false and the return value of the hasDueCallbacksLocked() method is true. You can guess whether the callback has expired by looking at the method name. Let s analyze this again.

In the end, if the conditions are met, it will call the scheduleFrameLocked() method. Is this method familiar? Yes, yes, the method that will be executed directly if it expires in the postCallbackDelayedInternal() method. It's time to see what's going on in 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 to be rendered. 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./** Translate, if in the main In the thread, directly call to arrange vertical synchronization immediately, otherwise it means that the non-main thread will send a message and arrange a vertical synchronization as soon as possible on the main thread*/if (isRunningOnLooperThreadLocked()) {scheduleVsyncLocked();} else {Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg);
Copy code

  • The purpose of this method is very clear is to arrange, arrange vertical synchronization and immediately as soon as possible. The condition for arranging vertical synchronization is that USE_VSYNC is true, that is, the device supports vertical synchronization

  • If it is not vertical synchronization, send a message with a delay of one cycle through the handler to arrange vertical synchronization. The what value of this Message is MSG_DO_FRAME. Refer to the code block of 1.2 to execute the doFrame() method for the message whose what is MSG_DO_FRAME.

  • A detail, when this value mFrameScheduled is true, it will directly return without requesting the next frame rendering. If it is false, execute the scheduleFrameLocked() method to continue execution and set it to true; when is it set to false? ? See appendix 2 for details

The specific implementation of arranging vertical synchronization is the FrameDisplayEventReceiver class, which is DisplayEventReceiver for receiving vertical signals

    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 frame) {mTimestampNanos = timestampNanos; mFrame = frame; Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true);//Message is set to asynchronous mHandler.sendMessageAtTime(msg, timestampNanos/TimeUtils.NANOS_PER_MS) ;} @Override public void run() {mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame);}}
Copy code

The onVsync method is called back after receiving the vertical synchronization signal. This method uses the handler to send a message with callback (Runnable type, which has been inherited by itself), and finally doFrame() is called in run(); (The detailed information logic of this handler is Refer to the following article appendix one handler to distribute the message

This message is set to be asynchronous (msg.setAsynchronous(true);), which means that he has the priority to execute it. How is it executed first? Refer to Appendix 3: Asynchronous mode of message

In summary, add a callback process

2. Execution

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 vertical synchronization time final long jitterNanos = startNanos-frameTimeNanos;//If the difference between the vertical synchronization time and the current time is greater than one period, correct it if (jitterNanos >= mFrameIntervalNanos) {//take the interpolation and the remainder of the period final long lastFrameOffset = jitterNanos% mFrameIntervalNanos;//The remainder obtained by subtracting the current time from the previous step is used as the latest always-signal time frameTimeNanos = startNanos-lastFrameOffset;}//The vertical synchronization time is smaller than the previous time, and the next vertical will be arranged, and return directly if (frameTimeNanos <mLastFrameTimeNanos) {scheduleVsyncLocked(); return;} mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos;} try {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); AnimationUtils.lockAnimationClock(Utils.lockAnimationClock) (); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer. ;} 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.000001f + "ms, latency" + (startNanos-frameTimeNanos) * 0.000001f + "ms.");}}
Copy code

The first step is to correct the judgment

  • Current time startNanos = System.nanoTime();

  • Find the difference between the current time and the vertical synchronization time: jitterNanos = startNanos-frameTimeNanos;

  • If the difference between the vertical synchronization time and the current time is greater than one cycle (jitterNanos >= mFrameIntervalNanos), correct it

    • Take the remainder of the interpolation and the period: lastFrameOffset = jitterNanos% mFrameIntervalNanos;

    • The remainder obtained by subtracting the current time from the previous step is regarded as the latest always signal time: frameTimeNanos = startNanos-lastFrameOffset;

  • If the vertical synchronization time last time is still short, arrange the next rendering: frameTimeNanos <mLastFrameTimeNanos, return directly

Step 2: Execute callback

The execution order of callback is:

  • CALLBACK_INPUT input time has the highest priority

  • CALLBACK_ANIMATION is next to animation

  • CALLBACK_TRAVERSAL UI drawing layout again

  • CALLBACK_COMMIT animation correction related last.

2.2 doCallbacks();
  • In CallbackQueue[] mCallbackQueues is taking a singly linked list of a specific type (input, animation, layout, Commit)

  • Then take out the expired Callback/Runable to execute

Take out the Actions that need to be executed

Action is packaged in CallbackRecord, which is a one-way list, arranged in order of time. Take out the Actions to be executed through the extractDueCallbacksLocked() method of CallBackQueue. CallBackQueue can be regarded as the management class of CallBack, which also includes adding Action addCallbackLocked(), removing Action removeCallbacksLocked(), and whether there is an Anction hasDueCallbacksLocked(). method.

 private final class CallbackQueue {//Linked list header private CallbackRecord mHead;//Is there an Action that has expired public boolean hasDueCallbacksLocked(long now) {return mHead != null && mHead.dueTime <= now;}//The acquisition has expired Action public CallbackRecord extractDueCallbacksLocked(long now) {... return callbacks;}//Add Action public void addCallbackLocked(long dueTime, Object action, Object token) {...}//Remove Action public void removeCallbacksLocked(Object action , Object token) {...}}
Copy code

Execute Action

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

Traverse the CallBcakRecord from the callback and execute them one by one.

3. summary

  • Choreographer provides postCallback and other methods to the outside world. In the end, they internally implement this method by calling postCallbackDelayedInternal(). They mainly do two things.

    • Store Action

    • Request vertical synchronization, vertical synchronization

  • The vertical synchronization callback immediately executes the Action (CallBack/Runnable).

  • Action The type of action content may be

    • CALLBACK_INPUT input time has the highest priority

    • CALLBACK_ANIMATION is next to animation

    • CALLBACK_TRAVERSAL UI drawing layout again

    • CALLBACK_COMMIT animation correction related last.

  • Reviewing the Hanlder mechanism, I think it is the end point of the big engine running the Android system, the handler's distribution and execution of messages, and the "asynchronous mode".


Attachment 2: About the handler to execute Message

The following is the handler distribution logic. After the MessageQueue gets the message to be executed, Looper will hand it over to the target (Handler type) attribute of the message to process msg.target.dispatchMessage(msg);;

    public void dispatchMessage(Message msg) {//When msg's callback is not empty, execute msg's callback directly. It is a Runnable object if (msg.callback != null) {handleCallback(msg);} else {//Then Then hand it over to mCallBack, which is an attribute of the handler.//When creating the Handler, you can choose to pass in a CallBack object.//When the handleMessage in the callBack returns true, it means: True if no further handling is desired (no further processing is required) if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}//Execute the handleMessage() method of the Handler itself when the mCallback processing returns false. handleMessage(msg);}}
Copy code

The key logic is already annotated, summarize the logic of the handler's execution and distribution of Message

1. If the callback (runnable) attribute of the message is not empty, call the run() method of this runable to execute

 private static void handleCallback(Message message) {message.callback.run();}
Copy code

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

2. When the above conditions are not met, if the mCallback of the handler itself is not empty, it will send the message to mCallback for processing, and the handlerMessage() will end. This attribute is passed in when the handler is created. mCallback is a CallBack type, which is an internal interface of the handler.

    public interface Callback {boolean handleMessage(Message msg);}
Copy code

3. When the callBak of messaga is empty and the mCallBack of the handler is empty, it will be executed by its own handlerMessage() method. When we customize the handler, we can override this method to perform corresponding operations on the message.

Appendix II. The role of mFrameScheduled attribute
  • When callcack is executed, it will be judged that if the mFrameScheduled property is false, it means that the next frame is not scheduled to be rendered, and it will return directly without executing.

  void doFrame(long frameTimeNanos, int frame) {final long startNanos; synchronized (mLock) {if (!mFrameScheduled) {return;//no work to do} ... ... mFrameScheduled = false; ...}
Copy code

  • In the scheduleFrameLocked() method, setting the mFrameScheduled value to true means that the next frame is scheduled to be rendered. If mFrameScheduled is true at this time, it means that the next frame has been scheduled, then return, no confusion!

Attached three, the asynchronous mode of the Handler mechanism


The function of "asynchronous mode" is priority. Asynchronous messages have priority to execute in asynchronous mode.


MessageQueue uses the postSyncBarrier() method to add barriers, and the removeSyncBarrier() method removes barriers. These two methods are used in pairs.

Realization principle

  • The postSyncBarrier method of messageQueued adds a message whose target attribute is null to the head of the messagequeue

  • When next() of messageQueue encounters a message whose target is null, it will only take out the "asynchronous message" from the message list, ignoring the ordinary message, and hand it over to Looper for further distribution processing.

 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 code

Recommended reading:

The principle of Binder that intermediate Android development should know

In-depth understanding of ThreadLocal (source code + memory )

Android componentization scheme

wx number: gulinh ai531

Gu Linhai Public Account

Launch high-quality articles from time to time

Chapter, my favorite friends

Give me a good look.