Surface 与 SurfaceFlinger:Window 的绘制表面是如何创建的?
好,我们继续往下走。前面几章我们把 WindowManager、ViewRootImpl 和 WMS 之间的交互理清楚了。但有个关键问题一直悬着:应用层画的东西,到底是怎么跑到屏幕上去的?
这中间有个核心角色——Surface。说白了,它就是一块画布。应用往上面画,系统负责把画布内容显示出来。而 SurfaceFlinger,就是那个负责合成的“大管家”。
Surface 到底是什么?
我个人习惯把 Surface 理解成一块共享内存的封装。它不是一个普通的 Java 对象,它背后连接着 BufferQueue 的生产者端。
你想想看,应用进程和系统服务进程是隔离的。应用画了图,数据怎么传给 SurfaceFlinger?靠的就是 Surface 内部的 IGraphicBufferProducer 这个 Binder 接口。
核心要点:Surface 是应用端的“画笔”,SurfaceFlinger 是系统端的“画框”。两者通过 BufferQueue 进行数据交换。
创建流程:从 WMS 到 Surface
我记得刚接触这部分源码时,最困惑的就是:Surface 到底是谁创建的?是应用自己 new 的吗?不是。
流程是这样的:
- WMS 收到 addWindow 请求,开始为窗口分配 Surface。
- WMS 调用
Session.relayout(),最终走到WindowStateAnimator.createSurfaceLocked()。 - 这里会调用
SurfaceControl.openTransaction()和new SurfaceControl()。 SurfaceControl的构造方法里,会通过SurfaceComposerClient向 SurfaceFlinger 发起请求。- SurfaceFlinger 创建一个
Layer对象,并返回一个SurfaceControl给 WMS。 - WMS 再从
SurfaceControl中 copy 出一个 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 从创建到显示的全链路串起来。你仔细看看,应该能一目了然。
创建 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