Skip to content

SurfaceFlinger 与显示系统深入详解

更新: 5/15/2026 字数: 0 字 时长: 0 分钟

1. 显示系统全景

1.1 从代码到屏幕的完整链路

你写的代码                    Framework                    系统服务                  硬件
─────────                   ──────────                   ──────────               ──────
canvas.bindrawRect()
View.onDraw()
  → RenderThread           → Surface                   → SurfaceFlinger         → 显示器
    (Skia/HWUI)             (BufferQueue)               (合成器)                  (屏幕)
    绘制到 Buffer             传递 Buffer                  合成所有 Layer            显示

一个屏幕上同时有很多东西:状态栏、导航栏、App 窗口、可能还有悬浮窗、Dialog。每个都是独立的 Surface,各自绘制。SurfaceFlinger 的工作就是把这些 Surface 叠在一起,合成最终的一帧画面,送给屏幕显示。

1.2 核心概念一览

Surface        = App 拿到的绘制入口(BufferQueue 生产者端的包装)
Buffer         = 实际的像素数据(共享内存,跨进程零拷贝)
BufferQueue    = App 和 SurfaceFlinger 之间的传送带(2-3 个 Buffer 循环使用)
Layer          = SurfaceFlinger 眼中的图层(BufferQueue 消费者端 + 合成信息)
Canvas         = 绘制指令接口(提供 bindrawRect/bindrawText 等方法)
SurfaceFlinger = 合成器 + 显示流水线总控
HWC            = 硬件合成器(显示控制器,直接驱动屏幕)
Choreographer  = App 端的帧调度器(按 VSYNC 节奏调度绘制)

2. Surface、Buffer、BufferQueue、Layer 的关系

2.1 四者的对应关系

App 端                    中间                SurfaceFlinger 端
──────                   ──────              ──────────────────
Surface ──→ Producer ──→ BufferQueue ──→ Consumer ──→ Layer
                         [Buffer A]
                         [Buffer B]
                         [Buffer C]

Surface 和 BufferQueue 是一对一:一个 Surface 对应一个 BufferQueue
BufferQueue 和 Buffer 是一对多:一个 BufferQueue 里有 2-3 个 Buffer
Layer 和 BufferQueue 是一对一:一个 Layer 持有一个 BufferQueue 的消费者端
Surface 和 Layer 是同一个东西的两端:App 端叫 Surface,SurfaceFlinger 端叫 Layer

2.2 Surface:App 的绘制入口

Surface 本身不存储像素数据,它只是 BufferQueue 生产者端的一个句柄:

java
class Surface {
    IGraphicBufferProducer mProducer;  // 核心字段,BufferQueue 的生产者接口

    // 软件绘制用
    public Canvas lockCanvas(Rect dirty) {
        // 1. dequeueBuffer() 从 BufferQueue 取一个空闲 Buffer
        // 2. 把 Buffer 的内存地址包装成 Canvas
        // 3. 返回 Canvas
    }

    public void unlockCanvasAndPost(Canvas canvas) {
        // 1. 解锁 Buffer
        // 2. queueBuffer() 提交到 BufferQueue
    }
}

一个 Activity 至少有一个 Window,一个 Window 有一个 Surface:

Activity
  └── PhoneWindow(Window 的实现类)
        └── DecorView(根 View)
              └── ViewRootImpl
                    └── Surface

什么时候会有多个 Surface?
  Activity 本身的窗口    → Surface 1
  弹出一个 Dialog        → Surface 2(独立窗口)
  SurfaceView            → Surface 3(独立于 View 树)

2.3 Canvas:绘制指令接口

Canvas 提供所有绘制方法,但它本身不存储像素,需要一个目标:

kotlin
// 情况 1:画到 Bitmap(内存中的像素数组,和 Surface 无关)
val bitmap = Bitmap.createBitmap(100, 100, ARGB_8888)
val canvas = Canvas(bitmap)
canvas.bindrawRect(...)  // 像素写入 bitmap 的内存

// 情况 2:画到 Surface 的 Buffer(屏幕显示用)
val canvas = surface.lockCanvas(dirty)
canvas.bindrawRect(...)  // 像素写入 Surface 背后的 GraphicBuffer
surface.unlockCanvasAndPost(canvas)

// 情况 3:画到 DisplayList(硬件加速,只记录命令不执行)
// View.onDraw(canvas) 中的 canvas 实际是 RecordingCanvas
canvas.bindrawRect(...)  // 不画像素,只记录命令
// 后续由 RenderThread 用 GPU 执行

Surface 和 Canvas 的关系:

