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:
- Input: Process touch events and key presses. Handling input first ensures that animations react instantly to the user's touch.
- Animations: Calculate the new positions of views, update physics simulations, or advance ValueAnimators.
- Insets: Handle window inset changes (e.g., the keyboard sliding up).
- Traversal/Drawing: Execute
measure,layout, anddrawpasses 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.