This article, the eighth in the Systrace series, provides a brief introduction to Choreographer within Systrace

The “purpose” of this series is to use Systrace as a tool to look at the overall operation of the Android system from another perspective, and to learn about the Framework from another perspective. Maybe you read a lot of articles about the Framework, but can’t remember the code, or the process it runs through. Maybe you can understand it more deeply from the graphical perspective of Systrace.

This article introduces Choreographer, a class that App developers don’t often touch, but is very important in the Android Framework rendering link. Including introduction background, Introduction to Choreographer, partial source parsing, Choreographer and MessageQueue, Choreographer and APM, And some optimization ideas based on Choreographer by mobile vendors

Choreographer was introduced to work with Vsync to provide a stable time for Message processing for upper-level App renderers. This is when Vsync arrives and 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, 16.6ms per refresh. In order to match the refresh frequency of the screen, the system also sets the cycle of Vsync to 16.6ms, 16.6ms per refresh. The Vsync signal wakes up Choreographer to do the drawing operations of your App, and this is where Choreographer is introduced. Understanding Choreographer can also help App developers understand the basics of how each frame of an application works, as well as understanding Message, Handler, Looper, MessageQueue, Measure, Layout, and Draw

Series of articles

  1. Systrace profile
  2. Systrace Basics – Systrace prep
  3. Systrace Basics – Why 60 FPS?
  4. Systrace basic knowledge – SystemServer interpretation
  5. Systrace Basics – SurfaceFlinger interpretation
  6. Systrace Basics – Input interpretation
  7. Systrace basics – Vsync interpretation
  8. Systrace Basics – Vsync-App: Choreographer based Rendering mechanics in detail
  9. Systrace Basics – MainThread and RenderThread interpretation
  10. Systrace Basics – Triple Buffer interpretation
  11. Systrace Basics – CPU Info interpretation

The nature of the main thread running mechanism

Before we move on to Choreographer, let’s start by understanding the essence of how Android main threads run. This is essentially the processing of messages. Our operations, including the rendering of each frame, are sent to the main thread’s MessageQueue as a Message. MessageQueue processes the message and continues to wait for the next message, as shown in the following figure

A MethodTrace diagram for the main thread runtime

MethodTrace when the main thread runs

Systrace diagram of the main thread running

Systrace for the main thread runtime

evolution

Initially, the Android Framework did not have Vsync. Prior to the introduction of Vsync, the Android Framework rendered a frame of Message with no interval between frames. After the last frame was drawn, the next frame’s Message was processed. The problem is that the frame rate can be unstable, either high or low, as shown below

A MethodTrace diagram of the main thread running without introducing Vsync

MethodTrace_NoVsync

Systrace diagram of the main thread running without introducing Vsync

Systrace_NoVsync

It can be seen that the bottleneck at this time is in the dequeueBuffer, because the screen has a refresh cycle, FB consumes the Front Buffer at a certain rate, so SF consumes the App Buffer at a certain rate. Therefore, the App will get stuck in the dequeueBuffer, which will lead to the unstable acquisition of the App Buffer. It is easy to get stuck and drop frames.

A stable frame rate is a good experience for the user. If you’re playing King of Glory, for example, it’s better to be stable at 50 FPS than if the FPS is constantly changing between 60 and 40.

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

Introducing the Choreographer

Choreographer was introduced to work with Vsync to provide a stable time for Message processing for upper-level App renderers. This is when Vsync arrives and the system controls the timing of each frame drawing operation by adjusting the Vsync signal cycle. As for the reason why the Vsync cycle is set to 16.6ms (60 FPS), it is because most mobile phone screens have a refresh rate of 60Hz, that is, 16.6ms per refresh. The system also sets the Vsync cycle to 16.6ms to match the screen refresh frequency. Every 16.6ms, Vsync signals wake up Choreographer to start drawing apps. If each Vsync cycle can be rendered, the App will have an FPS of 60, which will feel very smooth to the user. This is the main role introduced into Choreographer

