Surface 与 BufferQueue 机制

  • Surface 到底是什么?它和 View/Window 的关系是什么?
  • App 绘制的像素数据是如何存储在 Surface 中的?
  • Surface 是如何跨进程把数据传递给 SurfaceFlinger 的?

Surface 的定义

Surface 不是 “显示组件”,而是 「一块可绘制的内存缓冲区(Buffer)的抽象封装」。

  • 通俗理解:Surface 是 App 写给 “画布” 的 “画板”,App 只负责往画板上画画(写入像素数据),但不负责把画板挂到墙上(显示到屏幕);

  • 技术本质:Surface 内部持有 BufferQueue 的「生产者(Producer)」端,通过它向缓冲区写入数据。

Surface 与 Window/View 的关系

  • 1 个 Window 对应 1 个 Surface:每个 Activity/Dialog/ 状态栏都对应一个 Window,每个 Window 会创建一个专属的 Surface(通过 WindowManager 申请);

  • View 依赖 Surface 绘制:所有 View 的 onDraw() 最终都是通过 Canvas 把数据写入所属 Window 的 Surface 缓冲区;

  • Surface 与 ViewRootImpl 绑定:ViewRootImpl 是 Surface 和 View 树的桥梁,负责把 View 绘制的内容同步到 Surface 中。

Activity 启动 → WindowManager 创建 Window
  ↓
WindowManager 向 SurfaceFlinger 申请 Surface
  ↓
SurfaceFlinger 创建 BufferQueue,并返回「生产者」给 App 层
  ↓
App 层封装生产者为 Surface 对象,绑定到 ViewRootImpl
  ↓
ViewRootImpl 把 Surface 传递给 Canvas,供 View 绘制
  • 关键结论:Surface 的创建是跨进程的,由 SurfaceFlinger 主导,App 层只持有 “写入数据的权限”。

BufferQueue - 缓冲区的「生产者 - 消费者」模型

BufferQueue 是连接 App 和 SurfaceFlinger 的核心机制,本质是 「环形缓冲区队列」,遵循生产者 - 消费者设计模式:

  • 生产者(Producer):App 层(Surface 持有),负责往缓冲区写入绘制好的像素数据;

  • 消费者(Consumer):SurfaceFlinger 层,负责从缓冲区读取数据,进行图层合成。

BufferQueue 的核心作用

  • 解耦 App 绘制和 SurfaceFlinger 合成:App 绘制的速度和 SurfaceFlinger 合成的速度可以不一致,通过缓冲区队列缓冲;

  • 复用缓冲区:避免频繁创建 / 销毁内存缓冲区,减少内存抖动和性能消耗;

  • 同步数据:保证 SurfaceFlinger 读取的缓冲区是 “完整绘制完成” 的,避免读取到半拉子数据。

BufferQueue 的工作流程(60Hz 屏幕为例)

1. 初始状态:BufferQueue 有 N 个空缓冲区(通常 2-3 个);
2. App 向 BufferQueue 请求一个空缓冲区(dequeueBuffer);
3. App 通过 Canvas 把绘制内容写入该缓冲区;
4. App 把写好的缓冲区送回队列(queueBuffer),标记为“就绪”;
5. SurfaceFlinger 从队列取出就绪的缓冲区(acquireBuffer);
6. SurfaceFlinger 合成该缓冲区的数据到最终帧;
7. SurfaceFlinger 把用完的缓冲区放回队列(releaseBuffer),标记为“空”;
8. 重复步骤 2-7,每 16.6ms 执行一次(60Hz 屏幕)。

双缓冲 / 三缓冲机制

  • 双缓冲:BufferQueue 有 2 个缓冲区,一个供 App 写入,一个供 SurfaceFlinger 读取,避免 “写一半被读” 的问题;

  • 三缓冲:当 App 绘制耗时超过 16.6ms 时,第三个缓冲区可以承接下一次绘制,减少掉帧(比如 120Hz 屏幕常用三缓冲)。

Surface + BufferQueue 核心

// Surface 中的生产者逻辑(App 层)
public class Surface {
    private final SurfaceControl mSurfaceControl;
    private final BufferQueueProducer mProducer; // 生产者

    // 申请空缓冲区
    public long dequeueBuffer(BufferItem item, long timeout) {
        return mProducer.dequeueBuffer(item, timeout);
    }

    // 提交写好的缓冲区
    public int queueBuffer(long bufferId, int flags) {
        return mProducer.queueBuffer(bufferId, flags);
    }
}

// SurfaceFlinger 中的消费者逻辑(Native 层简化)
class SurfaceFlinger {
    private BufferQueueConsumer mConsumer; // 消费者

    // 获取就绪的缓冲区
    status_t acquireBuffer(BufferItem* item, nsecs_t timeout) {
        return mConsumer->acquireBuffer(item, timeout);
    }

    // 释放用完的缓冲区
    status_t releaseBuffer(int slot, uint64_t frameNumber) {
        return mConsumer->releaseBuffer(slot, frameNumber);
    }
}

Surface → SurfaceFlinger 的数据传递

跨进程通信的核心:Binder

Surface 持有的 BufferQueueProducer 是 Binder 接口,SurfaceFlinger 持有的 BufferQueueConsumer 是 Binder 服务端:

  • App 层调用 queueBuffer() 时,通过 Binder 把缓冲区的「句柄(Handle)」传递给 SurfaceFlinger(而非直接传递像素数据,避免内存拷贝);

  • SurfaceFlinger 通过句柄直接访问缓冲区内存(基于共享内存 Ashmem),无需拷贝,效率极高。

数据传递完整流程

App 层:View.onDraw() → Canvas 写入像素数据
  ↓
Canvas 把数据写入 Surface 持有的 BufferQueue 缓冲区
  ↓
Surface.queueBuffer() → 通过 Binder 把缓冲区句柄发给 SurfaceFlinger
  ↓
SurfaceFlinger.acquireBuffer() → 获取缓冲区句柄,访问共享内存
  ↓
SurfaceFlinger 合成该缓冲区(与其他 Surface 的缓冲区)→ 输出到屏幕
  • 关键结论:像素数据存储在共享内存中,只传递句柄,不拷贝数据,这是 Android 渲染性能的核心优化点。

SurfaceView vs TextureView

普通 View 依赖 Window 的 Surface 绘制,而高频绘制场景(视频、游戏、直播)需要更高效的 Surface 载体:

image.png

问题

  • 为什么 SurfaceFlinger 不直接读取 App 内存中的像素数据,而是通过 BufferQueue 共享内存?

  • 为什么 SurfaceView 适合高频绘制场景,而普通 View 不适合?

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容