Android Performance

Android Perfetto Series 5: Choreographer-based Rendering Flow

Word count: 6.7kReading time: 42 min
2025/03/26
loading

This article introduces Choreographer, a class that App developers may not frequently encounter but is critically important in the Android Framework rendering pipeline. We will cover the background of its introduction, a brief overview, partial source code analysis, its interaction with MessageQueue, its application in APM (Application Performance Monitoring), and some optimization ideas for Choreographer by mobile phone manufacturers.

The introduction of Choreographer is mainly to cooperate with Vsync to provide a stable Message processing timing for upper-layer application rendering. When the Vsync signal arrives, the system controls the timing of each frame’s drawing operation by adjusting the Vsync signal cycle. Currently, the screen refresh rate of mainstream mobile phones has reached 120Hz, which means refreshing once every 8.3ms. The system adjusts the Vsync cycle accordingly to match the screen refresh frequency. When each Vsync cycle arrives, the Vsync signal wakes up the Choreographer to execute the application’s drawing operation. This is the main purpose of introducing Choreographer. Understanding Choreographer can also help application developers deeply understand the operating principle of each frame, and at the same time deepen their understanding of core components such as Message, Handler, Looper, MessageQueue, Input, Animation, Measure, Layout, and Draw. Many APM (Application Performance Monitoring) tools also utilize the combination mechanisms of Choreographer (via FrameCallback + FrameInfo), MessageQueue (via IdleHandler), and Looper (via custom MessageLogging) for performance monitoring. After deeply understanding these mechanisms, developers can conduct performance optimization more specifically and form systematic optimization ideas.

Table of Contents

This is the fifth article in the Perfetto series, mainly giving a brief introduction to Choreographer in Perfetto.

The purpose of this series is to view the overall operation of the Android system from another perspective through the Perfetto tool, and also to learn about the Framework from a different angle. Maybe you have read many articles about the Framework, but always can’t remember the code, or are not clear about its running process. Perhaps from the graphical perspective of Perfetto, you can understand it more deeply.

Perfetto Series Catalog

  1. Android Perfetto Series Catalog
  2. Android Perfetto Series 1: Introduction to Perfetto
  3. Android Perfetto Series 2: Perfetto Trace Capture
  4. Android Perfetto Series 3: Familiarizing with Perfetto View
  5. Android Perfetto Series 4: Opening Large Traces Locally with Command Line
  6. Android Perfetto Series 5: Android App Choreographer-based Rendering Flow
  7. Android Perfetto Series 6: Why 120Hz? Advantages and Challenges of High Refresh Rate
  8. Android Perfetto Series 7: MainThread and RenderThread Interpretation
  9. Android Perfetto Series 8: Indepth Understanding of Vsync Mechanism and Performance Analysis
  10. Android Perfetto Series 9: CPU Information Interpretation
  11. Video (Bilibili) - Android Perfetto Basics and Case Sharing

If you haven’t seen the Systrace series yet, here is the portal:

  1. Systrace Series Catalog: Systematically introduces the use of Systrace, the predecessor of Perfetto, and learns and understands the basic rules of Android performance optimization and Android system operation through Systrace.
  2. Systrace version of this article: Detailed Explanation of Android Choreographer-based Rendering Mechanism
  3. Personal Blog: Personal blog, mainly Android-related content, also contains some life and work-related content.

Everyone is welcome to join the WeChat group or Planet on the About Me page to discuss your questions, the parts of Perfetto you most want to see, and discuss all Android development-related content with other group members.

The Essence of Main Thread Execution

Before discussing Choreographer, let’s first clarify the essence of Android’s main thread operation, which is actually the processing process of Messages. Our various operations, including the rendering operations of each frame, are sent to the MessageQueue of the main thread in the form of Messages. The MessageQueue continues to wait for the next message after processing the current one, as shown in the figure below.

MethodTrace Illustration

Perfetto Illustration

image-20250331235337987

Evolution

In Android versions before the introduction of Vsync, there was no interval between Messages related to rendering a frame. As soon as the previous frame was drawn, the Message for the next frame began to be processed immediately. The problem with this was that the frame rate was unstable, potentially high or low, as shown below:

MethodTrace Illustration

Trace Illustration (Resource is relatively old, using Systrace illustration)

It can be seen that the bottleneck at this time was in dequeueBuffer. Because the screen has a refresh cycle, the speed at which FB consumes the Front Buffer is constant, so the speed at which SF consumes the App Buffer is also constant. Therefore, the App would get stuck at dequeueBuffer. This would lead to unstable acquisition of the App Buffer, easily causing stuttering and dropped frames.