Surface 管"画布的生命周期":取出 Buffer、提交 Buffer
Canvas  管"怎么画":bindrawRect、bindrawText、bindrawBitmap

Surface 提供 Buffer → Canvas 往 Buffer 里画 → Surface 提交 Buffer

2.4 Buffer(GraphicBuffer):实际的像素数据

Buffer 是一块共享内存:
1080 × 1920 × 4 字节 = 8.3MB 的连续内存

通过 gralloc HAL 分配,跨进程共享:

App 进程                    SurfaceFlinger 进程
    │                            │
    │    同一块物理内存            │
    └──→ [像素数据] ←────────────┘
         8.3MB

App 写入像素 → SurfaceFlinger 直接读取 → 零拷贝

2.5 BufferQueue:生产者-消费者模型

App 进程(生产者)                          SurfaceFlinger(消费者)
┌──────────────┐                          ┌──────────────┐
│              │   dequeueBuffer          │              │
│  RenderThread │ ←──────────────────────  │              │
│              │   (取一个空 Buffer)       │              │
│              │                          │              │
│  绘制内容到   │                          │              │
│  Buffer 上    │                          │              │
│              │                          │              │
│              │   queueBuffer            │              │
│              │ ──────────────────────→  │  acquireBuffer│
│              │   (提交填好的 Buffer)     │  (取出来合成)│
│              │                          │              │
│              │                          │  releaseBuffer│
│              │                          │  (用完归还)  │
└──────────────┘                          └──────────────┘

Buffer 的状态流转:

FREE        App 可以取走绘制
  ↓ dequeueBuffer(App 取走)
DEQUEUED    App 正在绘制
  ↓ queueBuffer(App 提交)
QUEUED      等待 SurfaceFlinger 取走
  ↓ acquireBuffer(SurfaceFlinger 取走)
ACQUIRED    SurfaceFlinger 正在合成
  ↓ releaseBuffer(SurfaceFlinger 用完)
FREE        回到起点

2.6 Layer:SurfaceFlinger 端的图层

App 看到的是 Surface,SurfaceFlinger 看到的是 Layer。同一个东西的两面:

Layer {
    BufferQueue consumer;     // 从这里取 Buffer
    Rect bounds;              // 在屏幕上的位置和大小
    int zOrder;               // 叠放顺序
    float alpha;              // 透明度
    Matrix transform;         // 变换(旋转、缩放)
    Region visibleRegion;     // 可见区域(被遮挡的部分不需要合成)
    int compositionType;      // HWC 合成还是 GPU 合成
}

屏幕上的实际场景:

三个窗口 = 三套完整链路:

状态栏(SystemUI 进程):
  Surface₁ → BufferQueue₁ [B₁ B₂ B₃] → Layer₁ (z=3, 顶部48dp)

App 窗口(你的 App 进程):
  Surface₂ → BufferQueue₂ [B₄ B₅ B₆] → Layer₂ (z=2, 中间区域)

导航栏(SystemUI 进程):
  Surface₃ → BufferQueue₃ [B₇ B₈ B₉] → Layer₃ (z=1, 底部48dp)

2.7 Surface 的创建过程

1. App → WMS:"我要创建一个窗口"
   → WMS 记录窗口信息(位置、大小、Z 轴)

2. WMS → SurfaceFlinger:"创建一个新的 Layer"
   → SurfaceFlinger 创建 Layer + BufferQueue
   → 返回 BufferQueue 的 Producer 端

3. WMS → App:把 Producer 传回
   → App 用它创建 Surface 对象
   → ViewRootImpl 持有这个 Surface
   → App 可以通过 Surface 绘制了

3. VSYNC 与 Choreographer

3.1 VSYNC 信号的来源

硬件 VSYNC 由显示控制器(HWC)产生,SurfaceFlinger 接收后通过 DispSync 分发:

显示控制器(HWC)
  │ 每 16.6ms 产生一次硬件 VSYNC 中断

HWC HAL
  │ 通过回调通知

SurfaceFlinger
  │ 交给 DispSync(软件 PLL,平滑抖动)

DispSync
  ├── 加偏移 1ms → VSYNC-APP → Choreographer(通知 App 开始绘制)
  └── 加偏移 5ms → VSYNC-SF  → SurfaceFlinger 合成线程(通知开始合成)

3.2 为什么要分成 VSYNC-APP 和 VSYNC-SF

❌ 没有偏移(同时开始):
  App 和 SurfaceFlinger 同时工作
  SF 开始合成时 App 还没画完,没有新 Buffer → 白忙

