flutter学习笔记- 渲染引擎

41 阅读12分钟

flutter渲染

Flutter 被设计为一个可扩展的分层系统。它可以被看作是各个独立的组件的系列合集,上层组件各自依赖下层组件。组件无法越权访问更底层的内容,并且框架层中的各个部分都是可选且可替代的。

image.png 我理解的flutter渲染

  • Flutter自己完成一切:Flutter应用在屏幕上看到的每一个像素,从按钮到文字,都是由Flutter的渲染引擎(Skia/Impeller)绘制的。它不依赖iOS的UIKit或Android的View系统来生成原生控件。
  • 平台嵌入层:这是理解的关键。Flutter引擎本身(C++编写)是一个不知道自己是运行在手机、桌面还是Web上的“盲盒”。平台嵌入层 就是这个“盲盒”与具体操作系统(如iOS、Android、Windows)进行对话和连接的“翻译官”或“适配器”。
  • 稳定的ABI:ABI可以理解为引擎与嵌入层之间约定好的“电话线路”和“通话协议”。无论底层平台如何变化,引擎都通过这套固定的接口(方法调用、数据格式)与嵌入层通信,保证了引擎核心的稳定性和跨平台性。 可以理解为这个三层模型

image.png

flutter与android/IOS渲染逻辑对比

核心区别:两种思维模式

让我们用一个对比表来直观理解:

你的Dart代码完全不是转化成Android的“可绘制代码”或iOS的“UIView”的。 你写的Dart Flutter代码,其最终产物在屏幕上不直接对应任何平台的原生控件。

image.png

所以,这句话揭示的正是Flutter实现“高一致性”和“高性能”跨端能力的秘诀:

  1. 自上而下的统一渲染:通过自己控制渲染管线,保证UI的绝对一致性。
  2. 自下而上的抽象接口:通过定义稳定的ABI和平台嵌入层,将千差万别的平台原生能力抽象成一套统一的接口供引擎调用。

更加详细的 架构解析

比喻:自己作画 vs 拼装乐高套件

  • 原生开发(拼装乐高套件):平台(苹果/谷歌)给你提供了标准的“乐高零件库”(UIView, Button, TextView)。你的代码负责选择零件、拼装起来。最终作品是由标准零件构成的。
  • Flutter(在白纸上作画):平台只提供给你一张固定大小的空白画布和一个画室环境(生命周期、输入事件)。Flutter引擎就是你的手和画笔。你的Dart代码是“绘画指令”(画一个圆角矩形在这里,写一行文字在那里)。最终作品是一幅完全由你(Flutter)掌控的、独一无二的画,画布上没有任何平台的“标准零件”。

技术流程详解

你提到的“获取纹理”正是关键所在。整个过程是这样的:

  1. 你的Dart代码:定义了一棵Widget树,它只是一个配置的蓝图,存在于Dart的内存中。
  2. Flutter引擎(C++):
    • 根据Widget树,构建出对应的RenderObject树(负责布局和绘制计算)。
    • 布局引擎计算每个元素在屏幕上的位置和大小。
    • 合成与绘制引擎(Skia/Impeller)遍历渲染树,生成一系列底层的绘图指令(如“填充路径”、“绘制图像”)。
  1. 提交给GPU:这些绘图指令被提交到GPU,在一块由Flutter引擎请求、平台分配给的纹理(Texture)上进行光栅化,最终生成一张包含你整个UI的位图。
  2. 平台嵌入层的工作:平台(如iOS)的任务仅仅是:
    • 创建一个原生的UIView(作为容器)。
    • 将Flutter引擎绘制好的那块纹理,作为这个UIView的内容显示出来。
    • 负责将这个UIView插入到原生视图层级中,并传递触摸事件。

平台看到的只是一个“黑盒”的、会变化的内容视图,它完全不知道、也不关心这个视图里面画的是按钮、列表还是动画。 它只知道这是一张图。

fultter 与 react native 渲染逻辑对比

React Native采用了与Flutter完全不同的架构哲学。如果说Flutter是自己作画的画家,那么React Native更像是指挥两支本地乐队的指挥家。

核心区别:桥接与映射

RN的核心设计是:用JavaScript编写业务逻辑和组件声明,通过一个“桥接”将其翻译并映射到各自平台的原生控件上。