For users, a stable frame rate is a good experience. For example, when playing Honor of Kings, compared to frequent fluctuations between 60 and 40 fps, users feel better when it is stable at 50 fps.

Therefore, in the evolution of Android, the mechanism of Vsync + TripleBuffer + Choreographer was initially introduced. Later, in the Android S (Android 12) version, BlastBufferQueue was further introduced, jointly forming the modern Android stable frame rate output mechanism, allowing the software layer and hardware layer to work together at a common frequency.

Introducing Choreographer

The introduction of Choreographer is mainly to cooperate with Vsync to provide a stable Message processing timing for upper-layer application rendering. When the Vsync signal arrives, the system controls the timing of each frame’s drawing operation by adjusting the Vsync signal cycle. Currently, the screen refresh rate of mainstream mobile phones has reached 120Hz, which means refreshing once every 8.3ms. The system adjusts the Vsync cycle accordingly to match the screen refresh frequency. When each Vsync cycle arrives, the Vsync signal wakes up the Choreographer to execute the application’s drawing operation. If the application can complete rendering in each Vsync cycle, the application’s fps will be 120, and the user feels very smooth. This is the main function of introducing Choreographer.

Of course, the refresh rate of mainstream flagship mobile phones has reached 120Hz, and the Vsync cycle has been shortened to 8.3ms. The operations in the above figure must be completed in a shorter time, and the performance requirements are getting higher and higher. For details, please refer to the article New Smooth Experience, 90Hz Rambling (although the article discusses 90Hz, the same principle applies to 120Hz).

Introduction to Choreographer

Choreographer plays a connecting role in the Android rendering pipeline.

  1. Upstream connection: Responsible for receiving and processing various update messages and callbacks from the App, and processing them uniformly when Vsync arrives. For example, centralized processing of Input (mainly processing of Input events), Animation (animation related), Traversal (including measure, layout, draw, etc. operations), judging stuck frames and dropped frames, recording Callback time consumption, etc.
  2. Downstream connection: Responsible for requesting and receiving Vsync signals. Receive Vsync event callbacks (via FrameDisplayEventReceiver.onVsync); Request Vsync (FrameDisplayEventReceiver.scheduleVsync).

From the above, it can be seen that Choreographer plays a key coordinator role in the Android rendering pipeline. Its importance lies in ensuring that Android applications can run at a stable frame rate (60 fps, 90 fps, or 120 fps) through the complete rendering mechanism of Choreographer + SurfaceFlinger + Vsync + BlastBufferQueue, effectively reducing visual discomfort caused by frame rate fluctuations.

Understanding Choreographer can also help application developers deeply understand the operating principle of each frame, and at the same time deepen their understanding of core components such as Message, Handler, Looper, MessageQueue, Input, Animation, Measure, Layout, and Draw. Many APM (Application Performance Monitoring) tools also utilize the combination mechanisms of Choreographer (via FrameCallback + FrameInfo), MessageQueue (via IdleHandler), and Looper (via custom MessageLogging) for performance monitoring. After deeply understanding these mechanisms, developers can conduct performance optimization more specifically and form systematic optimization ideas.

In addition, although charts are an effective way to explain processes, this article will rely more on the visual output of Perfetto and MethodTrace tools. Perfetto displays the operating status of the entire system in a timeline manner (from left to right), covering the activities of key components such as CPU, SurfaceFlinger, SystemServer, and application processes. Using Perfetto and MethodTrace can intuitively display key execution processes. Once you are familiar with the system code, Perfetto’s output can be directly mapped to the actual running status of the device. Therefore, in addition to citing a small number of network charts, this article mainly relies on Perfetto to display analysis results.

Choreographer’s Workflow from the Perspective of Perfetto

The figure below uses sliding the settings interface as an example. Let’s first look at a complete preview of the settings interface from top to bottom. It can be seen that in Perfetto, from left to right, each green frame represents a frame, representing the picture we can finally see on the phone.

  1. The interval of each VSYNC-app value in the figure is a Vsync time, corresponding to the refresh rate of the current device, such as 16.6ms at 60Hz and 8.3ms at 120Hz. The rising edge or falling edge is the arrival time of Vsync.
  2. Processing flow of each frame: Receive Vsync signal callback -> UI Thread –> RenderThread –> SurfaceFlinger
  3. UI Thread and RenderThread can complete the rendering of one frame of the App. In the latest Android 15, through the BlastBufferQueue mechanism, the rendered Buffer is thrown to SurfaceFlinger for composition, and then we can see this frame on the screen.
  4. It can be seen that the time consumption of each frame of Settings sliding is very short (Ui Thread time consumption + RenderThread time consumption), but due to the existence of Vsync, each frame will wait until Vsync to be processed.

