Systrace Choreographer rendering mechanism

Lifelong learning with you, this is Android programmer

This article mainly introduces some knowledge points in Android development. By reading this article, you will gain the following contents:

1, The essence of the main thread running mechanism
2, Introduction to Choreographer
3, Source code analysis of Choreographer
4, APM and Choreographer
5, MessageQueue and Choreographer
6, Mobile phone manufacturer optimization

This article introduces a class Choreographer that App developers don't often contact but is very important in the Android Framework rendering link. It includes the introduction background of Choreographer, the introduction of Choreographer, some source code analysis, Choreographer and MessageQueue, Choreographer and APM, as well as some optimization ideas of mobile phone manufacturers based on Choreographer.

The introduction of Choreographer is mainly to cooperate with Vsync to provide a stable opportunity for Message processing for the rendering of upper App, that is, when Vsync comes, the system controls the timing of each frame drawing operation by adjusting the Vsync signal cycle At present, most mobile phones have a refresh rate of 60Hz, that is, refresh every 16.6ms. In order to match the refresh rate of the screen, the system also sets the cycle of Vsync to 16.6ms. Each 16.6ms, the Vsync signal wakes up Choreographer to Draw the App. This is the main function of introducing Choreographer Understanding Choreographer can also help App developers know the basic principle of each frame of the program, and deepen their understanding of Message, Handler, Looper, MessageQueue, Measure, Layout and Draw

1, The essence of main thread running mechanism

Before talking about Choreographer, let's take a look at the essence of Android main thread running, which is actually the processing process of Message. Our various operations, including the rendering operation of each frame, are sent to the main thread's MessageQueue in the form of Message. After processing the Message, the MessageQueue continues to wait for the next Message, as shown in the figure below

MethodTrace diagram

Systrace diagram

1. Evolution

The Android version before the introduction of Vsync renders a frame of related messages with no interval in the middle. After the previous frame is drawn, the Message of the next frame begins to be processed immediately. The problem is that the frame rate is unstable, which may be high or low, as shown in the following figure

MethodTrace diagram

Systrace diagram

For users, a stable frame rate is a good experience. For example, when you play king glory, users feel better when it is stable at 50 fps than when fps changes frequently between 60 and 40.

Therefore, in the evolution of Android, the mechanism of "Vsync + TripleBuffer + Choreographer" is introduced. Its main purpose is to provide a stable frame rate output mechanism so that the software layer and hardware layer can work together at a common frequency.

##2. Introduce Choreographer

The introduction of Choreographer is mainly to cooperate with Vsync to provide a stable opportunity for Message processing for the rendering of upper App, that is, when Vsync comes, the system controls the timing of each frame drawing operation by adjusting the Vsync signal cycle As for why the Vsync cycle is 16.6ms (60 fps), the reason is that most mobile phone screens are refreshed at a refresh rate of 60Hz, that is, once every 16.6ms. In order to match the refresh frequency of the screen, the system also sets the Vsync cycle to 16.6ms. Every 16.6ms, the Vsync signal wakes up Choreographer to draw the App, If the application can be rendered in every Vsync cycle, the fps of the application is 60, which makes the user feel very smooth. This is the main function of introducing Choreographer.

Of course, at present, more and more mobile phones use 90Hz refresh rate screens. The Vsync cycle has changed from 16.6ms to 11.1ms. The operations in the above figure should be completed in a shorter time, and the requirements for performance are becoming higher and higher. See the details New smooth experience, 90Hz rambling This article

2, Introduction to Choreographer

Choreographer plays a connecting role in the Android rendering link

  1. Bearing:
    Be responsible for receiving and processing various update messages and callbacks of the App, and handle them uniformly when Vsync arrives. For example, centralized processing of Input (mainly the processing of Input events), animation (animation related), traversal (including measure, layout, draw and other operations), judgment of frame jamming, recording of CallBack time, etc.
  2. Start:
    Be responsible for requesting and receiving Vsync signals. Receive Vsync event callback (via FrameDisplayEventReceiver.onVsync); Request Vsync(FrameDisplayEventReceiver.scheduleVsync)

