Series Catalog:
- Overview of Android Performance Patterns
- Android Performance Patterns: Render Performance
- Android Performance Patterns: Understanding Overdraw
- Android Performance Patterns: Understanding VSYNC
- Android Performance Patterns: Profile GPU Rendering
Technical review (2026-10-13): The introduction and nine diagrams are retained from the 2015 video. The text corrects the fixed-60Hz assumption, tearing explanation, and “VSync copies the back buffer” description, and adds modern frame-rate hints and analysis tools.
Original video introduction:
Unbeknownst to most developers, there’s a simple hardware design that defines everything about how fast your application can draw things to the screen.
You may have heard the term VSYNC - VSYNC stands for vertical synchronization and it’s an event that happens every time your screen starts to refresh the content it wants to show you.
Effectively, VSYNC is the product of two components: Refresh Rate (how fast the hardware can refresh the screen), and Frames Per Second (how fast the GPU can draw images). In this video, Colt McAnlis walks through each of these topics and discusses where VSYNC (and the 16ms rendering barrier) comes from, and why it’s critical to understand if you want a silky smooth application.
Basic Concepts
To understand smoothness and jank, distinguish app frame production, system composition, and display presentation. VSync (vertical synchronization) coordinates their timing. It is not the mathematical product of refresh rate and FPS, nor an operation that renders the app’s content for it. See the AOSP VSync explanation.
The original video mainly uses 60Hz, corresponding to refresh intervals of about 16.67ms. At 90Hz and 120Hz, the intervals are about 11.11ms and 8.33ms. These are not separate full budgets for every thread; actual deadlines depend on pipeline scheduling, discussed below.
Before diving into VSYNC, we need to understand two concepts:
Refresh Rate
Refresh rate, measured in Hz, describes the display’s refresh cadence. Hardware limits the supported modes, but the active rate need not be fixed: the system may choose it based on content, power policy, and other windows. Do not assume 60Hz is the default on every Android device. See Android’s frame-rate guidance.

Frame Rate (FPS)
Frame rate, measured in fps, counts frames per second, but the measurement point matters. Frames submitted by the app, completed by the GPU, and shown as distinct images to the user may produce different counts. FPS is also distinct from the GPU’s clock frequency in MHz.
A higher rate is not always the goal. Interactive motion benefits from consistent pacing and low latency; video has its own content frame rate. Unneeded work costs power. Showing each frame of a steady 30fps video twice on a 60Hz display can be normal playback, rather than a dropped app frame.

How It Works
The app produces images, SurfaceFlinger organizes composition, and the display system presents the result. Each column below is a simplified content-to-display illustration. CPU work, queues, composition, and synchronization are omitted; it is not a full Android timeline and does not mean a static page must redraw continuously.

Display and content rates may differ; how their timing is coordinated is what matters.
Production faster than refresh: distinguish skipped presentation from tearing
The 100fps / 75Hz diagram illustrates that not every produced frame is necessarily shown. Which frames are discarded or queued, and where the producer is throttled, depend on queueing and presentation policy. The rate mismatch alone does not establish tearing.

For example, take a photo, rotate by 5 degrees, take another, and join their upper and lower halves:


The halves depict different moments and do not line up. This illustrates tearing: without correct synchronization between scanout and updates, a scan can contain parts of different frames. For example, an image might be modified while it is being read, or the display source might switch too early. Different frame and refresh rates are not sufficient to cause tearing; incorrect synchronization can also tear at equal average rates.

Android coordinates reads and writes through buffer ownership, synchronization fences, and presentation scheduling. With BufferQueue, a producer dequeues an available buffer and submits it; the consumer acquires and eventually releases it. BufferQueue passes handles without copying pixel contents. Buffer counts depend on producer/consumer constraints and the rendering path, rather than one universal double-, triple-, or quadruple-buffer rule.
A queued buffer is not necessarily finished on the GPU. Synchronization fences express completion dependencies: a reader must respect outstanding writes, and a producer must respect outstanding reads before reuse. Composition may still produce a new output buffer; a queue that does not copy pixels does not imply that the entire pipeline never copies anything.

The Back Buffer / Frame Buffer illustration is a teaching model for avoiding concurrent overwrite of an image being displayed. It does not mean every VSync copies the back buffer into the front buffer. VSync coordinates app, SurfaceFlinger, and display timing. Those stages can overlap and wake at different offsets; the GPU does not simply finish each frame and wait for the same hardware interrupt.
Normal View animation and traversal are scheduled through the framework’s Choreographer, available since Android 4.1 / API 16. Apps typically use animation APIs or View.postOnAnimation() instead of simulating VSync with sleep(16).
Refresh faster than production: distinguish intended repetition from missed deadlines
Without an available new frame, the display system can keep presenting previous content. The original 58fps / 60Hz figure is only illustrative. How long each new image stays visible, and whether it misses its expected presentation time, tells us more about stutter than average FPS alone.

An intentionally lower content rate differs from an animation unexpectedly missing an intended update. The latter can cause stutter or latency, but should not generally be called flicker. GPU overload is not the only possible cause: main-thread blocking, scheduling delays, rendering, queueing, and composition can all affect the result.
Surface.setFrameRate(float, int), available since Android 11 / API 30, declares the content’s intended rate. It helps the system select a display mode, but does not guarantee a switch, produce frames for the app, or throttle the app to that rate. Normal UI still follows framework scheduling; video and custom renderers must also manage presentation timing.
On Android 12 / API 31+, use Perfetto FrameTimeline to compare expected and actual timelines and jank reasons, then correlate captured thread, GPU, and SurfaceFlinger evidence for the same frame. Detailed fence and scheduling tracks depend on the device and recording configuration. Choreographer callback counts are not counts of actually presented frames, and one code section exceeding 16ms is not a complete diagnosis.
Perf Matters
Keep calm, profile your code, and always remember, Perf Matters.
About Me && Blog
Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”
- Blogger Intro
- Blog Content Navigation: A guide for my blog content.
- Curated Excellent Blog Articles - Android Performance Optimization Must-Knows
- Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.
One walks faster alone, but a group walks further together.
