Android Performance

Detailed Explanation of Android Rendering Mechanism Based on Choreographer

Word count: 2.9kReading time: 18 min
2019/10/22
loading

This article introduces Choreographer, a class that app developers rarely encounter but is crucial in the Android Framework rendering pipeline. It covers the background of Choreographer‘s introduction, an overview, source code analysis, its relationship with MessageQueue, its use in APM, and optimization ideas from mobile manufacturers.

Choreographer was introduced to coordinate with Vsync, providing a stable timing for handling Messages in app rendering. When Vsync arrives, the system adjusts the Vsync signal period to control the timing of drawing operations for each frame. Most phones currently have a 60Hz refresh rate (16.6ms). To match this, the system sets the Vsync period to 16.6ms, waking Choreographer every period to perform app drawing—this is its primary role. Understanding Choreographer also helps developers grasp the principles of frame execution and deepens knowledge of Message, Handler, Looper, MessageQueue, Measure, Layout, and Draw.

This is the eighth article in the Systrace series, providing a brief introduction to Choreographer within Systrace.

The purpose of this series is to view the overall operation of the Android system from a different perspective using Systrace, while also providing an alternative angle for learning the Framework. Perhaps you’ve read many articles about the Framework but can never remember the code, or you’re unclear about the execution flow. Maybe from Systrace’s graphical perspective, you can gain a deeper understanding.

Series Article Index

  1. Introduction to Systrace
  2. Systrace Basics - Prerequisites for Systrace
  3. Systrace Basics - Why 60 fps?
  4. Android Systrace Basics - SystemServer Explained
  5. Systrace Basics - SurfaceFlinger Explained
  6. Systrace Basics - Input Explained
  7. Systrace Basics - Vsync Explained
  8. Systrace Basics - Vsync-App: Detailed Explanation of Choreographer-Based Rendering Mechanism
  9. Systrace Basics - MainThread and RenderThread Explained
  10. Systrace Basics - Triple Buffer Explained
  11. Systrace Basics - CPU Info Explained
  12. Systrace Smoothness in Action 1: Understanding Jank Principles
  13. Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
  14. Systrace Smoothness in Action 3: FAQs During Jank Analysis
  15. Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
  16. Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
  17. Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
  18. Systrace Thread CPU State Analysis Tips - Runnable
  19. Systrace Thread CPU State Analysis Tips - Running
  20. Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep

Essence of the Main Thread Mechanism

Before discussing Choreographer, let’s clarify the essence of the Android main thread: it’s effectively a Message processing loop. All operations, including frame rendering, are sent as Messages to the main thread’s MessageQueue. The queue processes a message and waits for the next, as shown:

MethodTrace Illustration

Systrace Illustration

Evolution

In Android versions before Vsync, Messages for rendering frames had no gaps; as soon as one frame finished drawing, the next frame’s Message was processed. This led to unstable frame rates that fluctuated unpredictably:

MethodTrace Illustration

Systrace Illustration

The bottleneck here was dequeueBuffer. Because screens have a fixed refresh cycle and SurfaceFlinger (SF) consumes Front Buffer at a constant rate, SF also consumes App Buffers at a constant rate. Apps would get stuck at dequeueBuffer, resulting in unstable buffer acquisition and frequent jank.

For users, a stable frame rate is a better experience. For example, a stable 50 fps feels smoother than one that fluctuates wildly between 60 and 40 fps.

Android’s evolution introduced Vsync + TripleBuffer + Choreographer, aiming to provide a stable frame rate mechanism that allows software and hardware to operate at a synchronized frequency.

Introducing Choreographer

Choreographer coordinates with Vsync to provide a stable Message processing timing. When Vsync arrives, the system adjusts the signal period to control when each frame is drawn. Vsync typically uses a 16.6ms (60 fps) period to match the 60Hz refresh rate of most phone screens. Every 16.6ms, the Vsync signal wakes Choreographer to perform app drawing. If the app renders within this window every cycle, it achieves 60 fps, appearing very smooth.