image-20250401001138400

With the overall concept above, let’s zoom in on each frame of the UI Thread to see the position of the Choreographer and how the Choreographer organizes each frame.

image-20250401002047386

Choreographer’s Workflow

  1. Choreographer Initialization
  2. Initialize FrameHandler, bind Looper
  3. Initialize FrameDisplayEventReceiver, establish communication with SurfaceFlinger for receiving and requesting Vsync
  4. Initialize CallBackQueues
  5. SurfaceFlinger’s appEventThread wakes up and sends Vsync, Choreographer callbacks FrameDisplayEventReceiver.onVsync, entering Choreographer’s main processing function doFrame
  6. Choreographer.doFrame calculates frame drop logic
  7. Choreographer.doFrame processes Choreographer’s first callback: input
  8. Choreographer.doFrame processes Choreographer’s second callback: animation
  9. Choreographer.doFrame processes Choreographer’s third callback: insets animation
  10. Choreographer.doFrame processes Choreographer’s fourth callback: traversal
  11. UIThread and RenderThread synchronize data in traversal-draw
  12. Choreographer.doFrame processes Choreographer’s fifth callback: commit
  13. RenderThread processes drawing commands
  14. In Android S (Android 12) and above versions, RenderThread submits drawing content to SurfaceFlinger through BlastBufferQueue
  15. BlastBufferQueue is created and managed by the App side
  16. Works through producer (BBQ_BufferQueue_Producer) and consumer (BufferQueue_Consumer) models
  17. The UI thread does not have to wait for the RenderThread to complete, and can prepare the next frame earlier, reducing main thread blocking

After the first step of initialization is completed, subsequent steps will loop between steps 2-10

Also attached is the MethodTrace corresponding to this frame (just a preview here, there will be a detailed large picture below)

Interaction between Choreographer, RenderThread and BlastBufferQueue

In Android S (Android 12), the interaction between RenderThread and SurfaceFlinger has undergone important changes, the core of which is the introduction of BlastBufferQueue. Let’s see how this mechanism works.

BlastBufferQueue Working Principle

BlastBufferQueue is a BufferQueue variant optimized for UI rendering. It replaces the BufferQueue strictly created by SurfaceFlinger and is instead created and managed by the App side, used for buffer management between App and SurfaceFlinger.

  1. More efficient buffer management
    In the traditional BufferQueue, the App needs to get an available buffer through dequeueBuffer, then render content, and finally submit the buffer to SurfaceFlinger through queueBuffer. In this process, if there is no available buffer, the App needs to wait, which leads to blocking.

    BlastBufferQueue reduces this waiting through smarter buffer management, especially on high refresh rate devices, the effect is more obvious.

  2. Decoupling of RenderThread and UI Thread
    Before Android S, the UI thread (main thread) needed to wait for the RenderThread to complete its work before it could continue to process the next frame. In the new architecture, the UI thread can finish its work earlier, hand over the rendering task to the RenderThread, and then prepare for the work of the next frame without waiting for the current frame to be completely rendered.

    1
    2
    3
    4
    5
    6
    // In ViewRootImpl.performTraversals()
    // Old version needs to wait for draw to complete
    performDraw(); // This will block waiting for RenderThread

    // After BlastBufferQueue introduction
    scheduleTraversals(); // Can prepare for the next frame earlier
  3. Creation Mechanism
    BlastBufferQueue is created during the relayoutWindow process of ViewRootImpl:

    1
    2
    3
    4
    5
    6
    // Example code for creating BBQ
    if (mBlastBufferQueue == null) {
    mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
    mSurfaceSize.x, mSurfaceSize.y,
    mWindowAttributes.format);
    }
  4. Cooperation with Choreographer
    Choreographer is still the core that coordinates all this. When the Vsync signal arrives, Choreographer will trigger doFrame and execute various callbacks, among which CALLBACK_TRAVERSAL will trigger ViewRootImpl‘s performTraversals(), eventually going to the draw process.

    In the draw process, through BlastBufferQueue, RenderThread can work more independently, and the UI thread can return earlier to process other tasks.

Below we will look at the specific implementation from the perspective of source code.

Source Code Analysis

Below is a brief look from the source code perspective. The source code only excerpts some important logic, other logic is removed. In addition, the Native part interacting with SurfaceFlinger is not included, which is not the focus of this article. Those interested can follow it themselves. The following source code is based on the latest implementation of Android 15.

Choreographer Initialization