特性React Native (以 Fabric + JSI 为核心)Flutter
架构本质桥接与映射自主控制与绘制
UI最终形态由真正的平台原生控件(UIView/View)组成由Flutter引擎绘制的一张纹理(位图)
渲染引擎Fabric。现代化渲染系统,用于更高效地管理和更新由原生组件构成的UI树Impeller (iOS/Android默认),Skia (桌面/Web)。自研引擎,直接向GPU发送绘图指令。
最终渲染执行者iOS/Android 原生系统Flutter 自己的渲染引擎(Impeller/Skia)
通信方式JavaScript Interface (JSI):同步、直接的C++接口,替代旧的异步桥接,允许JS直接持有和调用原生对象引用直接调用:Dart代码直接驱动C++引擎,无序列化开销
性能瓶颈频繁的跨桥异步通信可能带来延迟图形渲染管线性能(现由Impeller优化)
性能侧重点原生般的交互与启动速度:Fabric实现同步渲染提升交互流畅度;TurboModules按需懒加载减少启动时间极致可预测的渲染性能:Impeller通过预编译着色器彻底消除动画卡顿,在复杂、高定制化UI上表现卓越
一致性与灵活度受制于平台控件,外观、行为需遵循平台规范,自定义复杂控件需大量原生代码完全自主,像素级控制,实现任何设计无平台限制
热重载/刷新Fast Refresh(状态保持较好)Hot Reload(体验极快,状态保持极好)

RN 技术流程详解

当你写一个RN组件时,流程如下:

// 1. 你写的JSX代码 (JavaScript) <View style={{backgroundColor: 'red'}}> <Text>Hello World</Text> </View> // 2. RN框架通过JSI直接内存访问 // 消息大致是:{ "type": "createView", "reactTag": 1, "viewType": "RCTView", "props": {style: {backgroundColor: 'red'}} } // 3. 原生端(iOS/Android)接收消息 // - iOS: 创建一个 UIView,背景色设为红色。 // - Android: 创建一个 View,背景色设为红色。 // 4. 屏幕上显示的是一个如假包换的原生视图。

关键点:你在RN中使用的 、、 等核心组件,在运行时确实会一对一地对应生成一个平台原生的 UIView/TextView/ImageView。你的界面最终是由这些真正的原生控件混合拼装而成的树。

flutter 与 android/IOS 渲染体系技术栈对比

三大渲染体系技术栈对比

对比维度iOS 原生渲染体系Android 原生渲染体系Flutter 渲染引擎
核心图形引擎不直接使用第三方引擎,使用自主技术栈。Skia 作为其2D图形绘制的核心引擎。自带引擎:在 iOS/Android 上主要为 Impeller(新)或 Skia(旧/后备)。
底层图形APIMetal (为主),专为 Apple 硬件优化。Vulkan (现代) / OpenGL ES (旧),跨平台标准。通过引擎适配层调用:在 iOS 用 Metal,在 Android 用 Vulkan/OpenGL ES。
UI框架与合成器UIKit (控件) -> Core Animation (合成与动画)。View 系统 (控件) -> RenderEngine/SurfaceFlinger (合成)。Flutter Framework (Widget) -> 自建渲染树 -> 引擎直接合成与绘制。
与Flutter的关系Flutter 引擎使用 Metal,但完全绕过 UIKit 和 Core Animation 的绘制逻辑,自行管理合成与动画。Flutter 引擎可能与系统共用 Skia 库,但绕过整个 View 系统及硬件加速画布,自行管理所有绘制。在两大平台上,Flutter 都作为一个独立的、顶层的图形客户端运行,输出一整张纹理。
输出形态由无数个 CALayer(对应各个UIView)合成的多层图像树。由 Surface 承载的多层图像树。一张统一的、由Flutter引擎绘制的单层位图(纹理)。
核心优势极致优化:与硬件/OS深度集成,能效比高。体验统一:100%符合iOS设计规范与交互细节。硬件兼容:通过驱动适配各种硬件。生态开放:图形栈各层可定制。绝对一致:像素级跨平台一致性。性能可控:消除平台渲染差异,动画与滚动流畅度可自主优化到底。
主要代价/挑战封闭:技术锁定Apple生态,无法跨平台。碎片化:硬件与驱动差异导致图形行为偶有不一致。平台特性损耗:需额外工作模拟或接入平台特有UI效果(如文本选择菜单、系统滚动条)。

核心差异与架构选择