Apply a frame to display the flow

Of course, there are more and more mobile phones using 90Hz or 120Hz refresh rate screen, and the Vsync cycle of the system ranges from 16.6ms to 11.1ms and then to 8ms. The operation in the figure above needs to be completed in a shorter time, and the requirements for performance are getting higher and higher. The details can be seen from the new smooth experience. Ramble on this article at 90Hz

Choreographer profile

Choreographer plays the role of the go-between for Android rendering links

  1. “On top” : Is responsible for receiving and processing App updates and callbacks until Vsync arrives. For example, processing Input(mainly processing Input events), Animation(Animation related), Traversal(including measure, layout, draw and other operations), judging the frame delay, recording CallBack time, etc
  2. Start down: requests and receives Vsync signals. Receive Vsync event callback (through FrameDisplayEventReceiver onVsync); Request Vsync (FrameDisplayEventReceiver scheduleVsync).

As you can see from above, Choreographer is playing the role of a tool man. He is important because through Choreographer + SurfaceFlinger + Vsync + TripleBuffer, This ensures that Android App can run at a stable frame rate (currently most of it is 60fps), reducing the discomfort caused by frame rate fluctuations.

Understanding Choreographer can also help App developers understand the fundamentals of how an application runs every frame, It can also deepen the understanding of “Message, Handler, Looper, MessageQueue, Measure, Layout, Draw”. Many “APM” tools also use “Choreographer(using FrameCallback + FrameInfo)” + “MessageQueue (using IdleHandler)” + “Looper (setting custom) MessageLogging) “and once you know what you’re doing, you’ll have a clearer idea of what you’re doing.

And although drawing diagrams is a good way to explain a process, I’m not a big fan of drawing diagrams because I use Systrace and MethodTrace a lot, Systrace is a tool to display the operation of the entire system from left to right (including CPU, SurfaceFlinger, SystemServer, App and other key processes). Using “Systrace” and “MethodTrace” also makes it easy to show key processes. When you are more familiar with the system code, look at Systrace to see how the phone actually works. So, in addition to some network diagrams, I will use Systrace to present the rest of the following articles.

Choreogrepher’s workflow from Systrace’s point of view

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

  1. Each gray bar and white bar width in the figure is a Vsync time, that is, 16.6ms
  2. -> UI Thread -> RenderThread -> SurfaceFlinger(not shown)
  3. The UI Thread and RenderThread can complete the rendering of an App frame. The rendered Buffer is thrown to SurfaceFlinger for composition, and then we can see the frame on the screen
  4. As you can see, each frame of the desktop slide takes a very short time (Ui Thread time + RenderThread time), but due to Vsync, each frame is not processed until Vsync
Slide desktop Systrace

With this overall concept in mind, let’s zoom in on each frame of the UI Thread and see where Choreogrepher is and how Choreogrepher organizes each frame

Slide the desktop for a detailed frame of Systrace

Choreographer’s workflow

  1. Choreographer initialization
  2. Initialize the FrameHandler and bind Looper
  3. Initialize FrameDisplayEventReceiver, establish communication with SurfaceFlinger Vsync used to receive and request
  4. Initialize CallBackQueues
  5. SurfaceFlinger’s appEventThread wakes up to send Vsync, Choreographer callback FrameDisplayEventReceiver onVsync, into SurfaceFlinger doFrame main processing function
  6. Choreographer. DoFrame calculates the frame drop logic
  7. Choreographer. DoFrame handles Choreographer’s first callback: input
  8. Choreographer. DoFrame handles Choreographer’s second callback: animation
  9. Choreographer. DoFrame handles Choreographer’s third callback: Insets animation
  10. Choreographer. DoFrame handles Choreographer’s fourth callback: traversal
  11. Traversal-draw In which UIThread and RenderThread synchronize data
  12. Choreographer. DoFrame handles Choreographer’s fifth callback: Commit?
  13. RenderThread processes the drawing data to actually render
  14. Render the rendered Buffer swap to SurfaceFlinger for composition