It can be seen from the above that Choreographer plays the role of a tool man. The reason why he is important is that through the top-down mechanism of Choreographer + SurfaceFlinger + Vsync + TripleBuffer, Android App can run at a stable frame rate (most of which are 60fps at present) to reduce the discomfort caused by frame rate fluctuation

Understanding Choreographer can also help App developers know the basic principle of each frame of the program, and deepen their understanding of Message, Handler, Looper, MessageQueue, Measure, Layout and Draw, Many APM tools also use the combination of "Choreographer" (using FrameCallback + FrameInfo) + MessageQueue (using IdleHandler) + Looper (setting custom MessageLogging). After understanding these in depth, you can do optimization, and your mind will be clearer.

In addition, although drawing is a good way to explain the process, I personally don't like drawing very much, because systrace and MethodTrace are often used. Systrace is a tool to show the operation of the whole system from left to right (including cpu, SurfaceFlinger, SystemServer, App and other key processes), Key processes can also be easily demonstrated using # systrace # and # MethodTrace #. When you are familiar with the system code, you can look at systrace to correspond to the actual situation of mobile phone operation. So in addition to some network diagrams, I will show more of the following articles with systrace

1. From the perspective of Systrace, choreographer's workflow

The following figure takes the sliding desktop as an example. Let's take a look at a complete preview (App process) of the sliding desktop from left to right. We can see that from left to right in Systrace, each green frame represents one frame, which represents the final picture we can see on the mobile phone

  1. The width of each gray bar and white bar in the figure is a Vsync time, that is, 16.6ms
  2. Processing flow of each frame: callback after receiving Vsync signal - > UI thread – > renderthread – > surfaceflinger (not shown in the figure)
  3. UI Thread and RenderThread can complete the rendering of a frame of App, and the rendered Buffer is thrown to SurfaceFlinger to synthesize, and then we can see this frame on the screen
  4. It can be seen that each frame of desktop sliding takes a short time (Ui Thread time + RenderThread time). However, due to the existence of Vsync, each frame will not be processed until Vsync

With the above overall concept, let's enlarge each frame of UI Thread to see the location of choreographer and how choreographer organizes each frame

##2.Choreographer workflow

  1. Choreographer initialization
    • Initialize FrameHandler and bind Looper
    • Initialize FrameDisplayEventReceiver and establish communication with SurfaceFlinger to receive and request Vsync
    • Initialize CallBackQueues
  2. The appEventThread of SurfaceFlinger wakes up and sends Vsync, and the Choreographer calls back framedisplayeventreceiver Onvsync, enter the main processing function doFrame of SurfaceFlinger
  3. Choreographer.doFrame calculates the frame logic
  4. Choreographer. The doframe handles the first callback: input of the choreographer
  5. Choreographer. The doframe handles the second callback: animation of the choreographer
  6. Choreographer.doFrame handles the third callback of Choreographer: insets animation
  7. Choreographer. The doframe handles the fourth callback of the Choreographer: traversal
    • Synchronous data of UIThread and RenderThread in traversal draw
  8. Choreographer.doFrame handles the fifth callback: commit of choreographer
  9. RenderThread processes the drawing data and really renders
  10. Give the rendered Buffer swap to SurfaceFlinger for synthesis

After the initialization of the first step is completed, the subsequent steps will cycle between steps 2-9

At the same time, the MethodTrace corresponding to this frame is also attached (just preview here, and there will be a detailed large picture below)

Let's look at the specific implementation from the perspective of source code

#3, Source code analysis of Choreographer

Let's take a brief look from the perspective of the source code. The source code only extracts some important logic, while other logic is eliminated. In addition, the interaction between Native and SurfaceFlinger is not included, which is not the focus of this article. Those who are interested can follow it by themselves.