Choreographer Singleton Initialization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// Get Looper of current thread
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
// Construct Choreographer object, use VSYNC_SOURCE_APP as Vsync source
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};

Choreographer Constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private final class FrameHandler extends Handler {
......
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:// Start operation for the next frame
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:// Request Vsync
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:// Handle Callback
doScheduleCallback(msg.arg1);
break;
}
}
}

Choreographer Initialization Chain

During the Activity startup process, after onResume is executed, Activity.makeVisible() is called, and then addView() is called. The calls layer by layer enter the following method:

1
2
3
4
5
6
7
8
9
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();
......
}

Introduction to FrameDisplayEventReceiver

Vsync registration, application, and reception are all done through the FrameDisplayEventReceiver class, so let’s introduce it simply first. FrameDisplayEventReceiver inherits from DisplayEventReceiver and has three important methods:

  1. onVsync – Vsync signal callback
  2. run – Execute doFrame
  3. scheduleVsync – Request Vsync signal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
......
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame, VsyncEventData eventData) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
// VsyncEventData added in Android 15 passes richer Vsync event data
mVsyncEventData = eventData;
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, mVsyncEventData);
}

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

Vsync Registration in Choreographer

From the function call stack below, we can see that Choreographer‘s inner class FrameDisplayEventReceiver.onVsync is responsible for receiving Vsync callbacks and notifying UIThread to process data.

So how does FrameDisplayEventReceiver callback onVsync when the Vsync signal arrives? The answer is that during the initialization of FrameDisplayEventReceiver, it finally listens to the file handle. The corresponding initialization process is as follows:

android/view/Choreographer.java

1
2
3
4
5
6
7
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
......
}

android/view/Choreographer.java

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

android/view/DisplayEventReceiver.java

1
2
3
4
5
6
public DisplayEventReceiver(Looper looper, int vsyncSource) {
......
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
}

Simply put, during the initialization of FrameDisplayEventReceiver, Vsync events are passed and requested through BitTube (essentially a socket pair). After SurfaceFlinger receives the Vsync event, it passes this event to DisplayEventDispatcher through BitTube via appEventThread. After DisplayEventDispatcher listens to the Vsync event through the receiver end of BitTube, it callbacks Choreographer.FrameDisplayEventReceiver.onVsync, triggering the start of frame drawing, as shown in the figure below:

DisplayEventReceiver Communication Details with SurfaceFlinger

In the Android system, DisplayEventReceiver calls the nativeInit method via JNI to establish a communication channel with the SurfaceFlinger service. This process involves several key steps:

  1. Create NativeDisplayEventReceiver Object: After calling nativeInit in the Java layer, JNI creates a NativeDisplayEventReceiver instance for receiving Vsync signals.

  2. Get IDisplayEventConnection: Get IDisplayEventConnection via ISurfaceComposer interface. This is a Binder interface used to communicate with the SurfaceFlinger service.

  3. Establish BitTube Connection: BitTube is a communication mechanism based on socket pair, designed for high-frequency, small-data-volume cross-process communication. It creates an efficient communication channel between the App process and the SurfaceFlinger process.

  4. File Descriptor Listening: Listen to the file descriptor of BitTube via Looper. When a Vsync signal arrives, Looper will notify DisplayEventDispatcher to process the event.

The entire communication flow is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
App Process                                    SurfaceFlinger Process
| |
|-- Create DisplayEventReceiver ------------->|
| |
|<- Return IDisplayEventConnection (Binder)---|
| |
|-- Create BitTube -------------------------->|
| |
|<- File Descriptor Exchange -----------------|
| |
|-- Register File Descriptor to Looper -------|
| |
|-- Request Vsync (requestNextVsync) -------->|
| |
|<- Send Vsync Event Data --------------------|
| |
|-- Looper Notify -> handleEvent -------------|
| |
|-- Callback Java Layer onVsync --------------|
| |

The advantage of this design is that it avoids using Binder to pass high-frequency Vsync event data, improving performance and real-time performance through direct socket communication, which is crucial for ensuring smooth UI rendering. At the same time, since BitTube uses file descriptors, it can be seamlessly integrated into Android’s Looper mechanism, enabling the entire system to work in an event-driven manner.

Logic of Choreographer Processing a Frame

The core of Choreographer’s drawing logic is in the Choreographer.doFrame function. As can be seen from the figure below, FrameDisplayEventReceiver.onVsync posts itself, and its run method directly calls doFrame to start the logic processing of a frame.

android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public void onVsync(long timestampNanos, long physicalDisplayId, int frame, VsyncEventData eventData) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
mVsyncEventData = eventData;
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, mVsyncEventData);
}