✅ 有偏移(错开):
  VSYNC-APP 先到 → App 先画
  VSYNC-SF 后到  → SF 后合成(此时 App 大概率已经画完了)
一个 VSYNC 周期内的时间关系:

0ms        1ms              5ms                    16.6ms
|          |                |                      |
硬件VSYNC  VSYNC-APP        VSYNC-SF               下一个硬件VSYNC
           |                |
           ▼                ▼
     Choreographer    SurfaceFlinger
     通知 App 开始画   通知 SF 开始合成

DispSync 还有省电优化:屏幕静止时关闭 VSYNC 接收,有人需要绘制时再重新开启。

3.3 Choreographer 的工作机制

Choreographer 是每个 App 主线程的帧调度器,在 VSYNC-APP 到来时按顺序执行这一帧的所有工作:

java
public final class Choreographer {
    // 五种回调队列,按优先级排列
    // [0] CALLBACK_INPUT       → 处理输入事件
    // [1] CALLBACK_ANIMATION   → 属性动画
    // [2] CALLBACK_INSETS_ANIMATION → 窗口动画
    // [3] CALLBACK_TRAVERSAL   → View 的 measure/layout/draw
    // [4] CALLBACK_COMMIT      → 帧提交后的收尾

    private final FrameDisplayEventReceiver mDisplayEventReceiver;
}

注册 VSYNC 回调

当 View 调用 invalidate() 时,最终走到 scheduleTraversals()

java
// ViewRootImpl
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;

        // 1. 插入同步屏障(让异步消息优先)
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

        // 2. 向 Choreographer 注册回调
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL,
            mTraversalRunnable,    // VSYNC 来了就执行这个
            null);
    }
}

Choreographer 收到注册后,向 SurfaceFlinger 请求下一个 VSYNC-APP:

java
private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        mFrameScheduled = true;
        mDisplayEventReceiver.scheduleVsync();
        // native 调用,注册到 SurfaceFlinger 的 DispSync
    }
}

注意:Choreographer 不是一直监听 VSYNC 的。只有当有回调需要执行时才注册,界面静止时不会收到 VSYNC,省电。

VSYNC-APP 到来时

java
// FrameDisplayEventReceiver 收到 VSYNC 信号
public void onVsync(long timestampNanos, ...) {
    // 发送异步消息到主线程(能穿过同步屏障)
    Message msg = Message.obtain(mHandler, this);
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, ...);
}

// 异步消息被处理时
void doFrame(long frameTimeNanos, int frame) {
    // 检查是否掉帧
    long skippedFrames = jitter / mFrameIntervalNanos;
    if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
        Log.i(TAG, "Skipped " + skippedFrames + " frames! "
            + "The application may be doing too much work on its main thread.");
    }

    // 按顺序执行五种回调
    doCallbacks(CALLBACK_INPUT, frameTimeNanos);      // 1. 输入事件
    doCallbacks(CALLBACK_ANIMATION, frameTimeNanos);   // 2. 动画
    doCallbacks(CALLBACK_INSETS_ANIMATION, ...);       // 3. 窗口动画
    doCallbacks(CALLBACK_TRAVERSAL, frameTimeNanos);   // 4. View 绘制
    doCallbacks(CALLBACK_COMMIT, frameTimeNanos);      // 5. 提交
}

为什么按这个顺序

1. INPUT 先执行 → 触摸事件先处理,可能触发 View 状态变化
2. ANIMATION 其次 → 计算动画值,更新 View 属性
3. TRAVERSAL 最后 → 此时所有状态变化都已发生,统一绘制一次
→ 一帧内先处理所有会导致 UI 变化的事件,最后统一绘制

4. 绘制流程:软件绘制 vs 硬件加速

4.1 软件绘制

全部在主线程完成,没有 RenderThread 参与:

java
// ViewRootImpl.drawSoftware()
Canvas canvas = mSurface.lockCanvas(dirty);
// dequeueBuffer() 从 BufferQueue 取 Buffer
// 把 Buffer 内存包装成 Canvas

mView.bindraw(canvas);
// 从根 View 递归调用每个 View 的 onDraw(canvas)
// CPU 逐像素绘制(Skia 软件渲染)
// 直接写入 Buffer 的像素数据

surface.unlockCanvasAndPost(canvas);
// queueBuffer() 提交到 BufferQueue
时间线:
  VSYNC-APP
  |[measure][layout][lock][CPU绘制所有像素──────────][unlock+post]|
  |←──────────── 全部在主线程,全部用 CPU ──────────────────────→|
  → 主线程被绘制阶段长时间占用,期间不能处理触摸、动画

4.2 硬件加速