1. Initialization of choreographer

  • Singleton initialization of Choreographer
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
 new ThreadLocal<Choreographer>() {
 @Override
 protected Choreographer initialValue() {
 // Gets the Looper of the current thread
 Looper looper = Looper.myLooper();
 ......
 // Constructing Choreographer objects
 Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
 if (looper == Looper.getMainLooper()) {
 mMainInstance = choreographer;
 }
 return choreographer;
 }
};
  • Constructor for Choreographer
private Choreographer(Looper looper, int vsyncSource) {
 mLooper = looper;
 // 1\.  Initialize FrameHandler
 mHandler = new FrameHandler(looper);
 // 2\.  Initialize DisplayEventReceiver
 mDisplayEventReceiver = USE_VSYNC
 ? new FrameDisplayEventReceiver(looper, vsyncSource)
 : null;
 mLastFrameTimeNanos = Long.MIN_VALUE;
 mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
 //3\.  Initialize CallbacksQueues
 mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
 for (int i = 0; i <= CALLBACK_LAST; i++) {
 mCallbackQueues[i] = new CallbackQueue();
 }
 ......
}
  • FrameHandler
private final class FrameHandler extends Handler {
 ......
 public void handleMessage(Message msg) {
 switch (msg.what) {
 case MSG_DO_FRAME://Start rendering the next frame
 doFrame(System.nanoTime(), 0);
 break;
 case MSG_DO_SCHEDULE_VSYNC://Request Vsync 
 doScheduleVsync();
 break;
 case MSG_DO_SCHEDULE_CALLBACK://Process Callback
 doScheduleCallback(msg.arg1);
 break;
 }
 }
}

##2. Choreographer initialization chain

In the process of Activity startup, after onResume is executed, Activity. Will be called Makevisible(), and then call addView(), and the layer by layer call will enter the following method

ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app) 
-->WindowManagerImpl.addView(View, LayoutParams) (android.view) 
 -->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view)
 -->ViewRootImpl.ViewRootImpl(Context, Display) (android.view)
 public ViewRootImpl(Context context, Display display) {
 ......
 mChoreographer = Choreographer.getInstance();
 ......
 }

3. Introduction to framedisplayeventreceiver

Vsync is registered, applied and received through the FrameDisplayEventReceiver class, so you can briefly introduce it first. FrameDisplayEventReceiver inherits DisplayEventReceiver and has three important methods

  1. onVsync – Vsync signal callback
  2. run – execute doFrame
  3. scheduleVsync – request Vsync signal
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
 ......
 @Override
 public void onVsync(long timestampNanos, long physicalDisplayId, 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() {
 mHavePendingVsync = false;
 doFrame(mTimestampNanos, mFrame);
 }

 public void scheduleVsync() {
 ...... 
 nativeScheduleVsync(mReceiverPtr);
 ......
 }
}

4. Registration of Vsync in choreographer

As can be seen from the function call stack below, the internal class framedisplayeventreceiver. Of Choreographer Onvsync is responsible for receiving Vsync callback and notifying UIThread to process data.

How does the FrameDisplayEventReceiver call back onVsync when the Vsync signal arrives? The answer is that when the FrameDisplayEventReceiver is initialized, it is finally in the form of listening file handle. The corresponding initialization process is as follows

android/view/Choreographer.java

private Choreographer(Looper looper, int vsyncSource) {
 mLooper = looper;
 mDisplayEventReceiver = USE_VSYNC
 ? new FrameDisplayEventReceiver(looper, vsyncSource)
 : null;
 ......
}

android/view/Choreographer.java

public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
 super(looper, vsyncSource);
}

android/view/DisplayEventReceiver.java

public DisplayEventReceiver(Looper looper, int vsyncSource) {
 ......
 mMessageQueue = looper.getQueue();
 mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
 vsyncSource);
}

The subsequent code of nativeInit can be followed by yourself. You can compare this article with the source code. Due to the large space, I won't write it in detail here( https://www.jianshu.com/p/304f56f5d486 ), after sorting out the logic of this section, it will be updated in another article.

