CN

Android Systrace Basics - SurfaceFlinger Explained

Word count: 2.3kReading time: 14 min
2020/02/14
loading

This is the fifth article in the Systrace series, primarily providing a brief introduction to the workflow of SurfaceFlinger. It covers several important threads within SurfaceFlinger, including Vsync signal interpretation, app buffer display, and jank detection. Since Vsync has already been covered in Systrace Basics - Vsync Explained and Detailed Explanation of Android Rendering Mechanism Based on Choreographer, it won’t be discussed in detail here.

The purpose of this series is to view the overall operation of the Android system from a different perspective using Systrace, while also providing an alternative angle for learning the Framework. Perhaps you’ve read many articles about the Framework but can never remember the code, or you’re unclear about the execution flow. Maybe from Systrace’s graphical perspective, you can gain a deeper understanding.

Table of Contents

Series Article Index

  1. Introduction to Systrace
  2. Systrace Basics - Prerequisites for Systrace
  3. Systrace Basics - Why 60 fps?
  4. Android Systrace Basics - SystemServer Explained
  5. Systrace Basics - SurfaceFlinger Explained
  6. Systrace Basics - Input Explained
  7. Systrace Basics - Vsync Explained
  8. Systrace Basics - Vsync-App: Detailed Explanation of Choreographer-Based Rendering Mechanism
  9. Systrace Basics - MainThread and RenderThread Explained
  10. Systrace Basics - Binder and Lock Contention Explained
  11. Systrace Basics - Triple Buffer Explained
  12. Systrace Basics - CPU Info Explained
  13. Systrace Smoothness in Action 1: Understanding Jank Principles
  14. Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
  15. Systrace Smoothness in Action 3: FAQs During Jank Analysis
  16. Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
  17. Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
  18. Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
  19. Systrace Thread CPU State Analysis Tips - Runnable
  20. Systrace Thread CPU State Analysis Tips - Running
  21. Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep

Main Content

Version note (reviewed 2026-10-14): This article retains its 2020 Systrace images and analysis order. The two complete functions below match AOSP android-10.0.0_r1; the old Binder/BufferQueue path and HIDL track names are historical. Updates cover BLAST and commit/composite in android-16.0.0_r1, plus FrameTimeline on Android 12 (API 31) and later. The screenshots do not identify a system build, so similar method names cannot establish their exact source version.

Read this article together with Perfetto 8: Vsync and performance analysis. SurfaceFlinger’s main thread, BufferQueue, HWC, and app buffers still matter; combine them with FrameTimeline, buffer and Display/HWC tracks, and CPU scheduling.

Identify the frame and its expected presentation time, then follow app submission, GPU readiness, SurfaceFlinger latching, composition, and display submission. Neither exceeding 16.6 ms nor having a long slice alone establishes a jank cause.

The official SurfaceFlinger overview describes receiving layer content and state, coordinating composition, and passing the result to the display stack. Keep these qualifications in mind when reading the old diagram:

  1. Status bar, navigation bar, and app content are examples, not a fixed layer count. An app can use several surfaces; windows and layers are not simply one-to-one, and transactions can coordinate state changes.
  2. Display refresh rate is measured in Hz, content frame rate in fps. Neither 58–62 Hz nor a fixed 16.6 ms budget describes every device. App/SF scheduling involves prediction and phase offsets; a scheduled callback is not necessarily a hardware interrupt at that instant.
  3. Without a new buffer, a layer can retain old content. That differs from never having submitted a buffer; non-buffer layers such as solid-color layers also need separate treatment.
  4. Submission, GPU completion, and presentation are distinct events. Frames overlap in a pipeline. Producing faster than consumption can introduce backpressure, latency, or dropped buffers, depending on queue mode and scheduling.

The diagram includes both GPU composition and paths into display hardware. Repeated GPU boxes do not imply separate GPUs or that every layer passes through SurfaceFlinger’s GPU composition. We will follow App, BufferQueue, SurfaceFlinger, and HWComposer as dependencies, not as a strictly serial timeline across all threads and hardware.

App Section

See MainThread and RenderThread Explained for the app side. These screenshots use a traditional View/HWUI path:

The app submits image buffers through producer interfaces such as Surface. It does not store drawcalls in an image buffer for SurfaceFlinger to execute. Recording drawing operations, submitting GPU work, and writing image pixels are separate stages. “Finished drawing” in annotation 8 should mean buffer submission with synchronization dependencies, not guaranteed GPU completion.

Follow three interactions: the Vsync callback, dequeueBuffer, and queueBuffer.

Vsync Signal Reception and Processing

Choreographer rendering explains frame requests and callbacks. The callback starts input, animation, and traversal work; its timestamp can differ from when the thread actually gets CPU time.

The green F symbols are the old viewer’s frame-analysis markers, not proof of on-time presentation. Correlate the buffer and display frame as well.

RenderThread’s dequeueBuffer

dequeueBuffer gives the producer a writable buffer slot and synchronization information. In the old images, the consumer side resides in SurfaceFlinger and the app requests it through Binder. Buffers may be reused; each call need not allocate or copy an entire image.

