Flutter 高性能原理浅析
[图片上传失败...(image-1a480b-1642738386010)]
北斗星_And关注
1字数 2,470阅读 4,273
这是我第三篇Flutter相关博客
欢迎 查看我的前两篇 Flutter实现篇
- Flutter PIP(画中画)效果的实现
- Flutter 侧滑栏及城市选择UI的实现
前言
Flutter是Google用以帮助开发者在Ios和Android两个平台开发高质量原生应用的全新移动UI框架.我开始认识Flutter时,经历了三个Flutter重要历史版本.
- 2018年2月27日,在2018世界移动大会上,Google发布了Flutter的第一个Beta版本。
- 2018年6月21日,在全球大前端技术大会上,发布了第一个Release Perview 1版本。
- 2018年12月5日第一个Release 1.0版本发布.
此后越来越多的人开始关注到Flutter。
在 Flutter 诞生之前,已经有许多跨平台 UI 框架的方案,比如基于 WebView 的 Cordova、AppCan 等,还有使用 HTML+JavaScript 渲染成原生控件的 React Native、Weex 等。Flutter 则开辟了一种全新的思路,从头到 尾重写一套跨平台的 UI 框架,包括 UI 控件、渲染逻辑甚至开发语言。
Flutter框架
从图中可以看出 Flutter主要被分为两层 Framework层和Flutter Engine.
Framework层全部使用Dart编写,有完整UI框架的API,并预写了Android(MaterialDesign)和IOS的(Cupertino)风格的UI,极大方便了开发移动端.
Framework 底层是 Flutter 引擎, 引擎主要负责图形绘制 (Skia)、 文字排版 (libtxt) 和提供 Dart 运行时, 引擎全部使用 C++实现.
Flutter高性能原理
与其他跨平台框架对比
在看Flutter框架前,我们先看一下其他跨平台框架的设计
看Hybrid的架构,我们可以知道UI层的渲染是基于Webview去渲染,他的性能取决于webview的渲染性能,目前已知webview渲染性能 < NativeUI的性能
RN/Weex 的架构中,是基于Native的UI框架去适配,中间多了一层js转NativeUI的过程
而Flutter不需要中间层(Webview,js 转NativeUI这个过程),他是基于图像渲染引擎去直接绘制UI.
Dart 对于UI框架的高性能支持
我们知道Flutter的Framework层是使用了Dart语言编写,那Dart语言有哪些优势呢?下面分为几个点来阐述
Dart内存分配机制
DartVM的内存分配策略非常简单,创建对象时只需要在现有堆上移动指针,内存增长始终是线形的,省去了查找可用内存段的过程
Dart中类似线程的概念叫做Isolate,每个Isolate之间是无法共享内存的,所以这种分配策略可以让Dart实现无锁的快速分配。
Dart 垃圾回收机制
Dart的垃圾回收也采用了多生代算法,新生代在回收内存时采用了“半空间”算法,触发垃圾回收时Dart会将当前半空间中的“活跃”对象拷贝到备用空间,然后整体释放当前空间的所有内存如图.
整个过程中Dart只需要操作少量的“活跃”对象,大量的没有引用的“死亡”对象则被忽略,这种 多生代无锁垃圾回收器,专门为UI框架中常见的大量Widgets对象创建和销毁优化,非常适合Flutter框架中大量Widget重建的场景.
Dart 编体积优化,及编译JIT和AOT支持
代码体积优化(Tree Shaking),编译时只保留运行时需要调用的代码(不允许反射这样的隐式引用),所以庞大的Widgets库不会造成发布体积过大。
Dart支持两种编译模式:
- JIT编译 Just In Time Compiler -即时编译
- AOT编译Ahead Of Time 预编译
在debug模式下使用JIT编译,生成srcipt/bytecode进行解释执行,可以支持HotReload(热重载),修改代码,保持即可在设备上看到效果. 而在Release下 AOT编译生成Machine Code,高效的运行.
Dart 单线程 异步消息机制
客户端交互简述
对于移动端的交互来说,大多数情况下都是在等待状态,等待网络请求,等待用户输入等.那么设想一下,发起一个网络请求只在一个线程中可以进行吗?当然网络请求肯定是异步的(注意这里说的异步而多线程并非一个概念.),事实验证是可以的,Flutter就采用了Dart这种单线程机制,省去了多线程上下文切换带来的性能损耗.(对于高耗时操作,也同样支持多线程操作,通过Isolate开启,不过注意这里的多线程,内存是无法共享的.)
Dart 异步消息原理
当一个Dart的方法开始执行时,他会一直执行直至达到这个方法的退出点。换句话说Dart的方法是不会被其他Dart代码打断的。
当一个Dart应用开始的标志是它的main isolate执行了main方法。当main方法退出后,main isolate的线程就会去逐一处理消息队列中的消息。
有了消息队列,然后有了循环去读取消息队列中的消息,就可以有单线程去执行异步消息的能力.
一般的消息使用dart:async中使用Future来支持异步消息.
Flutter Engine 高性能
在讲Flutter Engin层时,我们先讲一下屏幕绘制的原理.
屏幕绘制原理
我们都知道显示器以固定的频率刷新,比如 iPhone的 60Hz、iPad Pro的 120Hz。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(VSync),所以 60Hz的屏幕就会一秒内发出 60次这样的信号。
并且一般地来说,计算机系统中,CPU、GPU和显示器以一种特定的方式协作:CPU将计算好的显示内容提交给 GPU,GPU渲染后放入帧缓冲区,然后视频控制器按照 VSync信号从帧缓冲区取帧数据传递给显示器显示。
作为一个专职Android开发,看过Android的绘图机制,通过SurfaceFlinger 和HAL层之间的工作机制发现和Flutter的很像,那么IOS的如何呢?个人推测屏幕的绘图机制是一样的,只是不同平台有不同实现.
Flutter Engine的渲染机制
Flutter只关心向 GPU提供视图数据,GPU的 VSync信号同步到 UI线程,UI线程使用 Dart来构建抽象的视图结构,这份数据结构在 GPU线程进行图层合成,视图数据提供给 Skia引擎渲染为 GPU数据,这些数据通过 OpenGL或者 Vulkan提供给 GPU.
所以 Flutter并不关心显示器、视频控制器以及 GPU具体工作,它只关心 GPU发出的 VSync信号,尽可能快地在两个 VSync信号之间计算并合成视图数据,并且把数据提供给 GPU.
Flutter Framework层的绘图机制
UI树原理
在 Flutter 界面渲染过程分为 3 个阶段: 布局、绘制、合成.
而布局阶段,有三个重要的对象.RenderObject、Element、Widget.
Widget是开发经常接触的控件,默认是只读的.
Element 是 Flutter 用来分离控件树和真正的渲染 对象的中间层, 控件用来描述对应的 element 属性,控件重建后可能会复用同一个 element.
RenderObject 负责提供配置信息并创建具体的 Element。
Element 持有真正负责布局、 绘制和碰撞测试 (hit test) 的 RenderObject 对象.
那么这样,如果控件的属性发生了变化 (因为控件的属性是只 读的, 所以变化也就意味着重新创建了新的控件树), 但是其树上每个节点的类型没有变化时, element 树和 render 树可以完全重用原来的对象 (因为 element 和 render object 的属性都是可变的)
布局原理
传统布局,如Android可能需要多次Measure,计算宽高。Flutter 采用约束进行单次测量布局. 整个布局过程中只需要深度遍历一次,极大的提升效能。
渲染对象树中的每个对象都会在布局过程中接受父 对象的 Constraints 参数,决定自己的大小, 然后父对象 就可以按照自己的逻辑决定各个子对象的位置,完成布局过程.
子对象不存储自己在容器中的位置, 所以在它的位置发生改变时并不需要重新布局或者绘制. 子对象的位 置信息存储在它自己的 parentData 字段中,但是该字段由它的父对象负责维护,自身并不关心该字段的内容。
同时也因为这种简单的布局逻辑, Flutter 可以在某些节 点设置布局边界 (Relayout boundary), 即当边界内的任 何对象发生重新布局时, 不会影响边界外的对象, 反之亦然.