Flutter 里的 Layer 解析,带你了解不一样角度下的 Flutter 渲染逻辑

众所周知,Flutter 最被人熟知的就是 Widget - Element - RenderObject 这三棵树,而实际上如下图所示,在 paint 阶段,RenderObject 并不直接执行真正的绘制操作,而是会创建和配置相应的 Layer 对象,例如 PictureLayer

而我们知道,在 Dart 层面,当 RenderObjectisRepaintBoundaryture 时,Flutter Framework 就会自动创建一个 OffsetLayer 来"承载"这片区域,所以 Layer 内部的画面更新一般不会影响到其他 Layer ,这是 Flutter 在 Dart 层面最基础的 Layer 概念。

例如 Navigator 跳转不同路由页面,每个页面内部就有一个 RepaintBoundary 控件,这个控件对应的 RenderRepaintBoundary 内的 isRepaintBoundary 标记位就是为 true ,从而路由页面之间形成了独立的 Layer

Layer 是如何更新?这就涉及了 Dart Layer 内部的 markNeedsAddToSceneupdateSubtreeNeedsAddToScene 这两个方法,简单来说:

  • markNeedsAddToScene 方法类似 setState ,它其实就是把 Layer 内的 _needsAddToScene 标记为 true
  • updateSubtreeNeedsAddToScene 方法就是遍历所有 child Layer,通过递归调用 updateSubtreeNeedsAddToScene() 判断是否有 child 需要 _needsAddToScene ,如果是那就把自己也标记为 true

而对应到实际的执行上就是:只有当 _needsAddToScene 等于 true 时,对应 LayeraddToScene 才会被调用;而当 Layer_needsAddToScenefalse_engineLayer 不为空时就触发 Layer 的复用

例如,当一个新的页面打开时,底部的页面并没有发生变化时,它只是参与画面的合成,所以对于底部页面来说它 "Layer" 是可以直接被复用参与绘制 。

当然,Dart 层的 Layer 实际上对应在 C++ 有具体的 EngineLayer 实现,而 Flutter 提交给 Engine 进行光栅化操作的对象,实际上就是承载绘制指令的 C++ LayerTree,而 Layer 从 Dart 「透传」到 C++ 之后,就会通过 flow 模块来实现光栅化并合成输出,这才是真实绘制的开始。

Flutter 里 Layer 有 Dart 层面的 Layer 和 C++ 层面的 Layer ,简单说,一般 C++ 层面的会称为 EngineLayer ,比如 ClipPathLayerClipPathEngineLayer

而这里说的「透传」,在上古老版本的是 ui.window,在现在的版本指的是 ui.FlutterView,在 Dart 层面一般是通过如 ui.window.render(sceneBuilder.build())或者 FlutterView.run(sceneBuilder.build()) 来提交到 C++ 构建,但是这里我们可以看到, render 方法需要的参数是 Scene 对象而不是 Layer ,这又是为什么?

因为简单来说,Scene 对象就是 Flutter 在 Dart 操作 C++ Layer 的入口 ,而在 Flutter Framework 中 Scene 只能通过 SceneBuilder 构建。

所以如下图所示,一般来说 Dart 层的 Layer 不会直接操作 Scene ,而是通过统一基类的 addToScene 方法来操作 SceneBuilder , 例如 Dart 层的 ClipPathLayer 会在它实现的 addToScene(ui.SceneBuilder builder) 调用 builder.pushClipPath 来得到一个 ClipPathEngineLayer,而所有 Dart Layer 最终会通过 sceneBuilder.build() 统一提交到 C++ :

所以 ui.SceneBuilder (Dart) 属于构建渲染场景的关键 API,它提供了一系列 push 方法和 add 方法,如 addPictureaddTextureaddPlatformView,这些方法在底层分别对应着 Engine 中 PictureLayerTextureLayerPlatformViewLayer 的创建,SceneBuilder 的职责就是收集这些 Layer,并将它们组装成一个完整的 LayerTree,最终交由 Engine 处理