主线程只记录绘制命令,RenderThread 用 GPU 执行:

主线程:
  View.bindraw(canvas)
  → canvas 实际是 RecordingCanvas
  → 不真正绘制,只记录命令到 DisplayList

  syncFrameState()
  → 把 DisplayList 同步给 RenderThread
  → 主线程空闲了,可以处理其他消息

RenderThread:
  → 遍历 DisplayList
  → 转换为 GPU 指令(OpenGL/Vulkan)
  → GPU 执行渲染,写入 GraphicBuffer
  → eglSwapBuffers() → queueBuffer() 提交到 BufferQueue
时间线:
  主线程:     [measure][layout][记录DisplayList][同步] → 空闲!
  RenderThread:                                [GPU渲染────][提交]
  → 主线程提前释放,可以处理其他消息
  → 主线程和 RenderThread 可以并行

4.3 硬件加速的问题

不是所有操作都支持

kotlin
canvas.bindrawPicture(picture)        // 不支持
paint.setMaskFilter(BlurMaskFilter()) // 部分支持
// 解决:对单个 View 关闭硬件加速
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

内存占用更大:DisplayList + GPU 纹理缓存 + 离屏缓冲,复杂页面可能额外占用 20-50MB。

离屏渲染

kotlin
view.alpha = 0.5f  // 半透明:需要先画完整个 View 到临时 Buffer,再整体设置 alpha
// 优化:用 RenderNode 属性代替
view.translationX = 100f  // ✅ 不触发离屏
view.scaleX = 1.5f        // ✅ 不触发离屏
view.rotation = 45f       // ✅ 不触发离屏

5. SurfaceFlinger 合成

5.1 两种合成方式

HWC 硬件合成(优先,省电)

显示控制器有多个叠加层(overlay plane)
每个 Layer 分配一个叠加层,硬件直接混合输出

Layer 0 (导航栏) ──→ Overlay Plane 0 ──┐
Layer 1 (App)    ──→ Overlay Plane 1 ──┼──→ 显示器(硬件实时混合)
Layer 2 (状态栏) ──→ Overlay Plane 2 ──┘

优点:省电,不占 GPU
限制:叠加层数量有限(通常 4-8 个)

GPU 合成(兜底方案)

Layer 太多或需要复杂效果时:
SurfaceFlinger 用 GPU 把多个 Layer 画到一个 Buffer
再把这个 Buffer 交给 HWC 显示

实际策略:混合合成

SurfaceFlinger 每帧询问 HWC 每个 Layer 的处理方式:
  状态栏:   OVERLAY(简单矩形,HWC 处理)
  App 窗口: OVERLAY(简单矩形,HWC 处理)
  圆角弹窗: CLIENT(需要 alpha 混合,GPU 处理)
  导航栏:   OVERLAY

5.2 SurfaceFlinger 的合成流程

cpp
void SurfaceFlinger::onMessageInvalidate() {
    // 1. 从每个 Layer 的 BufferQueue 取最新 Buffer
    for (Layer* layer : layers) {
        if (layer->hasNewBuffer()) {
            layer->acquireBuffer();
        }
    }

    // 2. 计算可见区域、遮挡关系
    computeVisibleRegions();

    // 3. 询问 HWC 合成策略
    hwc.prepare(layers);

    // 4. GPU 合成 CLIENT 类型的 Layer
    for (Layer* layer : clientLayers) {
        renderEngine->bindraw(layer);
    }

    // 5. 提交给 HWC 输出到显示器
    hwc.commit();

    // 6. 释放用完的 Buffer(归还给 BufferQueue)
    for (Layer* layer : layers) {
        layer->releaseBuffer();
    }
}

6. 双缓冲与三缓冲

6.1 双缓冲的问题

VSYNC:  V0          V1          V2          V3
App:    [画帧1──────────超时了──]
                                [画帧2────]
SF:                 [没有新帧!]  [合成帧1──]
Display:            [重复旧帧──]  [重复旧帧──][显示帧1]

V1 时刻:
  Buffer A: App 还在画(DEQUEUED)
  Buffer B: 显示器在用
  → 两个 Buffer 都被占着,App 画完帧 1 后没有空闲 Buffer
  → 必须等 V2 才能开始画帧 2 → 连续掉两帧

6.2 三缓冲的解决

VSYNC:  V0          V1          V2          V3          V4
App:    [画帧1──────────超时了──]
                    [画帧2────]  [画帧3────]
                    ↑ 用 Buffer C,不用等!