After the initial initialization is complete, subsequent cycles are performed between steps 2-9

Also attached is the MethodTrace that corresponds to this frame (just preview it here, and see the larger image below).

MethodTrace

Here we are from the source point of view, look at the specific implementation

The source code parsing

Here is a brief look from the source code point of view, the source code only extracts some important logic, other logic is excluded, in addition to Native part and SurfaceFlinger interaction part is not included, it is not the focus of this article, interested can follow.

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(a) {
 // Get the current thread's Looper  Looper looper = Looper.myLooper(); . // Construct Choreographer objects  Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);  if (looper == Looper.getMainLooper()) {  mMainInstance = choreographer;  }  return choreographer;  } }; Copy the code

The 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());  Initialize the CallbacksQueues  mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];  for (int i = 0; i <= CALLBACK_LAST; i++) {  mCallbackQueues[i] = new CallbackQueue();  } .} Copy the code

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

Choreographer initializes chains

During the Activity startup process, after onResume is executed, activity.makevisible () is called, and then addView() is called, which leads to 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(); . } Copy the code

FrameDisplayEventReceiver profile

Vsync registration, apply for and receive by FrameDisplayEventReceiver this class, so we can make a brief introduction. DisplayEventReceiver FrameDisplayEventReceiver inheritance, there are three important methods

  1. OnVsync — Vsync signal callback
  2. Run — Performs doFrame
  3. ScheduleVsync — Request a 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(a) {  mHavePendingVsync = false;  doFrame(mTimestampNanos, mFrame);  }   public void scheduleVsync(a) { . nativeScheduleVsync(mReceiverPtr); . } } Copy the code

The registration of Vsync in Choreographer

From the function call stack as you can see, the Choreographer of the inner class FrameDisplayEventReceiver. OnVsync receive Vsync callback, notify the UIThread for data processing.

So FrameDisplayEventReceiver is through what way in callback onVsync Vsync signal arrival time? The answer is FrameDisplayEventReceiver initialization, ultimately through the monitor file handle forms, its 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;
.} Copy the code

android/view/Choreographer.java

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

android/view/DisplayEventReceiver.java

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

NativeInit can oneself with the subsequent code, can control the article and the source code, because the space is more, here is not fine to write (www.jianshu.com/p/304f56f5d)… After the logic of this piece is sorted out, it will be updated in another article.

In simple terms, FrameDisplayEventReceiver initialization process, through BitTube (is essentially a socket pair), to deliver Vsync and request event, when SurfaceFlinger received Vsync events, Pass the event via BitTube via appEventThread to DisplayEventDispatcher, which listens to the Vsync event via the BitTube receiver, The callback Choreographer. FrameDisplayEventReceiver. OnVsync, trigger start to paint a frame, the following figure

FrameDisplayEventReceiver

Choreographer deals with the logic of a frame

Choreographer processing drawing logical core in Choreographer. DoFrame function, from the image below you can see, FrameDisplayEventReceiver. OnVsync post himself, Its run method directly calls 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(a) {  mHavePendingVsync = false;  doFrame(mTimestampNanos, mFrame); } Copy the code

The doFrame function does several things

  1. Computes the drop frame logic
  2. Record frame drawing information
  3. CALLBACK_INPUT, CALLBACK_ANIMATION, CALLBACK_INSETS_ANIMATION, CALLBACK_TRAVERSAL, CALLBACK_COMMIT

Computes the drop 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.");  }  } . } .} Copy the code

Choreographer. DoFrame drop detection is relatively simple. As you can see below, the arrival of the Vsync signal is marked with a start_time and the execution of the doFrame signal with an end_time. That’s dropping frames

Drop frame detection logic

Let’s look at the actual case of frame drop in Systrace to see the calculation logic of frame drop

Drop frame logic in Systrace

It should be noted 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, and some frames are not counted

Record frame drawing information

Choreographer FrameInfo is responsible for recording frame drawing information. When doFrame is executed, it records the drawing time of each key node, which can be seen using Dumpsys GfXInfo. Of course Choreographer only records part of it, the rest will be recorded on the HWUi side.