In short, during the initialization of framedisplayeventreceiver, Vsync events are transmitted and requested through BitTube (essentially a socket pair). After the surface flinger receives the Vsync event, this event is transmitted to the DisplayEventDispatcher through BitTube through appEventThread, The DisplayEventDispatcher will call back choreographer.com after listening to the Vsync event through the receiving end of BitTube FrameDisplayEventReceiver. Onvsync, trigger to start the drawing of one frame, as shown in the following figure

##5.Choreographer handles the logic of a frame

The logical core of Choreographer's processing and drawing is in Choreographer In the doFrame function, you can see from the following figure that framedisplayeventreceiver Onvsync post itself, and its run method directly calls the doFrame to start the logical processing of a frame

android/view/Choreographer.java

public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
 ......
 mTimestampNanos = timestampNanos;
 mFrame = frame;
 Message msg = Message.obtain(mHandler, this);
 msg.setAsynchronous(true);
 mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
public void run() {
 mHavePendingVsync = false;
 doFrame(mTimestampNanos, mFrame);
}

The doFrame function mainly does the following things

  1. Calculate frame logic
  2. Record frame drawing information
  3. Execute CALLBACK_INPUT,CALLBACK_ANIMATION,CALLBACK_INSETS_ANIMATION,CALLBACK_TRAVERSAL,CALLBACK_COMMIT

##6. Calculate frame logic

void doFrame(long frameTimeNanos, int frame) {
 final long startNanos;
 synchronized (mLock) {
 ......
 long intendedFrameTimeNanos = frameTimeNanos;
 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.");
 }
 }
 ......
 }
 ......
}

Choreographer. The frame drop detection of doFrame is relatively simple. As can be seen from the figure below, when the Vsync signal arrives, it will mark a start_time, mark an end when executing the doFrame_ Time, the difference between these two times is the Vsync processing delay, that is, the frame drop

We look at the calculation logic of frame dropping based on the actual situation of frame dropping of Systrace

It should be noted here that the frame drop calculated by this method is the frame drop of the previous frame, not the frame drop of this frame. This calculation method is flawed, which will lead to some frame drop not being calculated

##7. Record frame drawing information

FrameInfo in Choreographer is responsible for recording frame drawing information. When doFrame is executed, it will record the drawing time of each key node. We can see it by using dumpsys gfxinfo. Of course, Choreographer only records a part, and the rest is recorded in hwui.

The contents of records can be seen from the signs of FrameInfo. When we look at dumpsys gfxinfo later, the data is arranged according to this

// Various flags set to provide extra metadata about the current frame
private static final int FLAGS = 0;

// Is this the first-draw following a window layout?
public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;

// A renderer associated with just a Surface, not with a ViewRootImpl instance.
public static final long FLAG_SURFACE_CANVAS = 1 << 2;