With 90Hz screens becoming common, the Vsync period has dropped from 16.6ms to 11.1ms, requiring faster task completion and higher performance. See: A New Smooth Experience: A Talk on 90Hz.

Choreographer Overview

Choreographer acts as a bridge in the Android rendering pipeline:

  1. Upper side: It receives and processes app update messages and callbacks, handling them collectively when Vsync arrives. This includes Input events, Animation logic, and Traversal (measure, layout, draw), while also detecting dropped frames and recording callback durations.
  2. Lower side: It requests and receives Vsync signals, handling event callbacks (via FrameDisplayEventReceiver.onVsync) and scheduling Vsync (FrameDisplayEventReceiver.scheduleVsync).

Choreographer is a vital “utility player.” The Choreographer + SurfaceFlinger + Vsync + TripleBuffer stack ensures Android apps run at a stable frame rate (20, 90, or 60 fps), reducing the discomfort of frame fluctuations.

Understanding Choreographer also clarifies the fundamentals of frame execution and deepens knowledge of Message, Handler, Looper, MessageQueue, Input, Animation, Measure, Layout, and Draw. Many APM tools combine Choreographer (using FrameCallback + FrameInfo) + MessageQueue (using IdleHandler) + Looper (with custom MessageLogging) to optimize performance monitoring.

While diagrams are helpful, I prefer using Systrace and MethodTrace to show system operations (CPU, SurfaceFlinger, SystemServer, Apps) from left to right. Familiarity with the code allows you to map Systrace directly to actual phone behavior. This article will primarily use Systrace.

Choreographer Workflow from a Systrace Perspective

Using Launcher scrolling as an example, here is a complete preview of the App process. Each green frame represents a frame that finally appears on the screen.

  1. Each gray and white bar is a Vsync period (16.6ms).
  2. Frame processing flow: Vsync signal callback -> UI Thread -> RenderThread -> SurfaceFlinger (not shown).
  3. UI Thread and RenderThread together complete an app’s frame rendering. The rendered buffer is sent to SurfaceFlinger for composition, then displayed.
  4. Launcher scrolling frames are very short (Ui Thread + RenderThread time), but still wait for the next Vsync to begin processing.

Zooming into the UI Thread for a single frame, we see Choreogrepher‘s position and how it organizes the frame:

Choreographer Workflow

  1. Initialize Choreographer
    1. Initialize FrameHandler, binding it to the Looper.
    2. Initialize FrameDisplayEventReceiver to communicate with SurfaceFlinger for Vsync.
    3. Initialize CallBackQueues.
  2. SurfaceFlinger’s appEventThread sends Vsync; Choreographer calls FrameDisplayEventReceiver.onVsync, entering the main processing function doFrame.
  3. Choreographer.doFrame calculates dropped frames.
  4. Processes the first callback: input.
  5. Processes the second callback: animation.
  6. Processes the third callback: insets animation.
  7. Processes the fourth callback: traversal.
    1. traversal-draw synchronizes UIThread and RenderThread data.
  8. Processes the fifth callback: commit?
  9. RenderThread processes draw commands and sends them to the GPU.
  10. Calls swapBuffer to submit to SurfaceFlinger for composition. (The buffer isn’t truly finished until the CPU completes; newer Systrace versions use GPU fences to mark this).

After step 1, the system loops through steps 2-9.

Here is the corresponding MethodTrace (detailed view following):

Now, let’s examine the implementation via source code.

Source Code Analysis

The following analysis covers important logic, omitting less relevant parts like Native interactions with SurfaceFlinger.

Choreographer Initialization

Singleton Initialization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// Get current thread Looper
Looper looper = Looper.myLooper();
......
// Construct Choreographer object
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 rendering the next frame
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC: // Request Vsync
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK: // Process Callback
doScheduleCallback(msg.arg1);
break;
}
}
}

Choreographer Initialization Chain

During Activity startup, after onResume, Activity.makeVisible() is called, then addView(), eventually calling:

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

FrameDisplayEventReceiver Overview

