Flutter 高性能原理浅析
前言
Flutter 是 Google 推出的用于在 iOS 和 Android 平台开发高质量原生应用的全新移动 UI 框架。Flutter 的发展经历了几个重要版本节点:
Flutter 采用自绘引擎与 Dart 语言实现高性能跨平台开发。文章分析了 Flutter 框架结构,对比了 Hybrid 及 RN 架构的差异,指出 Flutter 无需中间层直接绘制 UI 的优势。重点阐述了 Dart 语言在内存分配、垃圾回收、JIT/AOT 编译及单线程异步消息机制上的特性。同时深入讲解了 Flutter Engine 的渲染流程,包括 VSync 同步、Skia 绘图及 Widget-Element-RenderObject 三层架构的布局原理。通过约束布局与 Relayout Boundary 优化,Flutter 实现了高效的视图更新与渲染,确保接近原生的性能表现。

Flutter 是 Google 推出的用于在 iOS 和 Android 平台开发高质量原生应用的全新移动 UI 框架。Flutter 的发展经历了几个重要版本节点:
此后,Flutter 逐渐受到广泛关注。在 Flutter 诞生之前,跨平台 UI 框架方案主要包括基于 WebView 的 Cordova、AppCan 等,以及使用 HTML+JavaScript 渲染成原生控件的 React Native、Weex 等。Flutter 开辟了一种全新的思路,从头到尾重写一套跨平台的 UI 框架,包括 UI 控件、渲染逻辑甚至开发语言。

从架构图中可以看出,Flutter 主要被分为两层:Framework 层和 Flutter Engine。
Framework 层全部使用 Dart 编写,提供完整的 UI 框架 API,并预写了 Android(Material Design)和 iOS(Cupertino)风格的 UI,极大方便了移动端开发。
Framework 底层是 Flutter 引擎。引擎主要负责图形绘制 (Skia)、文字排版 (libtxt) 和提供 Dart 运行时,引擎全部使用 C++ 实现。
Hybrid 架构的 UI 层渲染是基于 WebView 去渲染,其性能取决于 WebView 的渲染性能。目前已知 WebView 渲染性能 < Native UI 的性能。
RN/Weex 的架构中,是基于 Native 的 UI 框架去适配,中间多了一层 JS 转 Native UI 的过程,存在通信开销。
Flutter 不需要中间层(如 WebView 或 JS 转 Native UI 过程),它是基于图像渲染引擎去直接绘制 UI,减少了桥接损耗。
Flutter 的 Framework 层使用了 Dart 语言,Dart 语言具有以下优势:
DartVM 的内存分配策略非常简单,创建对象时只需要在现有堆上移动指针,内存增长始终是线性的,省去了查找可用内存段的过程。Dart 中类似线程的概念叫做 Isolate,每个 Isolate 之间是无法共享内存的,这种分配策略可以让 Dart 实现无锁的快速分配。
Dart 的垃圾回收采用了多生代算法。新生代在回收内存时采用了'半空间'算法,触发垃圾回收时,Dart 会将当前半空间中的'活跃'对象拷贝到备用空间,然后整体释放当前空间的所有内存。整个过程中 Dart 只需要操作少量的'活跃'对象,大量的没有引用的'死亡'对象则被忽略。这种多生代无锁垃圾回收器,专门为 UI 框架中常见的大量 Widgets 对象创建和销毁优化,非常适合 Flutter 框架中大量 Widget 重建的场景。
代码体积优化(Tree Shaking),编译时只保留运行时需要调用的代码(不允许反射这样的隐式引用),所以庞大的 Widgets 库不会造成发布体积过大。
Dart 支持两种编译模式:
对于移动端的交互来说,大多数情况下都是在等待状态,等待网络请求,等待用户输入等。发起一个网络请求在一个线程中是可以进行的,网络请求是异步的。Flutter 采用了 Dart 这种单线程机制,省去了多线程上下文切换带来的性能损耗。(对于高耗时操作,同样支持多线程操作,通过 Isolate 开启,不过注意这里的多线程,内存是无法共享的。)
当一个 Dart 的方法开始执行时,它会一直执行直至达到这个方法的退出点。换句话说 Dart 的方法是不会被其他 Dart 代码打断的。当 Dart 应用开始的标志是它的 main isolate 执行了 main 方法。当 main 方法退出后,main isolate 的线程就会去逐一处理中的消息。
有了消息队列,然后有了循环去读取消息队列中的消息,就可以有单线程去执行异步消息的能力。一般的消息使用 dart:async 中使用 Future 来支持异步消息。
显示器以固定的频率刷新,比如 iPhone 的 60Hz、iPad Pro 的 120Hz。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(VSync)。60Hz 的屏幕就会一秒内发出 60 次这样的信号。
计算机系统中,CPU、GPU 和显示器以一种特定的方式协作:CPU 将计算好的显示内容提交给 GPU,放入帧缓冲区,然后视频控制器按照 VSync 信号从帧缓冲区取帧数据传递给显示器显示。
Flutter 只关心向 GPU 提供视图,GPU 的 VSync 信号同步到 UI 线程,UI 线程使用 Dart 来构建抽象的视图结构。这份数据结构在 GPU 线程进行图层合成,视图数据提供给 Skia 引擎渲染为 GPU 数据,这些数据通过 OpenGL 或者 Vulkan 提供给 GPU。
Flutter 并不关心显示器、视频控制器以及 GPU 具体工作,它只关心 GPU 发出的 VSync 信号,尽可能快地在两个 VSync 信号之间计算并合成视图数据,并且把数据提供给 GPU。
在 Flutter 界面渲染过程分为 3 个阶段:布局、绘制、合成。 而布局阶段,有三个重要的对象:RenderObject、Element、Widget。
Element 持有真正负责布局、绘制和碰撞测试 (hit test) 的 RenderObject 对象。
如果控件的属性发生了变化(因为控件的属性是只读的,变化也就意味着重新创建了新的控件树),但是其树上每个节点的类型没有变化时,element 树和 render 树可以完全重用原来的对象(因为 element 和 render object 的属性都是可变的)。
传统布局,如 Android 可能需要多次 Measure,计算宽高。Flutter 采用约束进行单次测量布局。整个布局过程中只需要深度遍历一次,极大的提升效能。
渲染对象树中的每个对象都会在布局过程中接受父对象的 Constraints 参数,决定自己的大小,然后父对象就可以按照自己的逻辑决定各个子对象的位置,完成布局过程。
子对象不存储自己在容器中的位置,所以在它的位置发生改变时并不需要重新布局或者绘制。子对象的位置信息存储在它自己的 parentData 字段中,但是该字段由它的父对象负责维护,自身并不关心该字段的内容。
同时也因为这种简单的布局逻辑,Flutter 可以在某些节点设置布局边界 (Relayout boundary),即当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然。
Flutter 的高性能主要得益于其独特的架构设计。首先,通过自绘引擎(Engine + Skia)避免了系统原生控件的桥接损耗;其次,Dart 语言的单线程模型配合 Isolate 机制有效管理了并发与内存;再次,AOT 编译保证了发布版本的执行效率,JIT 编译提升了开发体验;最后,Widget-Element-RenderObject 三层架构结合约束布局算法,实现了高效的视图更新与渲染。这些机制共同作用,使得 Flutter 能够接近原生应用的流畅度。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online