@LongDef(flag = true, value = {
 FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
@Retention(RetentionPolicy.SOURCE)
public @interface FrameInfoFlags {}

// The intended vsync time, unadjusted by jitter
private static final int INTENDED_VSYNC = 1;

// Jitter-adjusted vsync time, this is what was used as input into the
// animation & drawing system
private static final int VSYNC = 2;

// The time of the oldest input event
private static final int OLDEST_INPUT_EVENT = 3;

// The time of the newest input event
private static final int NEWEST_INPUT_EVENT = 4;

// When input event handling started
private static final int HANDLE_INPUT_START = 5;

// When animation evaluations started
private static final int ANIMATION_START = 6;

// When ViewRootImpl#performTraversals() started
private static final int PERFORM_TRAVERSALS_START = 7;

// When View:draw() started
private static final int DRAW_START = 8;

The doFrame function records the time from Vsync time to markperformtraversalstart

void doFrame(long frameTimeNanos, int frame) {
 ......
 mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
 // Process CALLBACK_INPUT Callbacks 
 mFrameInfo.markInputHandlingStart();
 // Process CALLBACK_ANIMATION Callbacks
 mFrameInfo.markAnimationsStart();
 // Process CALLBACK_INSETS_ANIMATION Callbacks
 // Process CALLBACK_TRAVERSAL Callbacks
 mFrameInfo.markPerformTraversalsStart();
 // Process CALLBACK_COMMIT Callbacks
 ......
}

##8. Execute Callbacks

void doFrame(long frameTimeNanos, int frame) {
 ......
 // Process CALLBACK_INPUT Callbacks 
 doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
 // Process CALLBACK_ANIMATION Callbacks
 doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
 // Process CALLBACK_INSETS_ANIMATION Callbacks
 doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
 // Process CALLBACK_TRAVERSAL Callbacks
 doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
 // Process CALLBACK_COMMIT Callbacks
 doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
 ......
}

Input callback call stack

input callback is generally to execute viewrootimpl ConsumeBatchedInputRunnable

android/view/ViewRootImpl.java

final class ConsumeBatchedInputRunnable implements Runnable {
 @Override
 public void run() {
 doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
 }
}
void doConsumeBatchedInput(long frameTimeNanos) {
 if (mConsumeBatchedInputScheduled) {
 mConsumeBatchedInputScheduled = false;
 if (mInputEventReceiver != null) {
 if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
 && frameTimeNanos != -1) {
 scheduleConsumeBatchedInput();
 }
 }
 doProcessInputEvents();
 }
}

After the Input time is processed, it will eventually be passed to the dispatchTouchEvent of DecorView, which is the familiar Input event distribution

Animation callback call stack

In general, we often call view Callback will be used during postonanimation_ ANIMATION

public void postOnAnimation(Runnable action) {
 final AttachInfo attachInfo = mAttachInfo;
 if (attachInfo != null) {
 attachInfo.mViewRootImpl.mChoreographer.postCallback(
 Choreographer.CALLBACK_ANIMATION, action, null);
 } else {
 // Postpone the runnable until we know
 // on which thread it needs to run.
 getRunQueue().post(action);
 }
}

So when is the view called back As for postonanimation, I intercepted a picture. You can have a look by yourself. The most exposed operations should be startScroll and Fling

According to the content of its post, the following is the fling animation after the desktop slides and lets go.

In addition, our Choreographer's FrameCallback also uses CALLBACK_ANIMATION

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
 if (callback == null) {
 throw new IllegalArgumentException("callback must not be null");
 }

 postCallbackDelayedInternal(CALLBACK_ANIMATION,
 callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

Traversal call stack

void scheduleTraversals() {
 if (!mTraversalScheduled) {
 mTraversalScheduled = true;
 //To improve the priority, first post syncbarrier
 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 mChoreographer.postCallback(
 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
 }
}

final class TraversalRunnable implements Runnable {
 @Override
 public void run() {
 // Really start to execute measure, layout and draw
 doTraversal();
 }
}
void doTraversal() {
 if (mTraversalScheduled) {
 mTraversalScheduled = false;
 // SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
 // Real start
 performTraversals();
 }
}
private void performTraversals() {
 // measure operation
 if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
 }
 // layout operation
 if (didLayout) {
 performLayout(lp, mWidth, mHeight);
 }
 // draw operation
 if (!cancelDraw && !newSurface) {
 performDraw();
 }
}

TraceView example for doTraversal

9. Vsync request of the next frame

Due to the existence of animation, sliding and flying operations, we need a continuous and stable frame rate output mechanism. This involves the request logic of Vsync. In the case of continuous operations, such as animation, sliding and flying, the next Vsync application will be triggered when the doFrame of each frame is applied according to the situation, so that we can obtain the continuous Vsync signal.

Look at the following scheduleTraversals call stack (Vsync request will be triggered in scheduleTraversals)

Both invalidate and requestLayout, which we are familiar with, trigger Vsync signal requests

Let's take Animation as an example to see how Animation drives the next Vsync to continuously update the picture

##10. ObjectAnimator animation driven logic
android/animation/ObjectAnimator.java

public void start() {
super.start();
}

android/animation/ValueAnimator.java

 private void start(boolean playBackwards) {
 ......
 addAnimationCallback(0); // Add Animation Callback when the animation start s 
 ......
}
private void addAnimationCallback(long delay) {
 ......
 getAnimationHandler().addAnimationFrameCallback(this, delay);
}

android/animation/AnimationHandler.java

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
 if (mAnimationCallbacks.size() == 0) {
 // post FrameCallback
 getProvider().postFrameCallback(mFrameCallback);
 }
 ......
}

// The mFrameCallback callback here calls back to the doFrame, which post s itself
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
 @Override
 public void doFrame(long frameTimeNanos) {
 doAnimationFrame(getProvider().getFrameTime());
 if (mAnimationCallbacks.size() > 0) {
 // post yourself
 getProvider().postFrameCallback(this);
 }
 }
};