The contents of the records can be seen from the FrameInfo flags. Dumpsys Gfxinfo is used to arrange the data

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

Records from Vsync doFrame function time to markPerformTraversalsStart time

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

Implement Callbacks

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

The Input callback calls the stack

“Input” callback is generally perform ViewRootImpl. ConsumeBatchedInputRunnable

android/view/ViewRootImpl.java

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

The Input time is processed and eventually passed to the DecorView’s dispatchTouchEvent, leading to the familiar distribution of Input events

Input Event distribution logic

Animation calls back to the stack

The most common thing we see is CALLBACK_ANIMATION when we call View.postonanimation

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

PostOnAnimation is not a callback to view. postOnAnimation. It is not a callback to view. postOnAnimation

CALLBACK_ANIMATION

The call stack is based on the contents of the post, and the following is the fling after the slide.

fling

Choreographer also uses CALLBACK_ANIMATION for FrameCallback

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

Traversal call stack

void scheduleTraversals(a) {
    if(! mTraversalScheduled) {        mTraversalScheduled = true;
        // To increase the priority, postSyncBarrier is required
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 mChoreographer.postCallback(  Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);  } }  final class TraversalRunnable implements Runnable {  @Override  public void run(a) {  // Start executing measure, Layout, draw  doTraversal();  } } void doTraversal(a) {  if (mTraversalScheduled) {  mTraversalScheduled = false;  // remove the SyncBarrier mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);  // Get started  performTraversals();  } } private void performTraversals(a) {  / / measure operation  if(focusChangedDueToTouchMode || mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);  }  / / layout operations  if (didLayout) {  performLayout(lp, mWidth, mHeight);  }  / / the draw operation  if(! cancelDraw && ! newSurface) { performDraw();  } } Copy the code

TraceView example of doTraversal

doTraversal

Vsync request for the next frame

Animations, slides, and Fling require a continuous, stable frame rate. This involves the request logic of Vsync. In continuous operations such as animations, slides, and flings, each doFrame will trigger the next Vsync request. In this way, continuous Vsync signals can be obtained.

Look at the scheduleTraversals call stack below (where the Vsync request is triggered)

scheduleTraversals

Both the familiar Invalidate and requestLayout trigger the Vsync signal request

Let’s take the Animation as an example. How does the Animation drive the next Vsync to continuously update the screen

ObjectAnimator animation driver logic

android/animation/ObjectAnimator.java

public void start(a) {
    super.start();
}
Copy the code

android/animation/ValueAnimator.java

private void start(boolean playBackwards) {
.    addAnimationCallback(0); // Add an Animation Callback to start
.}
private void addAnimationCallback(long delay) { . getAnimationHandler().addAnimationFrameCallback(this, delay); } Copy the code

android/animation/AnimationHandler.java

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        // post FrameCallback
        getProvider().postFrameCallback(mFrameCallback);
    }
.}  // mFrameCallback calls doFrame and posts itself inside private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {  @Override  public void doFrame(long frameTimeNanos) {  doAnimationFrame(getProvider().getFrameTime());  if (mAnimationCallbacks.size() > 0) {  / / post  getProvider().postFrameCallback(this);  }  } }; Copy the code

Will call postFrameCallback go the mChoreographer postFrameCallback, this will trigger a Choreographer Vsync request logic

android/animation/AnimationHandler.java

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

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

Through the above Animation. Start setting, using the Choreographer. FrameCallback interface, each frame to request a Vsync “a frame of the Animation process TraceView sample”

Animation