The doFrame function mainly does the following specific things:

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

Recording Frame Drawing Info

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

From these FrameInfo flags, we can see the recorded content. Later when we look at dumpsys gfxinfo, the data is arranged according to this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

public @interface FrameInfoFlags {}

public static final int FRAME_TIMELINE_VSYNC_ID = 1;

// The intended vsync time, unadjusted by jitter
public static final int INTENDED_VSYNC = 2;

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

// The id of the input event that caused the current frame
public static final int INPUT_EVENT_ID = 4;

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

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

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

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

// When the frame needs to be ready by
public static final int FRAME_DEADLINE = 9;

// When frame actually started.
public static final int FRAME_START_TIME = 10;

// Interval between two consecutive frames
public static final int FRAME_INTERVAL = 11;

The doFrame function records the time from Vsync time to markPerformTraversalsStart.

1
2
3
4
5
6
7
8
9
10
11
12
13
void doFrame(long frameTimeNanos, int frame, VsyncEventData eventData) {
......
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
......
}

Executing Callbacks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void doFrame(long frameTimeNanos, int frame, VsyncEventData eventData) {
......
// 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);
......
}

Input Callback Call Stack

input callback generally executes ViewRootImpl.ConsumeBatchedInputRunnable

android/view/ViewRootImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}
}

Input events are processed and finally passed to DecorView‘s dispatchTouchEvent, which leads to the familiar Input event dispatching.

Animation Callback Call Stack

Generally, when we call View.postOnAnimation frequently, CALLBACK_ANIMATION is used.

1
2
3
4
5
6
7
8
9
10
11
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 View.postOnAnimation generally used? I took a screenshot, you can check it yourself. The most contacted should be operations like startScroll, Fling.

Its call stack depends on what it posts. Below is the fling animation after releasing the hand.

In addition, our Choreographer‘s FrameCallback also uses CALLBACK_ANIMATION.

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// In order to increase priority, postSyncBarrier first
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}

final class TraversalRunnable implements Runnable {
@Override
public void run() {
// Really start executing measure, layout, draw
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// removing SyncBarrier here
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// Really 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 of doTraversal

Next Frame Vsync Request

Due to the existence of operations such as animation, sliding, and Fling, we need a continuous and stable frame rate output mechanism. This involves the request logic of Vsync. In continuous operations, such as animation, sliding, and Fling, doFrame of each frame will trigger the application for the next Vsync according to the situation, so that we can obtain continuous Vsync signals.

See the call stack of scheduleTraversals below (scheduleTraversals will trigger Vsync request)

We are familiar with invalidate and requestLayout triggering Vsync signal requests.

Let’s take Animation as an example to see how Animation drives the next Vsync to continuously update the screen.

ObjectAnimator Animation Driving Logic

android/animation/ObjectAnimator.java

1
2
3
public void start() {
super.start();
}

android/animation/ValueAnimator.java

1
2
3
4
5
6
7
8
9
private void start(boolean playBackwards) {
......
addAnimationCallback(0); // Add Animation Callback when animation starts
......
}
private void addAnimationCallback(long delay) {
......
getAnimationHandler().addAnimationFrameCallback(this, delay);
}

android/animation/AnimationHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// post FrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
......
}

// mFrameCallback here callbacks doFrame, inside posts itself
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
// post itself
getProvider().postFrameCallback(this);
}
}
};

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

android/animation/AnimationHandler.java

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

android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 Animation.start setting above, the Choreographer.FrameCallback interface is used to request the next Vsync every frame.
TraceView Example of a Frame During Animation