FrameDisplayEventReceiver handles Vsync registration, requests, and reception. It inherits from DisplayEventReceiver and has three key methods:

  1. onVsync – Vsync signal callback
  2. run – Executes doFrame
  3. scheduleVsync – Requests the 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
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
......
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}

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

Vsync Registration in Choreographer

The FrameDisplayEventReceiver.onVsync inner class receives Vsync callbacks and notifies the UIThread. It achieves this by listening to file descriptors initialized 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);
}

nativeInit uses a BitTube (essentially a socket pair) to pass and request Vsync events. When SurfaceFlinger receives Vsync, its appEventThread sends the event through BitTube to DisplayEventDispatcher, which then triggers Choreographer.FrameDisplayEventReceiver.onVsync, starting frame drawing:

Logic for Processing One Frame

The core logic resides in Choreographer.doFrame. FrameDisplayEventReceiver.onVsync posts itself, and its run method calls doFrame:

android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}

doFrame performs three main tasks:

  1. Calculates dropped frame logic.
  2. Records frame drawing information.
  3. Executes CALLBACK_INPUT, CALLBACK_ANIMATION, CALLBACK_INSETS_ANIMATION, CALLBACK_TRAVERSAL, and CALLBACK_COMMIT.

Dropped Frame Logic

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

Dropped frame detection uses the difference between when Vsync was supposed to arrive (start_time) and when doFrame actually executes (end_time). This jitter represents the delay, or dropped frames:

Actual Systrace example of dropped frames:

Note: This method calculates jitter for the previous frame, meaning some dropped frames might be missed.

Recording Frame Drawing Info

FrameInfo records key node times (visible via dumpsys gfxinfo). hwui records the remaining parts.

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
// 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;

doFrame records times from Vsync to markPerformTraversalsStart:

1
2
3
4
5
6
7
8
9
10
11
12
13
void doFrame(long frameTimeNanos, int frame) {
......
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// Process CALLBACK_INPUT Callbacks
mFrameInfo.markInputHandlingStart();
// Process CALLBACK_ANIMATION Callbacks
mFrameInfo.markAnimationsStart();
// Process CALLBACK_INSETS_ANIMATION Callbacks
// Process CALLBACK_TRAVERSAL Callbacks
mFrameInfo.markPerformTraversalsStart();
// Process CALLBACK_COMMIT Callbacks
......
}

Executing Callbacks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void doFrame(long frameTimeNanos, int frame) {
......
// Process CALLBACK_INPUT Callbacks
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// Process CALLBACK_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// Process CALLBACK_INSETS_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
// Process CALLBACK_TRAVERSAL Callbacks
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// Process CALLBACK_COMMIT Callbacks
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
......
}

Input Callback Stack

input callback typically 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 eventually reach DecorView.dispatchTouchEvent for standard event distribution:

Animation Callback Stack

Commonly used via View.postOnAnimation:

1
2
3
4
5
6
7
8
9
public void postOnAnimation(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, action, null);
} else {
getRunQueue().post(action);
}
}

Used for operations like startScroll and Fling:

Here is a fling animation stack:

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 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
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// Post SyncBarrier to prioritize
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}

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

doTraversal TraceView Example

Requesting the Next Frame’s Vsync

Continuous operations like animations or scrolling need a stable frame output. Every doFrame triggers another Vsync request as needed. invalidate and requestLayout also trigger requests.

ObjectAnimator Animation 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);
......
}
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
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
......
}

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};

Animation uses the FrameCallback interface to request the next Vsync for every frame:

Source Code Summary

  1. Choreographer is a thread singleton bound to a Looper (usually the app’s main thread).
  2. DisplayEventReceiver is an abstract class whose JNI part creates an IDisplayEventConnection. Vsync signals from AppEventThread are passed to onVsync.
  3. DisplayEventReceiver.scheduleVsync requests a Vsync interrupt before drawing UI.
  4. Choreographer.FrameCallback is called every Vsync, greatly assisting animation implementation by providing fixed timing.
  5. Choreographer invokes callbacks set via postCallback when Vsync arrives. Five types are defined:
    1. CALLBACK_INPUT: Input events.
    2. CALLBACK_ANIMATION: Animation logic.
    3. CALLBACK_INSETS_ANIMATION: Insets animation.
    4. CALLBACK_TRAVERSAL: Measure, layout, draw.
    5. CALLBACK_COMMIT: trimMemory and frame monitoring.
  6. ListView item initialization happens within input or animation depending on context.
  7. CALLBACK_INPUT and CALLBACK_ANIMATION execute before CALLBACK_TRAVERSAL because they modify view properties.

