Advanced AOSP Subsystems
3 min read

Choreographer

Overview

The Choreographer is a framework-level class responsible for orchestrating the timing of animations, input handling, and drawing. It ensures that application rendering is perfectly synchronized with the display hardware's refresh rate, achieving smooth, tear-free visuals.

Choreographer Purpose: Frame Pacing

Before the introduction of Choreographer (in Android 4.1 "Project Butter"), applications often relied on arbitrary timers to schedule animations. This led to "jank" because the app might draw a frame slightly too late to make the physical display refresh, resulting in dropped frames.

Choreographer solves this by standardizing the rendering heartbeat. It dictates exactly when the UI thread should wake up, process input, calculate animations, and submit rendering commands. This strict pacing ensures that the application has the maximum possible time to compute a frame before the display requires it.

VSYNC Signal Consumption

The heartbeat of Choreographer is driven by the VSYNC (Vertical Synchronization) signal.

Hardware VSYNC is an electrical pulse from the physical screen indicating that it has finished scanning the previous frame and is ready for the next. SurfaceFlinger distributes this VSYNC signal to all visible applications. Choreographer registers a listener for this signal via the DisplayEventReceiver.

When the VSYNC pulse arrives, the operating system wakes the application's main thread and triggers the Choreographer.

The doFrame() Callback

The core method executed on every VSYNC is doFrame(long frameTimeNanos). This method guarantees a strict sequence of operations to ensure consistency. The sequence is explicitly ordered:

  1. Input: Process touch events and key presses. Handling input first ensures that animations react instantly to the user's touch.
  2. Animations: Calculate the new positions of views, update physics simulations, or advance ValueAnimators.
  3. Insets: Handle window inset changes (e.g., the keyboard sliding up).
  4. Traversal/Drawing: Execute measure, layout, and draw passes on the View hierarchy, culminating in hardware-accelerated rendering commands being sent to the GPU.
// Simplified view of Choreographer's dispatch loop
void doFrame(long frameTimeNanos) {
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_INSETS, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
}

postFrameCallback

Developers can interact directly with Choreographer to execute custom logic perfectly aligned with the display refresh rate. This is heavily used by game engines and custom animation frameworks.

By calling Choreographer.getInstance().postFrameCallback(), you schedule a FrameCallback to be executed during the animation phase of the next VSYNC.

Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        // Calculate physics for this frame
        updateGameLogic(frameTimeNanos);
        
        // Schedule the callback again for the next frame
        Choreographer.getInstance().postFrameCallback(this);
    }
});

To debug UI thread performance and observe Choreographer's behavior, use the Systrace or Perfetto tools:

adb shell setprop debug.choreographer.framerate true
adb shell dumpsys gfxinfo <package_name> framestats

These commands provide deep insights into how quickly an app is executing its doFrame loop relative to the VSYNC signal.