App-side Systrace:
-w1249

SurfaceFlinger-side Systrace:
-w826

An SF Binder thread handles the request separately from main-thread composition. Obtaining or writing the buffer may depend on available slots and release fences; inspect thread states to understand waits.

Modern path: Android 16 BLASTBufferQueue creates a local BufferQueue, acquires its buffers, and passes buffers, fences, and layer state to SurfaceFlinger through SurfaceControl transactions. On this path, a dequeueBuffer call need not make an App→SF Binder transaction. BufferQueues do not all live inside the SF process.

RenderThread’s queueBuffer

queueBuffer submits the producer’s buffer with metadata such as fences and timestamps. The pictured eglSwapBuffersWithDamageKHR → queueBuffer chain is an OpenGL ES path, not a universal chain for Vulkan, software Canvas, or every producer.

App-side Systrace:
-w1165

The image also shows a waiting for GPU completion track after submission. Explicit synchronization allows buffers and fences to move ahead of unfinished GPU work. A queueBuffer return proves neither GPU completion nor presentation; a waiting interval is not continuous GPU execution time either.

SurfaceFlinger-side Systrace:
-w1295

The layer counter represents queued items awaiting consumption, not all allocated buffers or a count of GPU-ready images. Acquisition decreases it and queuing increases it. The acquisition on the left and subsequent submission on the right are different events; the latter buffer was not consumed before it was submitted.

These steps correspond to the producer side of the diagram:
-w410

BufferQueue Section

Triple Buffer Explained covers queues and slot states. A BufferQueue connects a producer and consumer. One process may use several queues, and both ends may reside in the same process; there is no one-queue-per-visible-process rule.

The old path uses the app as producer and SF as consumer. BLAST places the consumer differently, but these four operations still explain the responsibilities:

  1. dequeueBuffer(): obtain a slot and a buffer matching dimensions, format, and usage, allocating or reusing as needed. Satisfy the returned synchronization dependency before writing.
  2. queueBuffer(): submit the buffer and acquire fence. Asynchronous writing may still be in progress.
  3. acquireBuffer(): acquire a queued item. Satisfy its acquire fence before reading, or pass the dependency to downstream hardware.
  4. releaseBuffer(): return the slot with a release fence. The next writer must still wait for earlier readers to finish.

These operations pass references/handles and synchronization information, not four pixel copies. The diagram does not determine buffer limits or whether a queue can replace older items.

SurfaceFlinger Section

Workflow

On the old path, SF processes transactions and candidate buffers, then decides whether composition is needed. Unchanged content does not require work on every Vsync. The image shows handlePageFlip → latchBuffer, followed by handleMessageRefresh:

-w1296

The code below matches AOSP android-10.0.0_r1 SurfaceFlinger.cpp. onMessageReceived is shortened: it omits backpressure, display-mode changes, refreshNeeded checks, and signalRefresh, among other logic. It is not a complete implementation to copy. The next two functions retain the implementation from that tag.

  1. MessageQueue::INVALIDATE: process transactions, attempt layer updates, and conditionally schedule REFRESH.
  2. MessageQueue::REFRESH: enter handleMessageRefresh.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void SurfaceFlinger::onMessageReceived(int32_t what) NO_THREAD_SAFETY_ANALYSIS {
ATRACE_CALL();
switch (what) {
case MessageQueue::INVALIDATE: {
// ... omitted ...
bool refreshNeeded = handleMessageTransaction();
refreshNeeded |= handleMessageInvalidate();
// ... omitted ...
break;
}
case MessageQueue::REFRESH: {
handleMessageRefresh();
break;
}
}
}

// handleMessageInvalidate implementation:
bool SurfaceFlinger::handleMessageInvalidate() {
ATRACE_CALL();
bool refreshNeeded = handlePageFlip();

if (mVisibleRegionsDirty) {
computeLayerBounds();
if (mTracingEnabled) {
mTracing.notify("visibleRegionsDirty");
}
}

for (auto& layer : mLayersPendingRefresh) {
Region visibleReg;
visibleReg.set(layer->getScreenBounds());
invalidateLayerStack(layer, visibleReg);
}
mLayersPendingRefresh.clear();
return refreshNeeded;
}

// handleMessageRefresh implementation; most SF work starts here:
void SurfaceFlinger::handleMessageRefresh() {
ATRACE_CALL();

mRefreshPending = false;

const bool repaintEverything = mRepaintEverything.exchange(false);
preComposition();
rebuildLayerStacks();
calculateWorkingSet();
for (const auto& [token, display] : mDisplays) {
beginFrame(display);
prepareFrame(display);
doDebugFlashRegions(display, repaintEverything);
doComposition(display, repaintEverything);
}

logLayerStats();

postFrame();
postComposition();

mHadClientComposition = false;
mHadDeviceComposition = false;
for (const auto& [token, displayDevice] : mDisplays) {
auto display = displayDevice->getCompositionDisplay();
const auto displayId = display->getId();
mHadClientComposition =
mHadClientComposition || getHwComposer().hasClientComposition(displayId);
mHadDeviceComposition =
mHadDeviceComposition || getHwComposer().hasDeviceComposition(displayId);
}

mVsyncModulator.onRefreshed(mHadClientComposition);

mLayersWithQueuedFrames.clear();
}