Source summary

  1. Choreographer is thread singleton and must be bound to a Looper because there is an internal Handler that needs to be bound to a Looper, typically the Looper binding for the main thread of your App
  2. The “DisplayEventReceiver” is an abstract class whose JNI code creates a Vsync listener object for IDisplayEventConnection. In this way, VSYNC interrupt signals from AppEventThread can be passed to Choreographer objects. When the Vsync signal arrives, the DisplayEventReceiver’s onVsync function will be called.
  3. “DisplayEventReceiver” also has a scheduleVsync function. When an application needs to draw a UI, it first requests a Vsync interrupt and then draws it in the onVsync function that handles the interrupt.
  4. Choreographer defines a “FrameCallback” “interface” whose doFrame function will be called whenever Vsync arrives. This interface is very helpful for the implementation of Android Animation. Before are their own control of time, now finally have a fixed interruption of time.
  5. The main function of Choreographer is to invoke callback functions set by the user via postCallback when a Vsync signal is received. Currently, there are five types of callbacks defined:
  6. CALLBACK_INPUT: processes input events
  7. CALLBACK_ANIMATION: deals with Animation processing
  8. CALLBACK_INSETS_ANIMATION: Handles callbacks related to Insets Animation
  9. “CALLBACK_TRAVERSAL” : handling is related to rendering controls such as UI
  10. CALLBACK_COMMIT: processes callbacks related to Commit
  11. The Item initialization of the “ListView” can be obtained in the input or in the animation, depending on how the animation works
  12. CALLBACK_INPUT and CALLBACK_ANIMATION modify the properties of the view, so they need to be traversal with CALLBACK_ANIMATION first

APM and Choreographer

Due to Choreographer’s location, many of the performance monitoring methods are done using Choreographer. In addition to the built-in frame drop calculation, Choreographer provides FrameCallback and FrameInfo that expose interfaces to apps. App developers can monitor the performance of their own apps through these methods, which are commonly used as follows:

  1. Take advantage of FrameCallback’s doFrame callback
  2. Use FrameInfo for monitoring
  3. Use: ADB shell Dumpsys GFxinfo framestats
  4. Example: ADB shell dumpsys gfxinfocom.meizu.flyme. Launcher framestats
  5. Using SurfaceFlinger for monitoring
  6. Adb shell dumpsys SurfaceFlinger –latency
  7. Example: the adb shell dumpsys SurfaceFlinger — latency com. Meizu. Flyme. The launcher/com. Meizu. Flyme. The launcher. Launcher# 0
  8. SurfaceFlinger PageFlip mechanism is used for monitoring
  9. Adb service Call SurfaceFlinger 1013
  10. Note: System permission is required
  11. Choreographer itself calculates the frame drop logic
  12. BlockCanary is based on Looper performance monitoring

Take advantage of FrameCallback’s doFrame callback

FrameCallback interface

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

Interface to use

Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );
Copy the code

Interface processing

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

TinyDancer uses this method to calculate FPS (github.com/friendlyrob…)

Use FrameInfo for monitoring

adb shell dumpsys gfxinfo framestats

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,DrawS tart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,,0,10158315693363,10158315760759,10158315769821,10158316032165,101583 0101831881 426101831881 426922372368477807 16627842101831838 988101831055 915101832387 269101832770 654428 000773 000,,0,10158332799196,10158332868519,10158332877269,10158333137738,101583 0101833036 261101833036 261922372368477807 33780654101833993 206101833078 467101833689 561101833307 061474 000885 000,,0,10158349710238,10158349773102,10158349780863,10158350405863,101583 0101834665 353101834665 353922372368477807 51135967101835360 446101835300 863101835305 654101835814 509471 000836 000,,0,10158365782373,10158365821019,10158365825238,10158365975290,101583 0101836296 729101836296 729922372368477807 66547946101836687 217101836240 706101836429 248101836291 852269 000476 000,Copy the code

Using SurfaceFlinger for monitoring

Command explanation:

  1. The unit of data is nanosecond, and the time starts from the startup time
  2. Each command yields 128 lines of frame-related data

Data:

  1. The first line of data, which indicates the refresh interval refresh_period
  2. Column 1: The data in this section represents the point in time at which the application drew the image
  3. Column 2: The vSYNC time before SF(software) submits the frame to H/W(hardware) rendering, i.e. the time stamp submitted to hardware after each frame is drawn. This column is the vsync time stamp
  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 the time point when the drawing is completed.

