深刻理解 Flutter 框架层次结构

做者: Frederik Schweiger
连接 : The Layer Cake算法

Flutter 是一个很是优秀的跨平台开发框架,基于 Flutter 咱们能够用不多的代码快速的开发出界面精美的 APP ,同时热重载机制也极大的提升了咱们的开发效率,而且基于 Flutter 开发的 APP 运行起来也是如丝般顺滑,可以达到 120 fps。那么,你对此有没有过疑问,Flutter 是怎么这么快的?这里面有什么奥秘吗?或者,更直接一点,Flutter 究竟是怎么工做的?接下来的内容但愿可以给你答案。编程

你确定早就据说过了,Flutter 中万物皆是 Widget,你的 app 是一个 widget,text 是一个widget,一个 widget 外面包裹的 padding 也是一个 widget ,甚至手势识别的功能也是经过 widget 来实现的。固然,这不是所有的真相,若是我告诉你,确实经过使用 widget 让咱们的开发变得简单高效,可是咱们可以建立一个 Flutter app 不使用哪怕是一个 widget 呢?接下来,就让我稍微深刻的探索一下这个框架吧。canvas

1、框架入门

也许你已经在一些相似于‘Flutter入门介绍’的文章中对Flutter有了大体的了解,可是你可能尚未准备好去理解这些层次结构背后的那些概念和原理,也许你也曾和我同样,呆呆的看着这张图片却得不到任何东西,不用担忧,我会帮助你来理解,咱们仍是先来看一下这个图片吧。缓存

Flutter 框架由许多抽象的层级组成,在这些层级的最顶端是咱们常常用到的 Material 和 Cupertino Widget,这下面是封装更通用组件的 Widget 层。一般状况下,你会发现你仅仅使用这两层中的 Widget 就够用了,而且目前你所能看到或者使用的,也基本都来自这两层,好比页面脚手架组件 Scaffold 和 FlaotingActionButton 来自 Meterial 包,Column 和 GestureDector 来自 widgets 层。微信

在Widget层下面是 Rendering 层。Rendering层简化了布局和绘制过程。它是 dart:ui 层的抽象化。dart:ui是框架的最底层,它负责处理与 Flutter Engine 的通讯。app

简而言之,层级越高,封装度越高,咱们使用越方便,而后层级越低使用起来可能更加自由,控制粒度更精细,固然也会更加复杂。框架

一、dart:ui 层

dart:ui library 暴露了最底层的服务,Flutter 框架基于这一层来构建应用程序,好比输入驱动、,绘制文字,布局和渲染系统等。less

因此你能够仅仅经过使用 dart:ui库中的类(例如Canvas、Paint和TextField)来构建一个Flutter App。可是若是你对于直接在 canvas 上绘制比较熟悉,你就知道了绘制不只仅是绘制一个简笔画图像,首先绘制管理就让人头疼,同时还要考虑管理和组织如何布局,而且当你触摸了你 app 上的元素,你还要可以计算出来并响应(hit-testing)。布局

那这究竟意味着什么呢?意味着你必需要精确的计算出你布局中全部元素的坐标,而后还要实现绘制到屏幕的功能,同时可以对外界的输入事件(触摸屏幕)可以捕获到作出响应。对于屏幕上绘制的每一帧,你都要实现这些功能,经过这种方式来开发的 APP,若是只是在一个蓝色区块上显示一个文本这种简单的界面应用,可能还能实现,可是若是是一个购物应用程序或者一个游戏,这可能就是不可能实现的了,更不要说本身去处理动画和实现复杂精美的界面效果了。我只是把个人经验告诉你,若是用这种方式开发这将是咱们开发者的噩梦。post

二、 Rendering 层

Rendering 层,也就是 Flutter 的渲染树(Flutter rendering) 。Widgets 层 使用 RenderObject 来实现了布局和绘制。一般状况下,若是你是使用自定义 RenderBox 来实现一些特殊效果,用到的就是这一层提供的类,可是大多数状况下,咱们和这层的交互都是当咱们遇到了一个布局问题而 debug 时。

Rendering 层是 dart:ui 层上的第一个抽象层,同时这层替咱们作了所有发的复杂的数学计算工做(好比持续追踪计算元素的坐标值),Rendering 层是经过 RenderObjects 来完成工做的。你能够把 RenderObjects 想象成汽车的发动机,咱们的 app 可以真的的显示到屏幕上就是经过RenderObjects 来完成的,由 RenderObjects 所组成的渲染树会被 Flutter 分层布局并渲染到屏幕上。固然为了优化这些复杂的流程,Flutter 也使用了一些智能算法,会缓存每次的计算结果,每次都只更新很小的部分。