SF:                 [没有新帧]   [合成帧1──][合成帧2──]
Display:            [重复旧帧──][显示帧1──][显示帧2──]
                     ↑ 只掉一帧    ↑ 恢复了

V1 时刻:
  Buffer A: App 还在画帧 1
  Buffer B: 显示器在用
  Buffer C: 空闲!App 可以立即开始画帧 2
  → 只掉一帧就恢复,不会连续掉帧

三缓冲的代价:多一帧延迟(约多 16ms),换来更少的连续掉帧。

6.3 稳定状态下三个 Buffer 的分布

Buffer A: DEQUEUED(App 在画下一帧)
Buffer B: ACQUIRED(SurfaceFlinger 在合成)
Buffer C: 显示中(通过 HWC 扫描输出)
→ 三个阶段完全并行,流水线满载

7. SurfaceView 与 TextureView

7.1 普通 View

Activity 的所有普通 View 共享同一个 Surface,画在同一个 Buffer 上。

7.2 SurfaceView

SurfaceView 有自己独立的 Surface:

Layer 1: Activity 窗口的 Surface(View 树,SurfaceView 位置透明)
Layer 0: SurfaceView 的 Surface(Z 轴在 Activity 下面)

Activity 的 Surface 上 SurfaceView 的位置是透明的(挖了个洞)
透过这个洞看到下面 SurfaceView 的 Surface

优点:可以在子线程绘制,不影响主线程,适合视频/游戏
缺点:不在 View 树中,不能做 View 动画,不能被其他 View 遮挡

7.3 TextureView

TextureView 没有独立的 Surface,用 SurfaceTexture:

内部有一个 BufferQueue,视频/相机内容先画到这里
TextureView.onDraw() 时从中取出最新帧,作为 GPU 纹理
画到 Activity 的 Surface 上

优点:在 View 树中,可以做任何 View 动画
缺点:多一次 GPU 纹理拷贝,必须硬件加速,比 SurfaceView 多一帧延迟

8. 一帧的完整旅程

T=0.0ms   硬件 VSYNC 到来 → DispSync 收到

T=1.0ms   VSYNC-APP 触发
          Choreographer.doFrame()
            → CALLBACK_INPUT: 处理触摸事件
            → CALLBACK_ANIMATION: 计算动画值
            → CALLBACK_TRAVERSAL: performTraversals()
              → measure → layout → draw(记录 DisplayList)

T=6.0ms   主线程完成,DisplayList 同步给 RenderThread
          RenderThread 开始 GPU 渲染

T=5.0ms   VSYNC-SF 触发
          SurfaceFlinger 被唤醒

T=10.0ms  RenderThread 完成,queueBuffer() 提交到 BufferQueue

T=10.5ms  SurfaceFlinger acquireBuffer() 取出 Buffer
          与其他 Layer 一起交给 HWC 合成

T=15.0ms  合成完成,写入 framebuffer

T=16.6ms  下一个硬件 VSYNC
          显示器开始扫描新的 framebuffer
          用户看到画面更新

三级流水线

VSYNC:    V0          V1          V2          V3          V4

App:      [绘制帧1───]           [绘制帧3───]
                      [绘制帧2───]           [绘制帧4───]

SF:                   [合成帧1──]           [合成帧3──]
                                [合成帧2──]           [合成帧4──]

Display:                        [显示帧1──][显示帧2──][显示帧3──]

在 V2 这个时刻,三件事同时在发生:
  App 在绘制帧 3
  SurfaceFlinger 在合成帧 2
  显示器在显示帧 1

9. Choreographer 帧监控

kotlin
// 方案1:postFrameCallback 监控帧间隔
Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
    private var lastFrameTimeNanos = 0L

    override fun doFrame(frameTimeNanos: Long) {
        if (lastFrameTimeNanos != 0L) {
            val costMs = (frameTimeNanos - lastFrameTimeNanos) / 1_000_000
            if (costMs > 16) {
                Log.w("Jank", "帧间隔 ${costMs}ms,掉了 ${costMs / 16 - 1} 帧")
            }
        }
        lastFrameTimeNanos = frameTimeNanos
        Choreographer.getInstance().postFrameCallback(this)
    }
})

// 方案2:Looper Printer 监控消息处理耗时(BlockCanary 原理)
Looper.getMainLooper().setMessageLogging { msg ->
    if (msg.startsWith(">>>>> Dispatching")) {
        startTime = SystemClock.elapsedRealtime()
    }
    if (msg.startsWith("<<<<< Finished")) {
        val cost = SystemClock.elapsedRealtime() - startTime
        if (cost > 16) {
            // dump 主线程堆栈,看在做什么
        }
    }
}