Source Code Summary

  1. Choreographer is designed using a thread singleton mode and is strongly coupled with Looper. Each thread can only has one Choreographer instance, and it must be bound to a valid Looper object because its internal Handler relies on Looper for message dispatching. In applications, it is usually bound to the main thread’s Looper to ensure thread safety of UI operations.

  2. DisplayEventReceiver is an abstract base class, and its JNI implementation creates an IDisplayEventConnection object as a listener for Vsync signals. Through this mechanism, the Vsync interrupt signal sent by AppEventThread of SurfaceFlinger can be accurately delivered to the Choreographer instance. When the Vsync signal arrives, the system callbacks onVsync method of DisplayEventReceiver, triggering the rendering process.

  3. DisplayEventReceiver provides scheduleVsync method for requesting Vsync signals. When the application needs to update the UI, it first requests the next Vsync interrupt through this method, and then executes the actual drawing logic in the onVsync callback, ensuring that rendering is synchronized with the screen refresh.

  4. Choreographer defines the FrameCallback interface, and its doFrame method is called every time Vsync arrives. This design is of great significance to the Android animation system, enabling animations to be accurately synchronized with the screen refresh rate, providing a smoother and more power-saving animation experience compared to early self-timing implementations.

  5. Choreographer‘s core function is to receive Vsync signals and trigger callback functions registered through postCallback. The framework defines five types of callbacks, sorted by execution priority:

    1. CALLBACK_INPUT: Handle input events, such as touch, key presses, etc.
    2. CALLBACK_ANIMATION: Handle various animation calculations and updates
    3. CALLBACK_INSETS_ANIMATION: Handle system inset animations, such as soft keyboard, status bar animations, etc.
    4. CALLBACK_TRAVERSAL: Handle measurement, layout, and drawing of the view tree
    5. CALLBACK_COMMIT: Perform finishing work, including component memory recycling (onTrimMemory) and performance monitoring
  6. ListView and RecyclerView‘s Item reuse mechanism (ViewHolder pattern) specific implementation at the framework level will involve CALLBACK_INPUT and CALLBACK_ANIMATION stages. When sliding or scrolling quickly, Item initialization, measurement, and drawing may be triggered in the Input callback (such as directly responding to touch events), or may be executed in the Animation callback (such as inertial sliding or automatic scrolling). RecyclerView, through a more efficient reuse mechanism and Prefetch strategy, can intelligently prepare ViewHolder in these two stages, reducing main thread blocking, performing especially well on high refresh rate devices.

  7. CALLBACK_INPUT and CALLBACK_ANIMATION will modify various properties of View (such as position, transparency, transformation matrix, etc.) during execution, so they must be executed before CALLBACK_TRAVERSAL to ensure that all state updates can be correctly applied during the measurement, layout, and drawing process of the current frame. This strict execution order guarantees the consistency and predictability of Android UI rendering.

APM and Choreographer

Due to the position of Choreographer, many performance monitoring means are done using Choreographer. In addition to the built-in dropped frame calculation, FrameCallback and FrameInfo provided by Choreographer expose interfaces to App, allowing App developers to monitor the performance of their own Apps through these methods. Commonly used methods are as follows:

  1. Use doFrame callback of FrameCallback

  2. Use FrameInfo for monitoring

  3. Use: adb shell dumpsys gfxinfo <packagename> framestats

    1. Example: adb shell dumpsys gfxinfo com.meizu.flyme.launcher framestats
  4. Use SurfaceFlinger for monitoring

    1. Use: adb shell dumpsys SurfaceFlinger --latency
    2. Example: adb shell dumpsys SurfaceFlinger --latency com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher#0
  5. Use SurfaceFlinger PageFlip mechanism for monitoring

    1. Use: adb service call SurfaceFlinger 1013
    2. Remark: System permission required
  6. Choreographer’s own dropped frame calculation logic

  7. BlockCanary performance monitoring based on Looper

  8. New: Powerful monitoring capabilities of Perfetto tool

    1. In Android 15, Perfetto became the main performance analysis tool, replacing the early Systrace
    2. Perfetto can capture more detailed system performance data, including Choreographer’s work details
    3. Using Perfetto UI can visualize and analyze the frame rendering process

Use Perfetto for Advanced Monitoring

Perfetto is Android’s new generation system tracking tool, which becomes the default performance analysis solution in Android 15. It provides more powerful functions than Systrace:

  1. Comparison of comprehensive performance data collection
    Perfetto can collect multi-dimensional data such as CPU, memory, graphics rendering, system services, etc. at the same time

  2. Lower overhead
    Using efficient tracking engine, less impact on system performance

  3. Better Choreographer tracking
    Can track Choreographer’s work process in detail, including:

    • Reception and processing of Vsync signals
    • Execution details of doFrame method
    • Execution time of various callbacks
    • The collaboration process between UI thread and RenderThread
  4. Tracking BlastBufferQueue
    Can track the working process of the newly introduced BlastBufferQueue, helping developers understand the buffer management mechanism

Use Perfetto to Track Choreographer

1
2
3
4
5
6
7
# Start recording Perfetto trace
adb shell perfetto --txt -c /data/misc/perfetto-configs/chrome-trace.config -o /data/misc/perfetto-traces/trace.perfetto-trace

# Get trace file after completion
adb pull /data/misc/perfetto-traces/trace.perfetto-trace

# Analyze in Perfetto UI (https://ui.perfetto.dev/)

In Perfetto UI, you can find the event named “Choreographer#doFrame”, which shows the processing time and details of each frame. You can also check the collaboration relationship between the UI thread and RenderThread, and the interaction with SurfaceFlinger.

Use FrameInfo for Monitoring

