在Flutter诞生之前,已经有许多跨平台UI框架的方案,比如基于WebView的Cordova、AppCan等,还有使用HTML+JavaScript渲染成原生控件的React Native、Weex等。
目前hybrid开发模式:
1.通过WebView来进行原生和web交互
2.为了解决WebView性能差的问题,以React Native为代表的一类框架将最终渲染工作交还给了系统,虽然同样使用类HTML+JS的UI构建逻辑,但是最终会生成对应的自定义原生控件,以充分利用原生控件相对于WebView的较高的绘制效率。
反观Flutter
首先看一下Flutter的架构图
Flutter的架构主要分成三层:Framework,Engine和Embedder。
1.Framework使用dart实现,包括Material Design风格的Widget,Cupertino(针对iOS)风格的Widgets,文本/图片/按钮等基础Widgets,渲染,动画,手势等。此部分的核心代码是:flutter仓库下的flutter package,以及sky_engine仓库下的io,async,ui(dart:ui库提供了Flutter框架和引擎之间的接口)等package。
2.Engine使用C++实现,主要包括:Skia,Dart和Text。Skia是开源的二维图形库,提供了适用于多种软硬件平台的通用API。
3.Embedder是一个嵌入层,即把Flutter嵌入到各个平台上去,这里做的主要工作包括渲染Surface设置,线程设置,以及插件等。从这里可以看出,Flutter的平台相关层很低,平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。
从架构图可以看出,从头到尾重写一套跨平台的UI框架,包括UI控件、渲染逻辑甚至开发语言。渲染引擎依靠跨平台的Skia图形库来实现,依赖系统的只有图形绘制相关的接口,可以在最大程度上保证不同平台、不同设备的体验一致性,逻辑处理使用支持AOT的Dart语言,执行效率也比JavaScript高得多。
万物皆widget
目前上主流的思想,都希望将各个ui控件接耦,慢慢演变出组件化的思想。
Flutter控件主要分为两大类,StatelessWidget和StatefulWidget,StatelessWidget用来展示静态的文本或者图片,如果控件需要根据外部数据或者用户操作来改变的话,就需要使用StatefulWidget。State的概念也是来源于Facebook的流行Web框架React,React风格的框架中使用控件树和各自的状态来构建界面,当某个控件的状态发生变化时由框架负责对比前后状态差异并且采取最小代价来更新渲染结果。
widget效果见这两篇文章 widget 应用一 widget 应用二
接下来主要看下渲染树和flutter引擎绑定在一起,看一下binding.dart、object.dart,分析下flutter是如何渲染的
abstract class RendererBinding extends BindingBase with ServicesBinding, SchedulerBinding, HitTestable { ... }
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
abstract class RenderBox extends RenderObject { ... }
class RenderParagraph extends RenderBox { ... }
class RenderImage extends RenderBox { ... }
class RenderFlex extends RenderBox with ContainerRenderObjectMixin,
RenderBoxContainerDefaultsMixin,
DebugOverflowIndicatorMixin { ... }
创建[RenderView]对象作为[RenderObject]呈现树的根,并对其进行初始化,以便在引擎下次准备显示一个框架时将其呈现。
创建绑定是自动调用
每个框架由以下几个阶段组成:
1. The animation phase:
2. Microtasks:
3. The layout phase: 布局阶段:
布局阶段:系统中所有脏的[RenderObject]都被放置(见[RenderObject.performLayout])。[RenderObject.markNeedsLayout]
有关为布局标记脏对象的详细信息
4. The compositing bits phase: 合成部分阶段
5. The paint phase:绘制阶段:
系统中所有的渲染对象都是重新绘制(见[RenderObject.paint])。这将生成[层]树
6. The compositing phase:
合成阶段:将图层树转换为[场景]和发送到GPU。
7. The semantics phase:语义阶段
8. The finalization phase:结束阶段
看一下代码当中的具体执行
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout(); //1
pipelineOwner.flushCompositingBits();//2
pipelineOwner.flushPaint();//3
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. //4
}
1。更新任何需要计算它们的渲染对象布局。在这个阶段,每个渲染的大小和位置计算对象。渲染对象可能会弄脏他们的画或这一阶段的合成状态。
2。更新有dirty的渲染对象合成部分。在这个阶段,每个呈现对象都知道是否它的任何一个孩子都需要合成。此信息在期间使用
绘画阶段选择如何实现视觉效果等剪裁。如果呈现对象有一个合成的子对象,它需要使用一个
[层]来创建剪辑,以便该剪辑应用于合成子元素(将被绘制到它自己的[图层]中)。
3.访问任何需要绘制的渲染对象。在这阶段,渲染对象有机会记录绘制命令[图片图层],并构建其他合成的[图层]。
4.将编译呈现对象的语义。这个语义信息被使用辅助技术,以改善渲染树的可访问性。
渲染对象树中的每个对象都会在布局过程中接受父对象的Constraints
参数,决定自己的大小,然后父对象就可以按照自己的逻辑决定各个子对象的位置,完成布局过程。子对象不存储自己在容器中的位置,所以在它的位置发生改变时并不需要重新布局或者绘制。子对象的位置信息存储在它自己的parentData
字段中,但是该字段由它的父对象负责维护,自身并不关心该字段的内容。同时也因为这种简单的布局逻辑,Flutter可以在某些节点设置布局边界(Relayout boundary),即当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然:
布局完成后,渲染对象树中的每个节点都有了明确的尺寸和位置,Flutter会把所有对象绘制到不同的图层上:
因为绘制节点时也是深度遍历,可以看到第二个节点在绘制它的背景和前景不得不绘制在不同的图层上,因为第四个节点切换了图层(因为“4”节点是一个需要独占一个图层的内容,比如视频),而第六个节点也一起绘制到了红色图层。这样会导致第二个节点的前景(也就是“5”)部分需要重绘时,和它在逻辑上毫不相干但是处于同一图层的第六个节点也必须重绘。为了避免这种情况,Flutter提供了另外一个“重绘边界”的概念:
在进入和走出重绘边界时,Flutter会强制切换新的图层,这样就可以避免边界内外的互相影响。典型的应用场景就是ScrollView,当滚动内容重绘时,一般情况下其他内容是不需要重绘的。虽然重绘边界可以在任何节点手动设置,但是一般不需要我们来实现,Flutter提供的控件默认会在需要设置的地方自动设置
在Flutter界面渲染过程分为三个阶段:布局、绘制、合成,布局和绘制在Flutter框架中完成,合成则交由引擎负责。
RN的效率由于是将View编译成了原生View,所以效率上要比基于Cordova的HTML5高很多,但是它也有效率问题,RN的渲染机制是基于前端框架的考虑,复杂的UI渲染是需要依赖多个view叠加.比如我们渲染一个复杂的ListView,每一个小的控件,都是一个native的view,然后相互组合叠加.想想此时如果我们的list再需要滑动刷新,会有多少个对象需要渲染.所以也就有了前面所说的RN的列表方案不友好;
Flutter 吸收了前两者的教训之后,在渲染技术上,选择了自己实现(GDI),由于有更好的可控性,使用了新的语言Dart,避免了RN的那种通过桥接器与Javascript通讯导致效率低下的问题,所以在性能方面比RN更高一筹;
ReactNative
Flutter
参考 Flutter原理和美团的实践
https://blog.csdn.net/zhangzeshuai/article/details/80151710