APM and Choreographer

Given its position, Choreographer is central to performance monitoring. Analysts use its FrameCallback and FrameInfo interfaces:

  1. FrameCallback.doFrame callbacks.
  2. FrameInfo monitoring (adb shell dumpsys gfxinfo <packagename> framestats).
  3. SurfaceFlinger monitoring (adb shell dumpsys SurfaceFlinger --latency).
  4. SurfaceFlinger PageFlip mechanism (adb service call SurfaceFlinger 1013).
  5. Choreographer‘s dropped frame calculation.
  6. BlockCanary based on Looper.

Using FrameCallback.doFrame

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

Usage:

1
Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );

TinyDancer uses this to calculate FPS.

Using FrameInfo

adb shell dumpsys gfxinfo <packagename> framestats output example:

1
2
3
4
5
6
7
Window: StatusBar
Total frames rendered: 1562
Janky frames: 361 (23.11%)
...
---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,

Using SurfaceFlinger

Data is in nanoseconds since boot. Each command returns 128 lines of frame data:

  1. Refresh period.
  2. Col 1: App drawing time.
  3. Col 2: Vsync timestamp before submission to hardware.
  4. Col 3: Hardware acceptance time (completion).

Jank is calculated when the jankflag (based on ceil((C - A) / refresh-period)) changes between lines.

Using SurfaceFlinger PageFlip

1
2
3
4
5
6
7
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
mFlinger.transact(1013, data, reply, 0);
final int pageFlipCount = reply.readInt();
...
mFps = (float) (frames * 1e9 / duration);

BlockCanary

Monitors performance by recording time before and after every Looper message:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void loop() {
...
for (;;) {
...
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

Asynchronous messages can bypass SyncBarriers. A Barrier blocks all subsequent synchronous messages until removed. This is used in Choreographer to prioritize Traversal messages:

SyncBarrier Example in Choreographer

scheduleTraversals posts a SyncBarrier:

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

doTraversal removes it:

1
2
3
4
5
6
7
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}
CATALOG
  1. 1. Series Article Index
  2. 2. Essence of the Main Thread Mechanism
    1. 2.1. Evolution
    2. 2.2. Introducing Choreographer
  3. 3. Choreographer Overview
    1. 3.1. Choreographer Workflow from a Systrace Perspective
    2. 3.2. Choreographer Workflow
  4. 4. Source Code Analysis
    1. 4.1. Choreographer Initialization
      1. 4.1.1. Singleton Initialization
      2. 4.1.2. Choreographer Constructor
      3. 4.1.3. FrameHandler
      4. 4.1.4. Choreographer Initialization Chain
    2. 4.2. FrameDisplayEventReceiver Overview
    3. 4.3. Vsync Registration in Choreographer
    4. 4.4. Logic for Processing One Frame
      1. 4.4.1. Dropped Frame Logic
      2. 4.4.2. Recording Frame Drawing Info
      3. 4.4.3. Executing Callbacks
    5. 4.5. Requesting the Next Frame’s Vsync
      1. 4.5.1. ObjectAnimator Animation Logic
    6. 4.6. Source Code Summary
  5. 5. APM and Choreographer
    1. 5.1. Using FrameCallback.doFrame
    2. 5.2. Using FrameInfo
    3. 5.3. Using SurfaceFlinger
    4. 5.4. Using SurfaceFlinger PageFlip
    5. 5.5. BlockCanary
  6. 6. MessageQueue and Choreographer
    1. 6.1. SyncBarrier Example in Choreographer