Surfaces and SurfaceFlinger
A Surface is a queue of buffers, not a canvas. SurfaceFlinger is the compositor that decides which buffers become pixels. Between them they explain BLAST, overlays, and why SurfaceView beats TextureView for video.
Most Android developers meet Surface as an object they get from a SurfaceHolder or a SurfaceView, and assume it
is something you draw on. It is not. A Surface is an endpoint of a queue: producers put buffers in, the compositor
takes them out. Everything else about it follows from that.
TL;DR#
- A Surface is a producer's handle to a ; it is not a bitmap and not a canvas.
- SurfaceFlinger is the compositor. It collects every visible layer on vsync and produces the frame the display scans out.
- The Hardware Composer lets the display scan out several layers directly as overlays, avoiding GPU composition.
- SurfaceView gives a view its own separate layer; TextureView renders into the normal view hierarchy.
- BLAST ties a window's first buffer to the transaction that creates or resizes the window, removing a first-frame flicker that used to be routine.
Mental model#
Think of a slide projector with several carousels.
Each carousel is a Surface: it holds slides (buffers) and feeds them in order. The person loading a carousel is a producer — an app, a video decoder, a camera.
The operator is SurfaceFlinger. On each beat they take the current slide from every carousel, stack them in the right order and project the combination.
The projector's own optics are the Hardware Composer: sometimes it can project several slides at once without the operator having to combine them first.
Where the analogy breaks#
Slides are images. Buffers come with fences, timestamps and a state machine, and a carousel can be empty — a producer that has not queued anything yet — in which case the layer simply is not shown. And unlike a projector, a layer's composition can change every frame: what was an overlay last frame may need GPU composition this frame because a translucent dialog appeared on top of it.
The big picture#
Text description of this diagram
An exploded view of the layers on a phone screen: wallpaper, the app window, a translucent dialog, the status bar, the navigation bar and the keyboard. Opaque, non-overlapping layers can be handed to the Hardware Composer as overlays. A translucent or overlapping layer forces SurfaceFlinger to composite with the GPU into a client target.
Each plate in that diagram is a Surface with its own BufferQueue and its own producer. The status bar's buffer comes from SystemUI; the keyboard's from a completely separate app's process. SurfaceFlinger does not care who produced a buffer, only what the layer's transform, alpha and blend mode say to do with it.
Deep dive#
A Surface is a queue endpoint#
Surface (native) and Surface (Java) are the producer-side handle. When you call lockCanvas or when the
RenderThread renders, the producer dequeues a buffer, writes into it, and queues it.
The consumer side is either SurfaceFlinger or another app — this is what makes SurfaceTexture possible: it is a
consumer you can hand to a producer so that the producer's output becomes a texture you can draw elsewhere.
The queue's lifecycle is the four-state machine you have already seen: free, dequeued, queued, acquired. What matters for composition is that the consumer acquires at a specific moment — on vsync — so a buffer queued after that moment waits a full frame.
What SurfaceFlinger actually does#
On each vsync, SurfaceFlinger:
- Collects the layers that have a queued buffer and are visible.
- Computes each layer's visible region, applying transforms, crops and alpha.
- Asks the Hardware Composer which layers it can handle directly.
- Composites the remainder with the GPU into a client target.
- Presents the resulting set of layers to the display.2
Step 3 is the one that determines cost. A typical phone's HWC supports a handful of overlays. If the visible stack fits, composition is essentially free and the GPU stays idle. If it does not, SurfaceFlinger must render some or all of the stack into a client target, which costs GPU time, memory bandwidth and power.3
Layers, z-order and the window connection#
SurfaceFlinger's layers and WindowManager's windows are different objects kept in sync. WindowManager decides the
window's position, size and z-order; SurfaceFlinger receives a Layer with a transform and a z value through a
transaction.4
Transactions are the mechanism for changing a layer atomically: position, crop, alpha, buffer and visibility all change together, so a resize never shows an intermediate state. This is also where BLAST comes in.
BLAST: the first-buffer problem#
Historically, creating a window and giving it content were two separate steps. The window could become visible before its first buffer arrived, showing a flash of background — most visibly when launching an app or rotating.
BLAST attaches the buffer to the transaction that creates or updates the layer, through
, so the window appears with
content already in place.5 In current AOSP the app-side object is
BLASTBufferQueue; SurfaceFlinger learns about the buffer at the same time it learns about the geometry.
The practical effect is fewer visible flashes on launch and rotation, and a simpler contract: "here is the window and its first frame, together".
SurfaceView vs TextureView#
Both let you show a producer's output — a video decoder, a camera, a GL renderer — inside a view hierarchy. They differ in where the output lives.
| SurfaceView | TextureView | |
|---|---|---|
| Layer | a separate SurfaceFlinger layer behind the window | a texture inside the app's window |
| Composition | can be an HWC overlay | always GPU-composited into the app's buffer |
| Transform / alpha | limited; the layer is separate | full View transforms, alpha, animation |
| Cost | lower, especially for video | higher; the video is drawn as part of the app's frame |
| First frame | historically later; BLAST improved it | immediate |
The trade is straightforward. SurfaceView gives the producer its own layer, which the display can often scan out directly — ideal for full-screen video and games. TextureView puts the producer's output into your window's buffer, which is what you need if you want to animate, scale or fade it like any other view.67
Fences, and why buffers are not just memory#
A buffer carries a fence: a kernel object that signals when the GPU has finished writing to it. The producer queues a buffer with a fence instead of waiting for the GPU to finish; the consumer waits on the fence before reading. The sync framework manages these fences across processes.8
Without fences, every handoff would need a CPU-side wait, and the pipeline would serialise. With them, the GPU can still be writing the last few scanlines while the compositor is already preparing to present.
What a layer is, exactly#
A SurfaceFlinger layer is a record of:
- A buffer (or nothing, if the producer has not queued one).
- A transform: position, scale, rotation, crop.
- A blend mode and alpha.
- A z-order within its display.
- A visible region, computed from the crop and the display bounds.
The layer is not the window. WindowManager owns the window — its frame, its insets, its focus state — and it pushes changes to the layer through a transaction. A window can exist with no layer at all, and a layer can outlive the window that created it during a transition.
Transactions make resizes atomic#
A transaction is a batch of layer changes applied together. This matters more than it sounds: a resize changes the layer's buffer size, its crop and its position, and if those changes were applied separately the display could show a frame at the wrong scale, or a buffer cropped to the old size.
Transactions are also the mechanism behind SurfaceView's setFixedSize and setZOrderMediaOverlay — both are
properties applied at the next transaction boundary, not immediately.
Why the compositor's cost is invisible to the app#
An app cannot see how many layers are visible or how many overlay planes the display has. It only sees its own frame time. When SurfaceFlinger falls back to client composition, the app's frame is unaffected — its buffer was produced on time — but the overall frame is late because composition took too long.
This is the practical reason the frame's "owner" is the system rather than the app. FrameTimeline attributes a missed deadline to the stage that caused it, which may be a stage the app does not control.
Multiple displays and virtual displays#
SurfaceFlinger composes per display. A phone with an external monitor has two compositions running, and a virtual
display — screen recording, casting, or a Presentation on a secondary screen — is a third. Each has its own
refresh rate, its own layer stack and its own composition decisions.
For an app, this is mostly invisible, except that a Presentation runs in a separate window on a separate display,
with its own insets and its own lifecycle. Screen recording captures the composited output, which is why it captures
the status bar and the cursor but not a secure window — secure layers are excluded from virtual displays by the
system, not by the app.
How it evolved#
| API | Version | What changed |
|---|---|---|
| 1 | 1.0 |
|
| 3 | 1.5 Cupcake |
|
| 9 | 2.3 Gingerbread |
|
| 11 | 3.0 Honeycomb |
|
| 12 | 3.1 Honeycomb |
|
| 16 | 4.1 Jelly Bean |
|
| 20 | 4.4W KitKat (Wear) |
|
| 21 | 5.0 Lollipop |
|
| 24 | 7.0 Nougat |
|
| 26 | 8.0 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 33 | 13 Tiramisu |
|
| 36 | 16 Baklava |
|
Under the hood#
- Surface.cppthe producer-side Surface: dequeue, queue, and the buffer cache
- SurfaceFlinger.cppthe composition loop on vsync
- the layer→composition pipeline and output handling
- BLASTBufferQueue.cppthe transaction-plus-buffer path
- SurfaceTexture.cppthe consumer that turns a producer into a texture
Curiosity corner#
- The compositor does not know what your app is. It sees layers, transforms and buffers. That is why a SurfaceView can show a video from a hardware decoder without the app ever touching a pixel.
- Overlay counts are a hardware property. Two phones with the same SoC can differ in how many layers they can scan out, because the display driver and panel differ.
- A layer can exist with no buffer. The window is there; it simply produces nothing, and the layer below shows through.
Try it#
adb shell dumpsys SurfaceFlinger --listExample output
+ Layer 0x7f8a2c00 (com.example.app/com.example.app.MainActivity#0)
z= 0 pos=(0,0) size=(1080,2400) crop=(0,0,1080,2400)
transform=0 alpha=1.0 composition=HWCadb shell dumpsys SurfaceFlingerExample output
Display 0 HWC layers:
type | handle | name
---------+--------+---------------------------
HWC | 0x1 | com.android.systemui/StatusBar
HWC | 0x2 | com.example.app/MainActivity
CLIENT | 0x3 | com.example.app/DialogCheck yourself#
Check yourself
At 120 Hz, how much time does one frame have before it misses the display's deadline?
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsSurfaceTexturesource.android.comaccessed 2026-10-27↩
- docsSurfaceFlinger and WindowManagersource.android.comaccessed 2026-10-27↩
- docsHardware Composer (HWC)source.android.comaccessed 2026-10-27↩
- docsLayers and displayssource.android.comaccessed 2026-10-27↩
- docsUnsignaled buffer latchsource.android.comaccessed 2026-10-27↩
- docsSurfaceView and GLSurfaceViewsource.android.comaccessed 2026-10-27↩
- docsTextureViewsource.android.comaccessed 2026-10-27↩
- docsSync frameworksource.android.comaccessed 2026-10-27↩
- docsSurface and SurfaceHoldersource.android.comaccessed 2026-10-27↩
- docsBufferQueue and grallocsource.android.comaccessed 2026-10-27↩