一般状况下,Flutter 使用 RenderBox 而不是 RenderObject,这是由于 Flutter 开发者发现,简单的盒约束布局模型也可以很好的完成工做构建精美的界面,想象一下,每一个 widget 都有本身盒子约束模型,在一个盒子当中,计算了其约束关系和大小等,而后把这个 widget 加入到一个已经计算好盒子系统当中,这里面,若是有一个 widget 发生了改变,系统只须要从新计算这个 widget 所在盒子的约束关系便可。

三、 Widgets 层

Flutter Widgets 框架层

Widgets 这层是颇有意思的一层,这层给咱们提供了能够直接使用的 UI 组件,这里面全部的组件均可以分红三类,每一类都有对应的 RenderObject 来处理。

  • 布局相关(layout)

好比 Column 和 Row ,这两个 Widget 能够帮咱们轻松的实现水平或者垂直排列组件。

  • 绘制相关(Parning)

好比 Text 和 Image ,这类的组件能够帮咱们展现或者说渲染一些内容到屏幕上。

  • 手势检测相关 好比 GestureDetector 组件,这个组件可以检测到屏幕点击或者滑动事件。

一般来讲,咱们都是经过使用这些基础组件来组成咱们本身的组件或者组件树,好比咱们可使用 GestureDetector 包裹 Container 来监听点击事件从而实现一个按钮组件。

四、 Material & Cupertino 层

这层的 Widget 是 Widgets 层的封装,只不过是实现了 Material 和 Cupetino 风格的 Widget 提供给咱们使用。

总的来讲,Flutter 框架的设计思想就是抽象和封装,这可让咱们开发者开发更加方便。这层是 Flutter 框架的第四层,这里面的组件都是 Flutter 框架封装好的提供给咱们使用,Material 是材料设计风格的而 Cupetino 是 iOS 风格的,好比 AlertDialog、Switch 和 FloatingActionButton ,若是你是 iOS 开发者,那么你可使用 CupertinoAlertDialog 、CupertinoButton 和 CupertinoSwitch ,这些组件你可能看起来更熟悉。

Flutter为了减小开发者的负担,建立了这个拥有Material和Cupertino风格的Widgets层。

2、综合分析

思考一下,RenderObject 是如何与 Widgets 联系起来的呢? Flutter是如何建立布局的?Element又是什么呢?

上面已经对框架结构进行了简单的介绍,接下来看一下真的的实践,好比下面简单的 控件树。

ps: 咱们实际开发中使用的 widget,好比 Text,都是由许多其余的 Widget 来组成的,为了演示和讲解,这里用抽象的 Widget (SimpleContainer 和 SimpleText) 来代替 。

咱们构建的这个 APP 是很是简单的。它由三个Stateless Widget组成:SimpleApp、SimpleContainer、SimpleText。当咱们调用 Flutter 的 runApp() 方法会发生什么呢?

当 Flutter 运行 runApp() 以后,这背后将会运行下面一些列事情:

一、Flutter 将会构建包含这三个 Widget 的 Widgets 树。

二、Flutter 遍历 Widget 树,经过 Widget 里面的 createElement 方法来建立其关联的 Element 对象,而后将这些对象组建成 Element 树。

三、最后 ,Element 会调用 createRenderObject() 方法来建立想关联的 RenderObject 对象,并组建第三棵树,RenderObject 树。

当 Flutter 建立好了三个对应的树以后,能够用以下图片来描述:

此时,Flutter 建立了三个不一样的树,一个是 Widget 树、一个是 Element 树,一个是 RenderObject 树,而且,每一个 Element 都会持有对应的 Widget 和 RenderObject 的引用。

接下来看一下什么是 RenderObject 。

RenderObject 里面实现了渲染其对应的 widget 的全部逻辑,同时 RenderObject 对象的实例化是一个很是重量级的操做,不只如此,这里面还要实现布局、渲染以及手势检测。所以,一个 RenderObject 对象的实例应该尽量的缓存到内存中,直到不用以后再回收它们。