Drop frames jank calculation

Each row can be given a value by the following formula. This value is a standard called JankFlag. If the jankFlag of the current row changes from the JankFlag of the previous row, the frame is called off

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

SurfaceFlinger PageFlip mechanism is used 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(); Copy the code

Choreographer itself calculates the frame drop logic

SKIPPED_FRAME_WARNING_LIMIT default is 30, from the debug. Choreographer. Skipwarning this property control

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

BlockCanary

Blockcanary calculates performance monitoring using Looper’s Message mechanism. It records before and after each Message in MessageQueue to monitor performance

android/os/Looper.java

public static void loop(a) {
.    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);  }   } } Copy the code

MessageQueue and Choreographer

If a Barrier is inserted into an enqueueBarrier, all synchronous messages will be blocked by the Barrier. Asynchronous messages do not matter until we call removeBarrier to remove the Barrier, and messages default to synchronous messages unless we call setAsynchronous of Message, which is hidden. The enqueueMessage of the Handler will call the setAsynchronous asynchronous Message to make the Message asynchronous only when the Handler is initialized. You can see this in the code above for handler. enqueueMessage.

If a Barrier is not set, asynchronous messages are no different from synchronous messages and can be removed by removeSyncBarrier

An example of SyncBarrier used in Choreographer

ScheduleTraversals postSyncBarrier

void scheduleTraversals(a) {
    if(! mTraversalScheduled) {        mTraversalScheduled = true;
        // To increase the priority, postSyncBarrier is required
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 mChoreographer.postCallback(  Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);  } } Copy the code

RemoveSyncBarrier when doTraversal

void doTraversal(a) {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // remove the SyncBarrier
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
 // Get started  performTraversals();  } } Copy the code

Choreographer Post Messages will be set to Asynchronous so that the messages in Choreographer will have a higher priority,

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

Vendors to optimize

System manufacturers can directly modify the source code, but also use the convenience of this aspect, do some functions and optimization, but because of the problem of confidentiality, the code is not directly put up, I can roughly say about the idea, interested in private discussion

Movement event optimization

Choreographer itself does not have input messages, but after modifying the source code, input messages can be sent directly to Choreographer. With these input messages, Choreographer can do things like respond ahead of time, Not to wait Vsync

Background animation optimization

When an Android App goes back into the background, as long as it doesn’t get killed, don’t be surprised what it does, because that’s Android. Some apps continue to call Choreographer’s Animation Callback even after they have fallen into the background, and the execution of this Callback is completely meaningless and unknown to the user, but is CPU intensive.

So Choreographer is going to optimize for this situation by preventing non-conforming apps from continuing useless operations in the background

Background Animation

Frame rendering optimization

As with motion event optimization, with Input event information, in some scenarios we can tell SurfaceFlinger to do the synthesis without waiting for Vsync

Application startup optimization

We said earlier that all operations on the main thread are given to Message, and that if non-important messages are queued at the end of an operation, that operation will be affected. By rearranging MessageQueue, the important startup messages related to the startup are placed in front of the queue when the application is started to speed up startup

High frame rate optimization

On phones with 90 or 120 FPS, the Vsync interval goes from 16.6ms to 11.1ms or 8ms, which brings huge performance and power challenges. How to perform the necessary operations of rendering in one frame is something that phone manufacturers have to think about and optimize:

  1. Super App performance and optimization
  2. Game high frame rate cooperation
  3. Logic for switching between 120fps, 90FPS and 60fps

The resources

  1. www.jianshu.com/p/304f56f5d…
  2. Gityuan.com/2017/02/25/…
  3. Developer.android.com/reference/a…
  4. www.jishuwen.com/d/2Vcc
  5. Juejin. Cn/post / 684490…
  6. Android development master class

About me

Small factory system r&d engineer, more information can click on me, I really hope to communicate with you, common progress.

A person can go faster, a group can go farther

This article is formatted using MDNICE