1架构理解
谈到跨平台一般绕不过web
、hybird
、react native
等框架,其中RN
是目前比较主流的跨平台解决方案。我们拿RN
和flutter
做一下对比。
1.1跨平台架构简介
1.1.1 React Native:
RN
可以让JS开发者使用JS代码就可以写出一个跨平台的App,同时RN
需要一个JS运行环境,在iOS中使用的是内置的javascriptcore
,在android使用的是webkit.org
官方开源的jsc.so
,搭建UI只需要在虚拟DOM中,之后通过框架转换对应到native
的视图上,需要一个和native
通信的则使用Bridge
进行异步通信。
1.1.2 flutter :
flutter的架构由两部分组成 分别是framework
和Engine
:
1.1.2.1framework
framework
是一个Dart
库 包括组件、手势、动画、有Material
和Cupertino
风格,是我们最常打交道的库。开发中的接大部分需求framework
给我们提供的库都可以满足。
1.1.2.2Engine
Engine
是C/C++
编写的库,是flutter
的核心库,包含了Dart
虚拟机、动画和图形、文字渲染、通信通道等,其中渲染引擎采用的是2D的图形渲染库Skia
,虚拟机是Dart VM,另外还包含了Embedder
中间层代码。
1.2RN和flutter对比
简单了解了RN
和Flutter
的架构,我们就能很直观的看到两者之间差异,至于为什么同是跨平台语言,Flutter
性能优于RN
、近似Native
,我们看一下下面这张图:
1.2.1flutter
构建一个flutter
项目,我们通过dart
语言开发,使用flutter framework
提供的库便可以,之后交于Engine
中的Skia
引擎做图像的绘制,最后生成的CPU
或者GPU
的指令,在设备上完成绘图。整个过程和Native
的流程一模一样。
1.2.2 Android
从上图可以看出flutter
框架代码完全取代了Android
的Java
代码,所以只要flutter
框架dart
代码的效率可以媲美原生框架的Java
代码的时候,那么flutter
的性能就可以媲美原生app。
1.2.3 iOS
在iOS
中和Skia
起到相似作用的是Core Graphic / Core Animation
,因为Skia
是一个google开源的2D图像库,所以直接用Skia来代替Core Graphic / Core Animation
来做图形的绘制即可,因为要把Skia
集成到项目中去,所以同一个项目iOS
打出来的包是要比Android
要大一些。
1.2.4 RN
它首先要调用框架本身的JavaScript
代码,然后再调用Android(iOS)
框架的Java(OC)
代码,然后调用skia(Core Graphic / Core Animation)
,这比原生的APP调用方式多出来一步,必然会产型性能上的损耗。
1.2.5 android 低端机
flutter
开发的app在一些低端机型上的性能优于android
原生app。
因为在原生app中,skia
图形引擎是作为android
操作系统的一部分,只要等操作系统升级,skia
才会升级得到最新的优化。
在flutter
中skia
是包含在flutter SDK
中的,只要flutter SDK
升级,skia
也会随着升级,目前flutter
处于快速迭代期,skia
上最新的优化和改进会很快的应用到flutter
项目中。android
系统的升级相对慢,且对低端机的升级也不是太友好,所以skia
的优化在原生app上是不得到及时的体现。
1.3flutter和native
经过上面的分析,我们再看下面这幅图基本上就可以清晰的看出flutter和native的对比和优势:
2、图像绘制
2.1基本概念
帧率:
GPU一秒内绘制图像的次数(skia可以达到120帧)
刷新率:
屏幕显示图像的数量60帧/秒 (现在机型能达到90帧或者120帧),人眼可接受的最低是12帧。
两者关系:
GPU提供数据 存放在Buffer中,显示器从Buffer中获取图像显示在屏幕,理想情况下,帧率和刷新率一直,实现画面流畅显示。
tearing:
通常情况下帧率是大于刷新率的,在这种情况下,第n张图片在屏幕上加载完成,n+1就已经出来了,会出现撕裂tearing
。
Vsync:
为了解决tearing
的情况,就需要引入Vsync
垂直同步信号,在屏幕刷新两帧之间会有一个VBlank
,这个间隔就会产生一个Vsync
信号,GPU/CPU
在收到信号之后再去计算下一帧画面,这样就能够避免tearing
,就算没有产生tearing
也建议使用Vsync
,普遍情况下帧率是要高于刷新率的,为了避免GPU/CPU
过快的渲染结果,产生冗余的计算。
Jank:
与tearing
相对应的就是jank
,这是由于在CPU/GPU
在收到Vsync
之后没有在16.6ms(以60帧/秒为例)提供渲染结果供屏幕显示,就会出现jank卡顿,这种情况下,就需要优化代码(isolate),或者做三级缓存(cache Buffer)在这里就不详细介绍。
2.2flutter渲染
2.2.1渲染过程
- framework 注册监听
engine
的vsync
信号
2.engine
注册监听GPU的vsync
信号 - GPU收到屏幕的
vsync
信号通知engine
4.engine
通知framework - 通过framework完成页面的重建、布局、绘制、合成,最终交由
engine
- GPU存放到buffer中,等待屏幕来获取,获取之后,屏幕发出
vsync
信号,重复3-6步骤。
2.2.2 framework中的流程
framew中的操作是开发具体操作的流程,大致步骤如下图:
Layout
确定每个widget的宽高和在屏幕中的位置
Constraints go down. Sizes go up. Parent sets position.
约束条件向下传,大小信息向上传,父视图决定位置
节点在组建视图树的时候,每个节点的Constraint
是自上而下,大小位置是从内到外的,应为节点自己的大小是由子节点size
决定的,所以获取size
必须从下到上,从里到外。
通过下面的例子就很好理解了(来源flutter官网):
# 整个屏幕作为 Container 的父级,并且强制 Container 变成和屏幕一样的大小。
# 所以这个 Container 充满了整个屏幕,并绘制成红色。
Container(color: Colors.red)
#红色的 Container 想要变成 100 x 100 的大小,但是它无法变成,因为屏幕强制它变成和屏幕一样的大小。
#所以 Container 充满了整个屏幕。
Container(width: 100, height: 100, color: Colors.red)
#屏幕强制 Center 变得和屏幕一样大,所以 Center 充满了屏幕。
#然后 Center 告诉 Container 可以变成任意大小,但是不能超出屏幕。现在,Container 可以真正变成 100 × 100 大小了。
Center(
child: Container(width: 100, height: 100, color: Colors.red)
)
Relayout boundary
通过上面的layout结构来看,一个节点的size
变化了,整个树结构都需要从新计算,为了避免这个问题使用Relayout boundary
来解决这个问题。
Relayout boundary
的作用是设置测量边界,边界内的Widget做任何改变都不会导致边界外重新计算并绘制。这个机制是在一些特定的情况下系统帮你设置的,一般情况下不需要开发手动来设置。
-
widget
设置了固定的size
(constraints.isTight (min = max)
) - 父
widget
是不需要依赖子widget
的size
(parentUsesSize == false
),这种情况下子widget
重新布局的时候就不会通知父widget
,布局的边界就是自身。 - 子
widget
自动占满父widget
所提供的空间大小(sizedByParent == true
),比如(Expanded/Center
)。
以上三种情况,就会自动触发了Relayout boundary
机制,自动隔绝了父子组件的联动刷新。
默认情况下,开发不要直接去设置Relayout boundary
,只需要触发上诉三个条件之一即可。
Paint
为widget提供画布,让widget进行绘制
在iOS中每一个UIView
都有一个layer
,UIView
和UIView
之间是相互隔离的。但是flutter中的renderObject
不一定有layer
,通常情况下,一个renderObject
的子节点都是渲染在同一个layer
上,这就会出现以下一种情况:
- 节点2、3、4、5、6 是节点1的子节点,应该绘制在节点1所在的绿色layer上。
- 如果节点4(video)需要单独占据一个layer,这个时候就4就会单独占据一个黄色layer。
- 节点2上面还有部分内容需要绘制,顶部黄色layer是4单独占据的,需要新的红色layer来绘制节点2上面剩余的部分内容5。
- 6也会绘制在顶层红色layer上,这样红色layer上就会存在5和6的共同数据。
- 如果节点2发生改变红色layer会重绘,这样会影响毫不相干的节点6
Repaint Boundary
为了应对上诉情况,flutter
设计了和Relayout Boundary
相同思想的Repaint Boundary
,在遇上上诉问题的时候 就强制添加新的layer
给节点6添加父节点RepaintBoundary widget
,在创建的时候遇到RepaintBoundary widget
就会添加新的layer
,实现节点layer
的隔离。即使发生重绘也不会其他子节点产生影响。
Composite
屏幕上看到的界面一般都是有很多的layer一层一层叠加在一起的,在这一步
把绘制好的多个layer合成为一个layer进行光栅化处理,然后交付给GPU处理,提高效率。
总结
flutter是一款非常优秀的框架,在跨平台上直截了当,和Native的切割也十分干净,flutter已经发布了2.0版本,支持空安全优化了web端的支持,期待flutter发展的越来越好。