The Six Layers That Turn an Android View.draw Call Into Screen Pixels
Android Graphics System Panorama
The Android graphics system spans applications, system services, and hardware, with many components and confusing terminology. This article walks through the system from application to hardware: six layers, how layers collaborate, and how typical scenarios plug in. It covers only the big picture, not implementation details.
1. What Problems Does the Graphics System Solve
An app UI, a video, and the system status bar all run on screen simultaneously — different sources, different rhythms — and must be composited into a single output frame. The graphics system handles several things at once:
- Provides a unified output path for different content sources such as Canvas, OpenGL ES, Vulkan, Camera, and video decoders.
- Shares large blocks of graphics memory among applications, system services, and hardware.
- Manages multiple independent image streams concurrently.
- Composites multiple windows, videos, and system UI into the final picture.
- Outputs to a physical screen, virtual display, or other image consumer.
- Balances throughput, latency, memory, power consumption, color correctness, and content security.
2. Six Layers from Application to Hardware
Use a layered perspective to build a holistic understanding: each layer solves one category of problem, and layers connect through well-defined interfaces. View.draw and Canvas, which app developers interact with most often, sit in the upper-middle section of the layers — they are the entry point for drawing intent. The bottom layers — GPU, HWC, Display — determine how pixels actually reach the screen. The middle layers progressively translate intent into screen-ready graphics data.
Application side
1 App UI Layer — View · ViewGroup · Drawable · Window
│ View.draw
▼
2 Drawing Interface Layer — Canvas · DisplayList/RenderNode · Picture
│ RenderThread playback
▼
3 Rendering Engine Layer — Skia · OpenGL ES · Vulkan
│ Write to Surface
▼
System side
4 Buffer Channel Layer — Surface · BufferQueue · GraphicBuffer · Fence
│ Submit Buffer
▼
5 Composition Coordination Layer — WindowManager · SurfaceControl · SurfaceFlinger · Layer
│ Deliver composition
▼
Hardware side
6 Hardware Display Layer — Gralloc · GPU · HWC · Display
Layer 1: App UI Layer
Describes interface structure and content, answering "what to draw and how to lay it out."
- View/ViewGroup: The interface is built from a View tree. The system performs three traversal passes on the View tree: measure, layout, and draw.
- Drawable: Reusable graphics resources (bitmaps, vectors, state selectors, etc.), held and drawn by Views.
- Window: The application-side window abstraction; one Window typically corresponds to one displayable area.
This layer does not care how pixels are generated; it only produces a description of what the interface should look like.
Layer 2: Drawing Interface Layer
Expresses View drawing intent as a replayable sequence of drawing operations, decoupled from the specific rendering backend. Under hardware acceleration, canvas.drawText inside onDraw does not immediately paint to the screen; it is first recorded as a command and handed to the render thread for later execution.
- Canvas: The drawing command interface, providing operations for drawing geometry, text, images, etc. Two working modes: in software mode, it draws directly into Bitmap memory; in hardware mode, it records commands into a DisplayList.
- DisplayList/RenderNode: Under hardware acceleration,
View.drawrecords commands via Canvas into a DisplayList (the modern implementation is called RenderNode), which is handed to RenderThread for asynchronous playback, avoiding main-thread blocking. - Picture: A set of drawing commands that can be recorded and replayed as a whole.
Canvas occupies a somewhat special position: it is both the interface called by the application and the input to the rendering engine. Here it is placed in the "abstraction and recording of commands" layer; execution is delegated to the rendering engine.
Layer 3: Rendering Engine Layer
Turns drawing commands or graphics API calls into pixels. The first two layers only describe what to draw; this layer turns commands into pixels.
- Skia: A cross-platform 2D graphics engine providing text, image, path, and other drawing capabilities. It is the rendering engine used by Android's hardware-accelerated 2D rendering (HWUI). Skia itself can choose different rendering backends: software rasterization (CPU), or via OpenGL ES/Vulkan to call the GPU.
- OpenGL ES / Vulkan: Low-level graphics APIs targeting the GPU, used to submit drawing tasks to the GPU. Games and applications requiring high-performance graphics use them directly.
- RenderThread: The rendering thread under hardware acceleration, which replays DisplayLists and calls Skia → GLES/Vulkan → GPU to generate pixels.
The pixels produced by this layer are ultimately written into the Surface at Layer 4.
Layer 4: Buffer Channel Layer
Responsible for the transfer and synchronization of graphics data between components.
- Surface: The unified entry point for producers to submit images; Canvas, GLES, Vulkan, Camera, and MediaCodec can all submit results to a Surface.
- ANativeWindow: The native abstraction behind Surface, the way C/C++ code connects to BufferQueue; EGLSurface is the wrapper for GLES to connect to Surface.
- BufferQueue: A buffer pool and queue between producer and consumer, coordinating buffer allocation, enqueue, and dequeue.
- GraphicBuffer/HardwareBuffer: The framework representation of graphics memory, carrying one frame or one layer of image data.
- Fence: Expresses when an asynchronous hardware task (such as GPU rendering or display readout) completes, preventing reads before data is ready.
The data flow model: the producer dequeues a Buffer from BufferQueue → writes content → submits it back to the queue → the consumer acquires and uses it → returns it when done.[^1]
Layer 5: Composition Coordination Layer
The status bar, app windows, and navigation bar are each drawn independently and must ultimately be stacked into a single picture. This layer manages windows and layers, compositing multiple layers into the final image.
- WindowManager: A system service that manages window creation, properties, and z-order relationships.
- SurfaceControl: A handle for operating on Layers, used to configure a layer's position, size, z-order, transform, and visibility.
- SurfaceFlinger: The system composition service, which collects Buffers from all visible Layers and composites them according to z-order and properties.
- Layer: The unit of layer processed during system composition; one Surface corresponds to one Layer.
The defining characteristic of this layer is the separation of control flow and data flow: WindowManager/SurfaceControl handles layer configuration, while SurfaceFlinger handles buffer composition.
Layer 6: Hardware Display Layer
Provides graphics memory, compute power, and physical output. The logic of the preceding five layers ultimately executes on the hardware at this layer.
- Gralloc: The graphics memory allocator, which allocates shareable graphics memory for different hardware units such as GPU, display controller, and Camera — key to zero-copy transfer.
- GPU: Executes the drawing tasks submitted by the rendering engine and can also participate in composition.
- HWC (Hardware Composer): Display controller hardware that can efficiently composite multiple Layers. SurfaceFlinger hands Layers to HWC; HWC decides which Layers the hardware can composite and which exceed its capabilities and must fall back to GPU composition.
- Display: The physical screen or virtual display, the final output target.
3. Relationships Between Layers
The layering provides a vertical understanding. This section ties them together along two main threads: data flow (how an image goes from drawing commands to screen pixels) and control flow (how windows and layers are configured). The two threads intersect at different layers, with the Producer/Consumer model running throughout.
Data Flow: From Drawing Commands to Screen Pixels
View tree traversal
│ View.draw
▼
Canvas records commands ──► DisplayList/RenderNode
│ RenderThread playback
▼
Skia / GLES / Vulkan ──► Generate pixels
│ Write
▼
Surface ──► BufferQueue ──► SurfaceFlinger acquires Buffer
│ Composite
▼
HWC / GPU composition
│
▼
Display presents to screen
A normal app window follows this entire path: the application side (Layers 1–3) generates pixels and writes them into a Surface; the system side (Layers 4–6) composites the Buffers from multiple Surfaces and presents them to the screen. Scenarios such as Camera and video decoding, acting as producers, can plug in directly at Layers 3–4, bypassing the View system of Layers 1–2.[^2]
Control Flow: Window and Layer Configuration
Alongside the data flow runs a parallel control flow:
WindowManager / SurfaceControl
│ Configure Layer properties (position, z-order, transform, visibility)
▼
SurfaceFlinger ──► Composite visible Layers according to configuration ──► HWC/Display
Window is the application-side window abstraction at Layer 1; its actual management happens at Layer 5: WindowManager creates and configures Layers via SurfaceControl, and SurfaceFlinger determines composition order and effects based on that configuration. When the interface changes, the control flow (window/layer property updates) and data flow (redrawing) work in concert.
The Producer/Consumer Model Throughout
A unified model runs through Layers 3–5:
- Producer: Rendering engines (Skia/GLES/Vulkan), Camera, MediaCodec, etc., which produce graphics Buffers.
- Consumer: SurfaceFlinger (composite to screen), SurfaceTexture (convert to texture for further processing), ImageReader (image analysis), MediaCodec Encoder (encoding), etc.
- Intermediary: BufferQueue connects the two ends; Buffers circulate within it.
The same producer can connect to different consumers: a Camera's Buffer can go to SurfaceFlinger for on-screen preview, to ImageReader for analysis, and to an encoder for recording — the only difference is which two ends BufferQueue connects.
Decoupling Achieved by Layering
Each layer solves a category of decoupling problem:
- Application from hardware: The app only draws to a Surface, without needing to know the GPU model or screen hardware.
- Commands from execution: DisplayList separates drawing intent from rendering timing; the main thread records, RenderThread executes asynchronously.
- Production from consumption: BufferQueue allows producers and consumers with different speeds to cooperate; multiple buffering improves throughput.
- Memory sharing: Buffers allocated by Gralloc are passed between GPU, HWC, and CPU with zero copy; Fence guarantees safe asynchronous read/write.
- Replaceable backends: Skia can switch between software, GLES, and Vulkan backends without changing the upper-layer interface.
4. Typical Graphics Scenarios
Using the layered perspective to view different scenarios, the differences lie in which layer they plug into and who the consumer is:
| Scenario | Producer | Consumer / Output |
|---|---|---|
| Normal app window | HWUI/Skia | SurfaceFlinger |
| Direct Canvas drawing | CPU/Skia Canvas | SurfaceFlinger |
| OpenGL ES game | GLES/EGL | SurfaceFlinger |
| Vulkan game | Vulkan Swapchain | SurfaceFlinger |
| Camera preview | Camera HAL | SurfaceFlinger or SurfaceTexture |
| Video playback | MediaCodec Decoder | SurfaceFlinger or SurfaceTexture |
| Video encoding | Camera/GLES | MediaCodec Encoder |
| Image analysis | Camera/Codec | ImageReader |
| Screen recording | SurfaceFlinger/Virtual Display | MediaCodec Encoder |
For each scenario, you must clarify: who the producer is, which Surface it writes to, which two ends BufferQueue connects, who ultimately consumes it, and whether it is composited to the screen. SurfaceView, SurfaceTexture, and TextureView are connection schemes for different scenarios, not separate architectural layers.[^2]
5. Common Questions
The role of multiple buffering With single buffering, after the producer finishes drawing a frame, it must wait for the display to finish reading before drawing the next frame; during that time the GPU/CPU are idle. Multiple buffering lets the producer draw to a back buffer while the display reads from the front buffer, forming a pipeline that improves throughput and reduces jank. Triple buffering goes further: even if one frame hasn't finished rendering, the producer always has a buffer available to write to.
How BufferQueue handles mismatched production and consumption speeds The queue acts as a reservoir between the two ends: when production is fast, buffers accumulate up to the limit, at which point the producer is blocked or frames are dropped; when production is slow, the consumer reuses the previous frame. Multiple buffering lets the two ends with mismatched speeds operate at their own rhythms without blocking each other.
Why avoid copying entire frames of pixels A single frame contains a lot of data (1080p RGBA is about 8MB); copying is time-consuming and wastes bandwidth and power. Android uses Gralloc to allocate graphics memory that can be shared and accessed by GPU, CPU, and HWC. Components pass memory handles rather than the data itself, achieving zero-copy transfer.
The performance and power trade-off between HWC and GPU HWC is display controller hardware; compositing multiple layers with it uses less power than using the GPU: it is dedicated hardware, does not occupy the GPU, and can even let the GPU sleep. SurfaceFlinger prioritizes HWC composition; only when HWC capabilities are exceeded (complex transforms, too many layers, special blend modes) does it fall back to the GPU, which then both consumes power and occupies application rendering resources.
The role of Fence Graphics tasks (GPU rendering, display readout, Camera output) are asynchronous hardware operations; after the CPU submits them, it cannot synchronously wait for completion. Fence is a kernel synchronization primitive that marks "when this buffer has finished being written or read." Without it, a consumer might read a half-finished product, or a producer might overwrite a buffer still being read. It uses signaling instead of busy-waiting, guaranteeing correct ordering in asynchronous hardware pipelines.
Behavioral differences between SurfaceView and TextureView SurfaceView owns an independent Surface and is handed directly to SurfaceFlinger for composition as a separate layer; it does not participate in the View system. Performance is good, but transforms and transparency are limited, and gaps can appear when layered with Views. TextureView treats its content as a texture: it first writes to a Buffer, then composites it as a texture into the View's drawing result, fully integrated into the View hierarchy. It supports arbitrary transforms and transparency, at the cost of one extra texture composition step. The essential difference: independent layer composition versus texture integration into the View hierarchy.
Why some videos cannot be screenshotted DRM-protected content (such as Widevine L1) is decoded in secure hardware and output to protected buffers marked as protected, which the GPU or CPU are not allowed to read. Taking a screenshot requires reading the framebuffer; SurfaceFlinger marks such layers as secure and skips them during screenshot capture. This is a content security mechanism.
Where differences in Buffer size at the same resolution come from It depends on pixel format and allocation method. At the same resolution, RGBA_8888 (4 bytes per pixel) is twice the size of RGB_565 (2 bytes per pixel); whether frame buffer compression (such as AFBC) is enabled, and differences in alignment and layout caused by different usage flags, all change the actual memory footprint.
The difference between physical and virtual displays A physical Display is connected to real screen hardware, outputs via HWC, and is directly visible to the user. A virtual Display has no real hardware; it outputs the composition result to a Surface that can be consumed by an application, used for screen recording, casting, etc. The composition result is not presented directly to a screen but sent out as a buffer.
The division of labor between WindowManager and SurfaceFlinger WindowManager manages window semantics and policy (creation, properties, z-order, focus); SurfaceFlinger manages pixel-level composition (collecting buffers, compositing, output). Separated, WM does not need to understand composition, and SF does not need to understand window policy. SF runs in a separate process; composition has strong real-time requirements and cannot be blocked by window management logic. The two are bridged by SurfaceControl: WM configures layers, SF executes composition.
Summary
From View.draw to screen pixels, you pass through six layers — App UI, Drawing Interface, Rendering Engine, Buffer Channel, Composition Coordination, and Hardware Display — threaded through by two main lines: data flow and control flow. With this framework in mind, when you dive into specialized topics on BufferQueue, SurfaceFlinger, or HWC, each piece can be mapped to a specific layer and main thread.
[^1]: Android Graphics Overview [^2]: Graphics Architecture
Top 1 from juejin.cn, machine-translated. The original thread is authoritative.
Great article, thanks for sharing.