Calling postFrameCallback will go to mchoreographer postFrameCallback, where the Vsync request logic of Choreographer will be triggered

android/animation/AnimationHandler.java

public void postFrameCallback(Choreographer.FrameCallback callback) {
 mChoreographer.postFrameCallback(callback);
}

android/view/Choreographer.java

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) {
// Request Vsync scheduleframelocked - > schedulevsynclocked - > mdisplayeventreceiver scheduleVsync ->nativeScheduleVsync
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

Through the above animation Start setting, using choreographer Framecallback interface. Every frame requests the next Vsync
TraceView example of one frame during animation

11. Summary of source code

  1. Choreographer # is a thread singleton and must be bound to a Looper, because there is a Handler inside it that needs to be bound to the Looper, which is generally bound to the Looper of the App main thread
  2. DisplayEventReceiver is an abstract class whose JNI code part will create a Vsync listener object of IDisplayEventConnection. In this way, the Vsync interrupt signal from AppEventThread can be passed to the Choreographer object. When the Vsync signal arrives, the onVsync function of DisplayEventReceiver will be called.
  3. DisplayEventReceiver also has a scheduleVsync function. When the application needs to draw the UI, it will first apply for a Vsync interrupt, and then draw in the onVsync function of interrupt processing.
  4. Choreographer , defines a , FrameCallback , interface. Whenever Vsync arrives, its doFrame function will be called. This interface is very helpful for the implementation of Android Animation. I used to control the time myself, but now I finally have a fixed time interrupt.
  5. The main function of Choreographer is to call the callback function set by the user through postCallback when receiving the Vsync signal. At present, there are five types of callbacks defined:
  • 1 . CALLBACK_INPUT: handle input events
  • 2 . CALLBACK_ANIMATION: Processing animation
  • 3 . CALLBACK_INSETS_ANIMATION: handle callbacks related to Insets Animation
  • 4 . CALLBACK_TRAVERSAL: it deals with the drawing of controls such as UI
  • 5 . CALLBACK_COMMIT: handle the callback related to commit
  1. The Item initialization (obtain\setup) of ListView # will be in input and animation, depending on
  2. CALLBACK_INPUT ,CALLBACK_ANIMATION # will modify the properties of view, so it is necessary to contact callback first_ Traversal execution

#4, APM and Choreographer

Due to the location of Choreographer, many performance monitoring methods are done by Choreographer. In addition to the built-in frame drop calculation, the FrameCallback and FrameInfo provided by Choreographer expose the interface to the App, so that App developers can monitor the performance of their App through these methods. The common methods are as follows:

  1. doFrame callback using FrameCallback
  2. Monitoring with FrameInfo
    1. Use: adb shell dumpsys gfxinfo# framestats
    2. Example: ADB shell dumpsys gfxinfo.com meizu. flyme. launcher framestats
  3. Monitoring with SurfaceFlinger
    1. Using: adb shell dumpsys SurfaceFlinger – latency
    2. Example: adb shell dumpsys SurfaceFlinger – latency com meizu. flyme. launcher/com. meizu. flyme. launcher. Launcher#0
  4. Use SurfaceFlinger PageFlip mechanism for monitoring
    1. Usage: adb service call SurfaceFlinger 1013
    2. Note: system permission is required
  5. Choreographer's own frame drop calculation logic
  6. BlockCanary performance monitoring based on Looper

1. Use the doFrame callback of FrameCallback

FrameCallback interface

public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}

