Surface 与 SurfaceFlinger:Window 的绘制表面是如何创建的?

好,我们继续往下走。前面几章我们把 WindowManager、ViewRootImpl 和 WMS 之间的交互理清楚了。但有个关键问题一直悬着:应用层画的东西,到底是怎么跑到屏幕上去的?

这中间有个核心角色——Surface。说白了,它就是一块画布。应用往上面画,系统负责把画布内容显示出来。而 SurfaceFlinger,就是那个负责合成的“大管家”。

Surface 到底是什么?

我个人习惯把 Surface 理解成一块共享内存的封装。它不是一个普通的 Java 对象,它背后连接着 BufferQueue 的生产者端。

你想想看,应用进程和系统服务进程是隔离的。应用画了图,数据怎么传给 SurfaceFlinger?靠的就是 Surface 内部的 IGraphicBufferProducer 这个 Binder 接口。

核心要点:Surface 是应用端的“画笔”,SurfaceFlinger 是系统端的“画框”。两者通过 BufferQueue 进行数据交换。

创建流程:从 WMS 到 Surface

我记得刚接触这部分源码时,最困惑的就是:Surface 到底是谁创建的?是应用自己 new 的吗?不是。

流程是这样的:

  1. WMS 收到 addWindow 请求,开始为窗口分配 Surface。
  2. WMS 调用 Session.relayout(),最终走到 WindowStateAnimator.createSurfaceLocked()
  3. 这里会调用 SurfaceControl.openTransaction()new SurfaceControl()
  4. SurfaceControl 的构造方法里,会通过 SurfaceComposerClient 向 SurfaceFlinger 发起请求。
  5. SurfaceFlinger 创建一个 Layer 对象,并返回一个 SurfaceControl 给 WMS。
  6. WMS 再从 SurfaceControlcopy 出一个 Surface,传给应用进程。

嗯,这里要注意:应用拿到的 Surface,其实是 SurfaceControl 的一个“副本”。真正的控制权在 WMS 手里。

SurfaceControl 与 Surface 的区别

对比项 SurfaceControl Surface
所属进程 System Server(WMS) 应用进程
主要功能 控制 Layer 属性(位置、大小、透明度) 提供 Canvas 或 EGL 绘制接口
内部持有 SurfaceFlinger 的 Layer 句柄 IGraphicBufferProducer
谁创建 SurfaceFlinger 从 SurfaceControl copy 而来

我曾经在项目里遇到过一个问题:应用侧调了 Surface.lockCanvas(),但画上去的内容死活不显示。排查了半天,发现是 WMS 那边把 SurfaceControl 的 visibility 设成了 false。你看,控制权根本不在应用手里。

BufferQueue:生产与消费的桥梁

Surface 内部维护了一个 BufferQueue 的生产者端。每次应用调用 lockCanvas() 时,其实是从 BufferQueue 中 dequeue 一块 Buffer。画完之后 unlockCanvasAndPost()queue 回去

而 SurfaceFlinger 作为消费者,会定期从 BufferQueue 中 acquire 已经填好的 Buffer,进行合成并送显。

小技巧:如果你在开发中遇到“掉帧”问题,可以关注一下 BufferQueue 的 dequeueBuffer 耗时。如果经常超过 16ms,说明 CPU/GPU 负载过高了。

SurfaceFlinger 的合成策略

SurfaceFlinger 拿到多个 Layer 的 Buffer 后,怎么把它们合成一帧?这里有三种策略:

  • GPU 合成(OpenGL ES):把所有 Layer 渲染到一个 Framebuffer 上。灵活,但耗电。
  • HWC 合成(Hardware Composer):让硬件直接叠加多个 Layer。省电,但 Layer 数量有限制。
  • 混合模式:部分 Layer 用 HWC,部分用 GPU。

我记得在 Android 8.0 之前,SurfaceFlinger 默认用 GPU 合成。后来为了省电,改成了优先尝试 HWC。如果 HWC 搞不定(比如 Layer 有透明区域重叠),再 fallback 到 GPU。

一张图看懂整个流程

下面我用 SVG 画了张图,把 Surface 从创建到显示的全链路串起来。你仔细看看,应该能一目了然。

应用进程 ViewRootImpl Surface (生产者) System Server WMS SurfaceControl SurfaceFlinger Layer BufferQueue (消费者) Binder Binder Buffer 流转(共享内存) 屏幕显示 合成后送显 Binder 通信 共享内存

创建 Surface 时的关键代码

我们来看一下 WMS 中创建 SurfaceControl 的核心逻辑。这部分代码在 WindowStateAnimator.java 里:

// WindowStateAnimator.createSurfaceLocked()
SurfaceControl.openTransaction();
try {
    // 设置 Surface 的 flags,比如是否支持硬件加速
    int flags = SurfaceControl.HIDDEN;
    if (mWindow.mAttrs.alpha != 1.0f) {
        flags |= SurfaceControl.FX_SURFACE_DIM;
    }

    // 创建 SurfaceControl,这会触发 SurfaceFlinger 创建 Layer
    mSurfaceControl = new SurfaceControl(
        mSession.mSurfaceSession,
        mService.mContext.getOpPackageName(),
        mWin.mAttrs.getTitle().toString(),
        width, height, format, flags,
        mWin.mFrameNumber,
        new SurfaceControl.Builder(mSession.mSurfaceSession)
    );

    // 设置初始位置和大小
    mSurfaceControl.setPosition(mWin.mFrame.left, mWin.mFrame.top);
    mSurfaceControl.setSize(width, height);
} finally {
    SurfaceControl.closeTransaction();
}

注意那个 SurfaceControl.openTransaction()closeTransaction()。这是为了批量提交属性变更,避免每次 setPosition 都触发一次 Binder 调用。

避坑指南:我曾经在自定义 Launcher 时,频繁调用 SurfaceControl.setAlpha() 来做动画。结果发现掉帧严重。后来才意识到,每次 setAlpha 都会触发一次跨进程调用。正确的做法是:用 SurfaceControl.Transaction 批量提交。

Surface 的硬件加速与软件绘制

Surface 支持两种绘制方式:

  • 软件绘制:通过 Surface.lockCanvas() 拿到 Canvas,用 CPU 画。适合简单场景。
  • 硬件加速:通过 EGL 创建 OpenGL 上下文,用 GPU 画。性能更好,但需要管理 EGL 环境。

我个人建议:能用硬件加速就别用软件绘制。软件绘制在低端机上很容易掉帧,而且不支持很多高级效果(如模糊、阴影)。

不过要注意,硬件加速的 Surface 创建方式略有不同。应用需要自己创建 EGLSurface,然后关联到 Surface 上。这部分代码比较繁琐,一般由 ThreadedRenderer 帮你搞定。

总结一下

Surface 的创建,本质上是一次跨进程协作。WMS 发起请求,SurfaceFlinger 分配资源,应用拿到画布开始绘制。绘制好的 Buffer 再通过 BufferQueue 送回 SurfaceFlinger 合成显示。

嗯,这里面的关键点就两个:

  • Surface 是生产者的视角,它只管画。
  • SurfaceFlinger 是消费者的视角,它只管合成和显示。

中间通过 BufferQueue 解耦,通过共享内存传递数据。这套设计,说白了就是生产者-消费者模式在 Android 图形系统中的经典应用。

一句话记住:Surface 是应用端的“画布”,SurfaceFlinger 是系统端的“画师”。画布由画师提供,画完交给画师展示。


公众号:蓝海资料掘金营,微信deep3321