通过对比,我们可以得出三个核心结论,它们直接影响您的技术选型:

  1. 架构哲学完全不同
    • 原生:是 “集成式” 架构。UI框架、合成器、图形驱动紧密集成在操作系统中,不可分割。
    • Flutter:是 “便携式” 架构。它自带一个完整的、可插拔的图形运行时,将自己“嵌入”到各个宿主平台中。
  1. Flutter 如何实现“覆盖”你可以将Flutter应用想象成一个全屏播放的、极高帧率的视频播放器。平台(iOS/Android)只负责提供“播放窗口”(UIView/SurfaceView)和通知事件(触摸、生命周期),而“视频”的每一帧内容(即整个UI)完全由Flutter引擎计算并绘制。它不理会平台当前原生的UI是什么,它只输出自己的画面。
  2. 如何根据渲染需求做技术选型
    • 选择原生开发,当你的应用极度依赖平台的原生特性与完美融合感(例如需要深度定制系统键盘、使用精准的平台无障碍服务、或要求与最新OS设计语言无缝同步)。
    • 选择 Flutter,当你的项目将“多端 UI 绝对一致”和“复杂自定义动画的性能”置于最高优先级,且愿意为适配平台特性(如边缘手势、原生组件嵌入)支付额外的开发成本。

Skia与Impeller技术对比解析

那么最新的flutter已经升级到Impeller 渲染,而android使用的skia,那我是否可以认为,最新版本的flutter比android的渲染效率要高?

核心结论

这本质上是 “为特定场景优化的新引擎” 与 “通用、成熟但需兼容旧设备的系统引擎” 之间的对比。

下面的表格清晰地展示了二者的核心差异:

对比维度Flutter (Impeller 引擎)Android 原生 (Skia 引擎)
设计哲学专为Flutter定制的现代引擎,目标是可预测的极致性能,完全控制渲染管线通用的2D图形库,服务于Android系统及所有应用,首要保证广泛的兼容性与稳定性。
性能杀手锏提前编译 (AOT)所有着色器,彻底消除运行时编译卡顿即时编译 (JIT),首次绘制新图形元素时需现场编译着色器,易引发卡顿。
硬件对接直接调用现代图形API(Android上为Vulkan),最大限度减少驱动开销经过多级Android图形栈(如HWUI),可能包含额外抽象层。
性能表现帧率更高、更稳定。实测显示GPU耗时降低约30%,120Hz流畅度达标率显著提升性能受设备硬件、厂商驱动、系统版本影响大,碎片化严重。
效率核心通过架构先进性和专属优化获得高性能。依赖系统深度集成和多年的广泛优化。

Skia与Impeller的对比测试

Impeller优化提升场景

🎨 1. 高频实时绘画与手写

这正是“高频绘画”的典型场景。

  • 业务举例:绘图应用、白板协作、签名、笔记类应用。
  • 传统问题 (Skia JIT):用户每一笔划过屏幕,都可能触发新的路径绘制和着色器编译。在复杂笔刷(如带有纹理、混色效果)下,首笔或变化笔刷时的卡顿感非常明显,破坏创作流畅度。
  • Impeller优势:所有笔刷效果的着色器均已预编译。无论用户如何快速、多变地绘画,每一笔都能即时、稳定地渲染,真正做到“笔随心动”,帧率稳如直线。

✨ 2. 复杂动画与矢量图形

  • 业务举例:加载动画、图标变形(Lottie类动画)、页面过渡特效、数据可视化图表。
  • 传统问题:任何非矩形、带渐变、阴影、裁剪的动画,在首次渲染时都需要编译着色器。导致动画启动时掉帧,或复杂图表交互时响应不及时。
  • Impeller优势:动画所需的着色器早已就绪。动画从第一帧开始就是全速的,即使是非常复杂的矢量路径动画,也能保持始终如一的高帧率。

👆 3. 重度手势驱动的交互

  • 业务举例:地图缩放拖拽、图片/视频缩放浏览、可拖拽排序的列表、3D模型查看器。
  • 传统问题:手势事件每秒触发数十次,UI需实时更新。若更新过程中遭遇着色器编译,就会导致触控反馈与视觉更新脱节,感觉“不跟手”。
  • Impeller优势:渲染管线全程无编译中断,能极快地响应每一帧手势更新,实现“丝滑般”的跟手体验,延迟感大幅降低。

📜 4. 快速滚动的复杂列表

  • 业务举例:社交媒体信息流(每条动态样式不一)、电商商品瀑布流、聊天列表(含各种气泡、图片)。
  • 传统问题:在快速滚动时,不断出现的新Item样式可能触发编译,导致滚动中突然的、无法预测的微小卡顿。
  • Impeller优势:列表Item的渲染方式(圆角、阴影、渐变)已预知,滚动过程中的渲染成本是恒定且可预测的,从而维持高速滚动的绝对流畅。