adb shell dumpsys gfxinfo <packagename> framestats

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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,

Use SurfaceFlinger for Monitoring

Command explanation:

  1. The unit of data is nanoseconds, and the time starts from the boot time
  2. Each command will get 128 lines of frame-related data

Data:

  1. The first line of data indicates the refresh interval refresh_period
  2. Column 1: This part of the data indicates the time point when the application draws the image
  3. Column 2: The vertical sync time before SF (software) submits the frame to H/W (hardware) for drawing, that is, the timestamp submitted to hardware after each frame is drawn. This column is the vertical sync timestamp.
  4. Column 3: The time point when SF submits the frame to H/W, which is regarded as the time point when H/W finishes receiving data from SF, and the time point when drawing is completed.

Frame Drop (Jank) Calculation

Each line can get a value through the following formula, which is a standard we call jankflag. If the jankflag of the current line changes compared to the jankflag of the previous line, it is called a dropped frame.

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

Use SurfaceFlinger PageFlip Mechanism for Monitoring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();

Choreographer’s Own Dropped Frame Calculation Logic

SKIPPED_FRAME_WARNING_LIMIT is 30 by default, controlled by the property debug.choreographer.skipwarning.

1
2
3
4
5
6
7
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.");
}
}

BlockCanary

BlockCanary uses Looper’s message mechanism for performance monitoring. It achieves the purpose of monitoring performance by recording before and after each Message in the MessageQueue.

android/os/Looper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}
...
}
}

MessageQueue and Choreographer

In the Android message mechanism, asynchronous messages have special processing priority. The system can insert a barrier (Barrier) into the message queue through the enqueueBarrier method, so that all synchronous messages after the barrier cannot be executed temporarily until removeBarrier method is called to remove the barrier. Messages marked as asynchronous are not affected by the barrier and can be processed normally.

Messages are synchronous by default. Only by using the setAsynchronous method of Message (this method is a hidden API) can the message be set to asynchronous. When initializing Handler, specific parameters can be passed to specify that all messages sent by this Handler are asynchronous. In this case, the enqueueMessage method of Handler will automatically call the setAsynchronous method of Message.

The core value of asynchronous messages lies in being able to bypass message barriers to proceed execution. If there is no barrier set, asynchronous messages are processed in exactly the same way as synchronous messages. The removeSyncBarrier method can be used to remove the previously set barrier.

An Example of SyncBarrier Used in Choreographer

postSyncBarrier is called when scheduleTraversals.

1
2
3
4
5
6
7
8
9
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// In order to increase priority, postSyncBarrier first
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}

removeSyncBarrier is called when doTraversal.

1
2
3
4
5
6
7
8
9
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// Remove SyncBarrier here
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// Really start
performTraversals();
}
}

When Choreographer posts a Message, it sets these messages as Asynchronous, so the priority of these Messages in Choreographer will be relatively high.

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

Manufacturer Optimizations

Since system manufacturers can directly modify the source code, they also take advantage of this to do some functions and optimizations. However, due to confidentiality issues, the code will not be put up directly. I can roughly talk about the ideas. Those interested can discuss privately.

Input Event Optimization

Choreographer itself does not have input messages, but after modifying the source code, input messages can be directly given to Choreographer. With these Input messages, Choreographer can do some things, such as responding in advance without waiting for Vsync.

Background Animation Optimization

When an Android application retreats to the background, if it is not terminated by the system, it may still continue to execute various operations. In some cases, the application will continue to call Animation Callback in Choreographer. Even if these animations are invisible to the user, the execution of these Callbacks is completely meaningless, but it will cause high CPU resource usage.

Therefore, system manufacturers will optimize for this situation in Choreographer, restricting background applications that do not meet the conditions from continuing to execute meaningless animation callbacks through a series of strategies, effectively reducing system resource usage.

Frame Drawing Optimization

Like input event optimization, since we have Input event information, in some scenarios we can notify SurfaceFlinger not to wait for Vsync to do composition operations directly.

App Launch Optimization

We said earlier that all operations of the main thread are based on Message. If an operation, a non-important Message, is arranged at the back of the queue, it will affect this operation; by rearranging MessageQueue, when the application starts, important startup Messages are placed in front of the queue to speed up the startup speed.

Animation Callback Front-loading

After the main thread of the previous frame is finished, there is actually a period of idle time before the next Vsync. This period of time can actually be used to prepare for the next frame. Then we can advance the animation callback of the next frame to do it here, which can effectively use the idle time of the CPU and reduce dropped frames.

Frame Interpolation