接下来就该说到 Element 了,Element 其实就像是 RenderObject 和 Widget 之间的粘合剂,Widtet 是不可变的,而 RenderObejct 是可变的。Element 能够看做是一个 Widget 在 Widget 树中特定位置的一个实例,Element 也同时持有其关联的 Widget 和 RenderObject 的引用。

为何使用三个树而不是一个树呢?简而言之是为了性能。当 Widget 树改变的时候,Flutter使用 Element 树来比较新的 Widget 树和已经建立的 RenderObject 树。当一个 Widget 的类型和上一次的 Widget 类型相同,那么 Flutter 就不会从新建立 RenderObject 了(太耗性能),只要更新一下其变化的参数配置就好了。因为 Flutter 中 widget 的建立和销毁都是轻量级的操做,所以用 widget 来作作为配置参数来描述当前 app 的状态再好不过了。而 RenderObject 的建立是一个重量级的操做,所以 RenderObejct 要尽量的复用。

然而,在Flutter 框架中,Element是被抽离开来的,因此咱们不须要常常和它们打交道。每一个Widget的 build(BuildContext context)方法中传递的 context 就是实现了BuildContext 接口的 Element,这也就是为何相同类别的单个Widget 会不一样的缘由。

3、深刻分析

由于 Widget 是不可变的,当某个 Widget 的配置改变的时候,整个 Widget 树都须要被重建。例如当咱们把咱们上面代码里面的 Container 的颜色为红色的时候,Flutter 框架就会触发重建整个 Widget 树的操做。而后在 Element 的帮助下,Flutter 会比较新的 Widget 树中的第一个 Widget 和 RenderObject 树中第一个 RenderObject 。接下来比较Widget树中第二个 Widget 和RenderObject 树中第二个 RenderObject,以此类推,直到 Widget 树和 RendObject 树比较完成。

Flutter 会遵循一个最基本的原则:判断新建立的 Widget 和老的 Widget 是不是同一个类型。若是不是同一个类型,那就把 Widget、Element、RenderObject 分别从它们的树(包括它们的子树)上移除,而后建立新的对象。若是是一个类型,那就仅仅修改 RenderObject中的配置,而后接着遍历其余对象。

在咱们的例子中,SimpleApp Widget 是和原来同样的类型,它的配置也是和原来的 SimpleAppRender 同样,因此什么都不会发生。下一个 item 在 Widget 树中是 SimpleContainer Widget,它的类型和原来是同样的,可是它的颜色变化了,RenderObject的配置发生变化了。由于SimContainer 仍然须要一个SimpleContainerRender 来渲染,所以 Flutter 只是更新了SimpleContainerRender的颜色属性,而后要求它从新渲染。其余的对象保持不变。

当 widget 树从新建立以后的状态以下图所示(须要注意的是,Element 和 RenderObject 依然是同一个实例对象)

这个过程是很是快的,由于Flutter 很是擅长建立那些轻量级的 Widgets。那些重量级的 RenderObject 则是保持不变,直到与其相对应类型的 Widget 从 Widget 树中被移除。那若是Widget的类型发生改变了会发生什么?

好比将 SimpleText 替换为 SimpleButton

和上面同样,Flutter 会重建 Widget 树,并遍历这棵树,而后比较 Widget 的类型是否和 RenderObject 中的对象对应。

此时三颗树的状态以下所示,改变类型的 Widget 对应的 Element 和 SimpleTextRender 已经被移除了

由于SimpleButton 的类型与 Element 树中相对应位置的 Element 的类型不一样,Flutter将会从另外两棵树上删除对应的 Element 和相对应的 SimpleTextRender。而后Flutter 将会重建与 SimpleButton 相对应的 Element 和 RenderObject 对象实例。

最终的状态以下

而后新的 RenderObject 树已经被重建,而后从新布局并绘制在屏幕上面。在这里面,Flutter 作了大量优化工做而且其使用的缓存策略,可以让咱们没必要手动的对这些对象进行管理。

4、总结

如今你应该对 Flutter 是如何工做的,为何可以渲染复杂的布局而且运行的还很流畅有了必定的了解,这里面尚未讲到 State,Flutter 引入了 State 来提高了这个过程的总体性能,今天暂时不对此进行讨论了,但愿这篇文章可以帮助你理解 Flutter 框架。


推荐阅读

Widget、RenderObject 与 Element


欢迎关注「Flutter 编程开发」微信公众号 。

相关文章
相关标签/搜索