Interface use

Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );

Interface processing

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
 ......
 postCallbackDelayedInternal(CALLBACK_ANIMATION,
 callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

TinyDancer uses this method to calculate FPS( https://github.com/friendlyrobotnyc/TinyDancer)

##2. Monitoring with FrameInfo

adb shell dumpsys gfxinfo 

 Window: StatusBar
Stats since: 17990256398ns
Total frames rendered: 1562
Janky frames: 361 (23.11%)
50th percentile: 6ms
90th percentile: 23ms
95th percentile: 36ms
99th percentile: 101ms
Number Missed Vsync: 33
Number High input latency: 683
Number Slow UI thread: 273
Number Slow bitmap uploads: 8
Number Slow issue draw commands: 18
Number Frame deadline missed: 287
HISTOGRAM: 5ms=670 6ms=128 7ms=84 8ms=63 9ms=38 10ms=23 11ms=21 12ms=20 13ms=25 14ms=39 15ms=65 16ms=36 17ms=51 18ms=37 19ms=41 20ms=20 21ms=19 22ms=18 23ms=15 24ms=14 25ms=8 26ms=4 27ms=6 28ms=3 29ms=4 30ms=2 31ms=2 32ms=6 34ms=12 36ms=10 38ms=9 40ms=3 42ms=4 44ms=5 46ms=8 48ms=6 53ms=6 57ms=4 61ms=1 65ms=0 69ms=2 73ms=2 77ms=3 81ms=4 85ms=1 89ms=2 93ms=0 97ms=2 101ms=1 105ms=1 109ms=1 113ms=1 117ms=1 121ms=2 125ms=1 129ms=0 133ms=1 150ms=2 200ms=3 250ms=0 300ms=1 350ms=1 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 

---PROFILEDATA---
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000,
0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000,
0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000,
0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000,

##3. Monitoring with SurfaceFlinger
Command interpretation:

  1. The unit of data is nanosecond, and the time is based on the startup time
  2. Each command will get 128 lines of frame related data

Data:

  1. The first row of data indicates the refresh interval refresh_period
  2. Column 1: the data in this part represents the time point when the application draws the image
  3. Column 2: the vertical synchronization time before SF (software) submits the frame to h / w (hardware) for drawing, that is, the timestamp submitted to hardware after drawing each frame. This column is the timestamp of vertical synchronization
  4. Column 3: the time point when SF submits the frame to H/W is the time point when H/W receives the data sent by SF and completes the drawing.

Drop frame jank calculation

Each line can get a value through the following formula. This value is a standard. We call it jankflag. If the jankflag of the current line changes from the jankflag of the previous line, it will be called the frame

ceil((C - A) / refresh-period)

4. Use the SurfaceFlinger PageFlip mechanism for monitoring

 Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
                data.writeInterfaceToken("android.ui.ISurfaceComposer");
mFlinger.transact(1013, data, reply, 0);
final int pageFlipCount = reply.readInt();

final long now = System.nanoTime();
final int frames = pageFlipCount - mLastPageFlipCount;
final long duration = now - mLastUpdateTime;
mFps = (float) (frames * 1e9 / duration);
mLastPageFlipCount = pageFlipCount;
mLastUpdateTime = now;
reply.recycle();
data.recycle();

##5. Choreographer's own frame drop calculation logic

SKIPPED_ FRAME_ WARNING_ The default limit is 30, which is determined by debug choreographer. Skipwarning is controlled by this attribute

 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.");
 }
}

##6. BlockCanary

Blockcanary computing uses Looper's Message mechanism for performance monitoring. It records the front and back of each Message in the MessageQueue to achieve the purpose of monitoring performance

android/os/Looper.java