Like Animation callback front-loading, after the main thread of the previous frame is finished, there is actually a period of idle time before the next Vsync. This period of time can actually be used to prepare for the next frame. It’s just that here we directly generate a new frame: that is, execute doFrame twice in one Vsync. Frame interpolation is equivalent to preparing the data of the next few frames in advance, so that when encountering a truly time-consuming frame, there will be no stuttering.

Frame interpolation implementation is mainly in sliding scenarios, used on sliding components that implement OverScroller, such as ListView, RecyclerView. Because if OverScroller is used, we will know the sliding time and distance when touch up, and we can do tricks (frame interpolation) in the middle, equivalent to drawing the content of the next few VSyncs in one Vsync.

High Refresh Rate Optimization

The high refresh rate (120 Hz) on modern Android devices shortens the Vsync interval from 16.6 ms to 8.3 ms, which brings huge performance and power consumption challenges. How to complete the necessary operations for rendering in one frame is a place that mobile phone manufacturers must think about and optimize:

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

Reference Materials

  1. https://www.jianshu.com/p/304f56f5d486
  2. http://gityuan.com/2017/02/25/choreographer/
  3. https://developer.android.com/reference/android/view/Choreographer
  4. https://www.jishuwen.com/d/2Vcc
  5. https://juejin.im/entry/5c8772eee51d456cda2e8099
  6. Android Development Expert Class
  7. Perfetto - System profiling, app tracing, and trace analysis
  8. Use Perfetto to analyze UI performance
  9. BufferQueue and Gralloc
  10. Android 15 Graphics Rendering Optimization

Zhihu Address

Since blog comments are inconvenient, if you want to like or communicate, please move to the Zhihu page of this article
Zhihu - Detailed Explanation of Android Choreographer-based Rendering Mechanism - Perfetto Version
Juejin - Detailed Explanation of Android Choreographer-based Rendering Mechanism - Perfetto Version

About Me & Blog

Below is a personal introduction and related links. I look forward to communicating with you all!

  1. Blogger Personal Introduction: Contains personal WeChat and WeChat group links.
  2. Blog Content Navigation: A navigation of personal blog content.
  3. Excellent Blog Articles Collected and Organized by Individuals - Must-Know for Android Performance Optimization: Welcome everyone to recommend yourself and recommend (WeChat private chat is fine)
  4. Android Performance Optimization Knowledge Planet: Welcome to join, thanks for support~

One person can go faster, a group of people can go further

Wechat Scan


CATALOG
  1. 1. Table of Contents
  • Perfetto Series Catalog
  • The Essence of Main Thread Execution
    1. 1. Evolution
    2. 2. Introducing Choreographer
  • Introduction to Choreographer
    1. 1. Choreographer’s Workflow from the Perspective of Perfetto
    2. 2. Choreographer’s Workflow
    3. 3. Interaction between Choreographer, RenderThread and BlastBufferQueue
      1. 3.1. BlastBufferQueue Working Principle
  • Source Code Analysis
    1. 1. Choreographer Initialization
      1. 1.1. Choreographer Singleton Initialization
      2. 1.2. Choreographer Constructor
      3. 1.3. FrameHandler
      4. 1.4. Choreographer Initialization Chain
    2. 2. Introduction to FrameDisplayEventReceiver
    3. 3. Vsync Registration in Choreographer
      1. 3.1. DisplayEventReceiver Communication Details with SurfaceFlinger
    4. 4. Logic of Choreographer Processing a Frame
      1. 4.1. Recording Frame Drawing Info
      2. 4.2. Executing Callbacks
    5. 5. Next Frame Vsync Request
      1. 5.1. ObjectAnimator Animation Driving Logic
    6. 6. Source Code Summary
  • APM and Choreographer
    1. 1. Use Perfetto for Advanced Monitoring
      1. 1.1. Use Perfetto to Track Choreographer
    2. 2. Use FrameInfo for Monitoring
    3. 3. Use SurfaceFlinger for Monitoring
    4. 4. Use SurfaceFlinger PageFlip Mechanism for Monitoring
    5. 5. Choreographer’s Own Dropped Frame Calculation Logic
    6. 6. BlockCanary
  • MessageQueue and Choreographer
    1. 1. An Example of SyncBarrier Used in Choreographer
  • Manufacturer Optimizations
    1. 1. Input Event Optimization
    2. 2. Background Animation Optimization
    3. 3. Frame Drawing Optimization
    4. 4. App Launch Optimization
    5. 5. Animation Callback Front-loading
    6. 6. Frame Interpolation
    7. 7. High Refresh Rate Optimization
  • Reference Materials
  • Zhihu Address
  • About Me & Blog