Read handleMessageRefresh by responsibility:

  1. Preparation: preComposition(), rebuildLayerStacks(), and calculateWorkingSet().
  2. Per-display work: beginFrame(), prepareFrame(), debug flashing, and doComposition().
  3. Follow-up: logLayerStats(), postFrame(), postComposition(), and composition-type, Vsync modulation, and queue-state updates.

The final group includes synchronization and accounting after submission; returning from these methods does not establish completed presentation. Their CPU slice durations are not direct GPU timings.

Android 16 entry points: In android-16.0.0_r1, commit() updates layer snapshots, processes transactions, and decides whether composition is needed. composite() prepares arguments and invokes CompositionEngine::present, which prepares and presents outputs. Some HWC present calls can be offloaded to other threads, with results awaited afterward. Follow these entry points and the device’s actual tracks/source instead of requiring the two old Message names. This outline omits display-mode, backpressure, and multi-display branches.

There are far too many display-system details to cover them all here. If you’re just getting familiar with the flow, start with SurfaceFlinger’s main responsibilities. If this is the part of the system you work on, follow the matching source version and work through those branches and dependencies.

Dropped Frames (Jank)

First identify which frame of which layer missed its expected presentation time, then trace its dependencies:

  1. Check the refresh mode, target frame rate, and frame identity. Static content or content running below the display rate need not be recomposed on every Vsync.
  2. When an expected update is delayed, inspect SF scheduling, transactions, latch conditions, and backpressure from earlier frames. Follow CPU scheduling, Binder, and GPU/HWC fences to establish the actual wait.
  3. SF may compose while retaining this app’s old buffer or updating only other layers. A queue count of zero can mean an item was just acquired or has not been submitted yet. It does not assign blame to the app; downstream backpressure may delay production too.
  4. On Android 12 (API 31) and later, where captured data supports it, use FrameTimeline to connect app SurfaceFrames with SF DisplayFrames. Compare Expected/Actual timelines, present type, and jank type, then inspect thread/hardware evidence. Actual Timeline is not CPU Running time, and absent tracks do not prove an absence of jank.

See Triple Buffer: Jank Detection for the legacy Systrace view. These cropped screenshots cannot establish a complete jank rate.

HWComposer Section

The Hardware Composer HAL negotiates composition using layer state and device capabilities:

  1. Client composition: SurfaceFlinger uses RenderEngine and sends the client target to HWC. Android 16 RenderEngine supports GL/Vulkan backend selection; client does not universally mean GLES.
  2. Device composition: the device HWC implementation handles the layers. Overlays are common, but plane counts, scaling, rotation, blending, and formats depend on hardware and drivers. Android 4.4 and later do not guarantee four planes.
  3. Hardware composition may save GPU work, while static-content reuse, bandwidth, and blending costs also affect the choice. Client composition is not itself a performance fault; layer counts cannot directly predict dropped frames.

Interface version: The image below shows the historical HIDL android.hardware.graphics.composer@2.1-service. Android 13 introduced AIDL composer3, and HIDL 2.1–2.4 are deprecated. Android 13 did not require every vendor to switch immediately, so verify the actual device HAL.

-w1149

SF’s executeCommands call crosses Binder into HWC, with Present/Commit/ATOMIC work visible below. The selected 642 μs is the marked interval, not an established GPU execution time or presentation-completion time. Hardware may continue processing or waiting on fences after HWC returns.

This is the latter part of the original diagram:
-w563

Distinguish acquire fences (dependencies before reading), release fences (safe reuse of earlier buffers), and display present fences (presentation synchronization). For example, NDK API 29 transaction stats expose separate present and previous-release fences. Devices without present-fence support can return an invalid value; that is not zero latency. A HAL return or SF latch timestamp is not the present fence’s signal time.

HWC waits can affect later frames; see System-side Jank Causes. Read Android P Hardware Composition HWC2 and its series as Android 9/P historical material, not as the current interface layout.

References

  1. Android P Hardware Composition HWC2 (historical)
  2. Android P Graphics Display System (historical series)
  3. SurfaceFlinger and WindowManager
  4. SurfaceFlinger Analysis Document (historical)

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!”

  1. Blogger Intro: Includes personal WeChat and WeChat group links.
  2. Blog Content Navigation: A guide for my blog content.
  3. Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome to recommend projects/articles.
  4. 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.

Scan WeChat QR Code

CATALOG
  1. 1. Table of Contents
  • Series Article Index
  • Main Content
    1. 1. App Section
      1. 1.1. Vsync Signal Reception and Processing
      2. 1.2. RenderThread’s dequeueBuffer
      3. 1.3. RenderThread’s queueBuffer
    2. 2. BufferQueue Section
    3. 3. SurfaceFlinger Section
      1. 3.1. Workflow
      2. 3.2. Dropped Frames (Jank)
    4. 4. HWComposer Section
  • References
  • About Me && Blog