而在 Flutter 里,一般 Flutter 中 Layer 可以分为 ContainerLayer 和非 ContainerLayerContainerLayer 是可以具备子节点,也就是带有 append 方法,例如:

  • 位移类(OffsetLayer/TransformLayer);
  • 透明类(OpacityLayer
  • 裁剪类(ClipRectLayer/ClipRRectLayer/ClipPathLayer);

对于 ContainerLayer因为这些 Layer 都是一些像素合成的操作,其本身是不具备"描绘"控件的能力,如果要呈现画面一般需要和 PictureLayer 结合

比如 ClipRRect 控件的 RenderClipRRect 内部,在 pushClipRRect 时可以会创建 ClipRRectLayer ,而新创建的 ClipRRectLayer 会通过 appendLayer 方法触发 append 操作添加为父 Layer 的子节点。

ContainerLayer 一般不具备子节点,比如:

  • PictureLayer 是用于绘制画面,Flutter 上的控件基本是绘制在这上面;
  • TextureLayer 一般用于外界纹理或者 PlatformView 实现
  • PlatformViewLayer 一般用于 PlatformView 相关嵌入的使用场景;

举个例子,控件绘制时的 Canvas 来源于 PaintingContext , 而如下代码所示 PaintingContext 通过 _repaintCompositedChild 执行绘制后得到的 Picture 最后就是提交给所在的 PictureLayer.picture

ini 复制代码
void stopRecordingIfNeeded() {
    if (!_isRecording)
      return;
    _currentLayer.picture = _recorder.endRecording();
    _currentLayer = null;
    _recorder = null;
    _canvas = null;
  }

而对于 TextureLayerPlatformViewLayer ,实际上它们也是有着不一样的故事,特别是在 PlatformView 实现上。

在 C++ 层面,PlatformViewLayerTextureLayer 是 Flutter Engine 在 flow 渲染层中的两个核心类,特别是在 PlatformView 上:

  • PlatformViewLayer 通过将最终的视图合成工作委托给宿主系统(Android),提供了原生的交互和 UI 保真度,但对应性能开销也会较高 ,比如 Android 平台的 HC 实现
  • TextureLayer 提供了更轻量级的纹理合成支持,用于在 Flutter 自己的渲染管线内展示原生像素流,但在交互性和原生视图类型兼容性上存在一些限制,比如 Android 平台的 VD 和 TLHC 实现

要了解 PlatformViewLayerTextureLayer 在 PlatformView 的实现,就需要简单聊聊 Layer 的 PrerollPaint 阶段:

  • Preroll 阶段 :这是对 LayerTree 的第一次遍历,Engine 会调用每个 Layer 的 Preroll 方法,用于计算绘制边界(paint bounds)、确定渲染属性(如不透明度、裁剪区域)以及检查所需资源是否就绪
  • Paint 阶段 :这是第二次遍历,Engine 调用每个 Layer 的 Paint 方法来生成绘制指令,对于大多数 Layer 而言,这意味着将绘制命令添加到一个 DisplayList(Flutter 内部的绘制操作记录)

TextureLayer

TextureLayer 的核心目标是在 Flutter 画布上提供一个矩形区域,用于渲染由原生代码管理的外部纹理,它的机制非常适合那些会产生连续图像流的场景,例如视频播放器、相机预览,或使用 OpenGL/Vulkan 等原生图形库渲染的自定义图形。

TextureLayerPreroll 方法实现非常直接,它主要调用 set_paint_bounds 来声明其在场景中所占据的矩形区域,此外它还会通知 DiffContext 该子树中存在 TextureLayer,防止在某些情况下父级 ContainerLayer 错误地跳过对 TextureLayer 的差异比对(diffing)优化。

Paint 方法是 TextureLayer 功能的核心,简单流程为:

  • 获取纹理对象 :通过 PrerollContext 中的 texture_registry,使用成员变量 texture_id_ 查找到对应的 flutter::Texture 对象
  • 执行绘制 :如果纹理对象有效,就调用对象的 Paint 方法 texture->Paint(context.canvas,...),这个调用会直接将外部纹理绘制到当前 Flutter 的 DlCanvas

这里的texture_id_ 对应的是一个实现了 flutter::ExternalTexture C++ 接口的原生对象,在不同平台上,ExternalTexture 的实现有所不同,例如在 Android 上通常由 SurfaceTexture 支持;在 iOS 上,则由一个实现了 FlutterTexture 协议的对象来支持,这个对象提供一个 CVPixelBuffer

TextureLayer 支持绘制的核心是可以直接在 GPU 上生成纹理,然后通过 ExternalTexture 接口将对应的 GPU 纹理的句柄(handle)传递给 Flutter Engine ,从而避免了将像素数据从 GPU 拷贝到主内存,再从主内存拷贝回 GPU 的昂贵过程。

PlatformViewLayer

如果说 TextureLayer 是把最终绘制过程统一到 Flutter ,那 PlatformViewLayer 就是将最终绘制过程从 Flutter 放到系统渲染。

PrerollPlatformViewLayer 会执行一个关键检查:if (context->view_embedder == nullptr),这意味着它的正常工作其实依赖于一个有效的 ViewEmbedder 上下文。

如果检查失败,就会直接输出类似 "Trying to embed a platform view but the PrerollContext does not support embedding" 的错误。

通过检查后,它会设置一个贯穿渲染流程的标志位:context->has_platform_view = true;,这个标志位会向整个渲染管线宣告:即将发生一次"合成中断"。

所以 PlatformViewLayerPaint 方法与 TextureLayer 的截然不同,对于 Flutter 的 DlCanvas 而言,这个方法几乎是一个空操作,它不会向 context.canvas 发出任何绘制指令,它的唯一作用是在 LayerTree 中充当一个标记。

在 Dart 层,PlatformViewLayer.addToScene 方法会调用 builder.addPlatformView(...),也就是指示 Engine 在场景中插入这个特殊标记的指令。

所以 PlatformViewLayerViewEmbedder 很关键,它起到了编排合成中断的作用

ViewEmbedder 是一个平台相关的组件,负责管理原生视图层级,当 Rasterizer 在 Preroll 阶段检测到 has_platform_view 标志位被设置时,就会导致整个渲染行为会发生根本性改变,它不再将所有 Flutter 内容渲染到单一的 Surface,而是执行以下步骤:

  1. 渲染底层内容 :将 PlatformViewLayer 之前的所有 Flutter 内容渲染到一个离屏纹理中
  2. 通知 Embedder :通知 ViewEmbedder 显示由 view_id_ 标识的原生视图
  3. 渲染顶层内容 :将 PlatformViewLayer 之后的所有 Flutter 内容渲染到第二个离屏纹理中
  4. 委托系统合成 :最后,它依赖宿主操作系统的合成器(Android 上的 SurfaceFlinger)来正确地堆叠这些图层,最终形成屏幕上看到的画面

因此,PlatformViewLayer 本质上不是一个给 Skia 或 Impeller 的绘制指令,而是给 Engine Shell 和操作系统合成器的一个高级命令,而这个切换过程涉及昂贵的内存操作和线程同步,这也是其性能开销的根源。

最后

那么,到这里关于 Flutter 里 Layer 的整体概念就介绍完了,虽然说的是 Layer ,但是也涉及了不少其他东西,例如 PlatformView,整体来说这并不是一篇实用的文章,但是它确实能帮你更好理解 Flutter 里的渲染机制,特别是对于 PlatformView 的底层实现原理,换句话说,也许哪天你遇到 Flutter 画面闪烁了,这些概念也许就用上了呢?

相关推荐
Bryce李小白1 小时前
Flutter中实现页面跳转功能
flutter
一笑的小酒馆3 小时前
Android12去掉剪贴板复制成功的Toast
android
程序视点3 小时前
IObit Uninstaller Pro专业卸载,免激活版本,卸载清理注册表,彻底告别软件残留
前端·windows·后端
一笑的小酒馆3 小时前
Android12App启动图标自适应
android
前端程序媛-Tian4 小时前
【dropdown组件填坑指南】—怎么实现下拉框的位置计算
前端·javascript·vue
嘉琪0014 小时前
实现视频实时马赛克
linux·前端·javascript
烛阴4 小时前
Smoothstep
前端·webgl
程序员江同学4 小时前
Kotlin 技术月报 | 2025 年 7 月
android·kotlin
若梦plus5 小时前
Eslint中微内核&插件化思想的应用
前端·eslint
爱分享的程序员5 小时前
前端面试专栏-前沿技术:30.跨端开发技术(React Native、Flutter)
前端·javascript·面试