public static void loop() {
 ...
 for (;;) {
 ...
 // This must be in a local variable, in case a UI event sets the logger
 Printer logging = me.mLogging;
 if (logging != null) {
 logging.println(">>>>> Dispatching to " + msg.target + " " +
 msg.callback + ": " + msg.what);
 }
 msg.target.dispatchMessage(msg);
 if (logging != null) {
 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
 }
 ...
 }
}

5, MessageQueue and Choreographer

In fact, the so-called asynchronous Message is like this. We can insert a Barrier into the Message queue through enqueueBarrier, and then the synchronous messages in the queue whose execution time is later than the Barrier will be blocked by the Barrier and cannot be executed until we call removeBarrier to remove the Barrier, while the asynchronous Message will not be affected, Messages are synchronous messages by default. Unless we call setAsynchronous of Message, this method is hidden. Only when initializing the Handler can you specify that the messages sent to the Handler are asynchronous through parameters. In this way, setAsynchronous of Message will be called in the enqueueMessage of the Handler to set the Message to be asynchronous, from the above Handler enqueueMessage can be seen in the code.

In fact, the so-called asynchronous message has only one function, that is, it can still be processed normally without the influence of the Barrier when setting the Barrier. If the Barrier is not set, the asynchronous message is no different from the synchronous message. You can remove the Barrier through removeSyncBarrier

##1. An example of syncbarrier used in Choreographer

postSyncBarrier when scheduling Traversals

void scheduleTraversals() {
 if (!mTraversalScheduled) {
 mTraversalScheduled = true;
 //To improve the priority, first post syncbarrier
 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 mChoreographer.postCallback(
 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
 }
}

removeSyncBarrier in doTraversal

 void doTraversal() {
 if (mTraversalScheduled) {
 mTraversalScheduled = false;
 // SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
 // Real start
 performTraversals();
 }
}

When the Choreographer Posts messages, these messages will be set as Asynchronous, so the priority of these messages in the Choreographer will be higher,

Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);

6, Vendor optimization

System manufacturers can directly modify the source code and make use of this convenience to do some functions and optimization. However, due to the problem of confidentiality, the code will not be put up directly. I can give you a general idea, and those who are interested can be discussed in private

1. Mobile event optimization

There is no input message in Choreographer itself, but after modifying the source code, the input message can be directly sent to Choreographer. With these input messages, Choreographer can do some things, such as respond in advance without waiting for Vsync

2. Background animation optimization

When an Android App retreats to the background, as long as he is not killed, don't be surprised what he does, because this is Android. Some apps continue to call the Animation Callback in Choreographer after retreating to the background, and the execution of this Callback is completely meaningless, and the user does not know it yet, but the cpu consumption is relatively high.

Therefore, the Choreographer will optimize this situation and prohibit unqualified apps from continuing useless operations in the background

##3. Frame rendering optimization

Like mobile event optimization, with the information of Input event, in some scenarios, we can notify SurfaceFlinger not to wait for Vsync to perform synthesis directly

4. application start optimization

As we said earlier, all operations of the main thread are given to messages. If an operation and unimportant messages are arranged behind the queue, it will have an impact on the operation; By rearranging the MessageQueue, important startup messages related to startup are placed in front of the queue when the application starts, so as to speed up the startup

5. High frame rate optimization

On 90 fps mobile phones, the Vsync interval has changed from 16.6ms to 11.1ms, which brings huge performance and power consumption challenges. How to complete the necessary operations of rendering in one frame is what mobile phone manufacturers must think about and optimize:

  1. Performance and optimization of super App
  2. Game high frame rate cooperation
  3. Logic for switching between 90 fps and 60 fps

Original link: https://androidperformance.com/2019/10/22/Android-Choreographer/

So far, this article has ended. Reprint the articles on the Internet. Xiaobian feels very excellent. You are welcome to click to read the original text and support the original author. If there is infringement, please contact Xiaobian to delete it. Your suggestions and corrections are welcome. At the same time, we look forward to your attention. Thank you for reading. Thank you!

Added by steved on Sun, 30 Jan 2022 00:36:19 +0200