作者: 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 呢?接下来,就让我稍微深入的探索一下这个框架吧。
一、框架入门
也许你已经在一些类似于‘Flutter入门介绍’的文章中对Flutter有了大致的了解,但是你可能还没有准备好去理解这些层次结构背后的那些概念和原理,也许你也曾和我一样,呆呆的看着这张图片却得不到任何东西,不用担心,我会帮助你来理解,我们还是先来看一下这个图片吧。
Flutter 框架由许多抽象的层级组成,在这些层级的最顶端是我们经常用到的 Material 和 Cupertino Widget,这下面是封装更通用组件的 Widget 层。通常情况下,你会发现你仅仅使用这两层中的 Widget 就够用了,并且目前你所能看到或者使用的,也基本都来自这两层,比如页面脚手架组件 Scaffold 和 FlaotingActionButton 来自 Meterial 包,Column 和 GestureDector 来自 widgets 层。
在Widget层下面是 Rendering 层。Rendering层简化了布局和绘制过程。它是 dart:ui 层的抽象化。dart:ui是框架的最底层,它负责处理与 Flutter Engine 的通信。
简而言之,层级越高,封装度越高,我们使用越方便,然后层级越低使用起来可能更加自由,控制粒度更精细,当然也会更加复杂。
1、dart:ui 层
dart:ui library 暴露了最底层的服务,Flutter 框架基于这一层来构建应用程序,比如输入驱动、,绘制文字,布局和渲染系统等。
所以你可以仅仅通过使用 dart:ui库中的类(例如Canvas、Paint和TextField)来构建一个Flutter App。但是如果你对于直接在 canvas 上绘制比较熟悉,你就知道了绘制不仅仅是绘制一个简笔画图像,首先绘制管理就让人头疼,同时还要考虑管理和组织如何布局,并且当你触摸了你 app 上的元素,你还要能够计算出来并响应(hit-testing)。
那这究竟意味着什么呢?意味着你必须要精确的计算出你布局中所有元素的坐标,然后还要实现绘制到屏幕的功能,同时能够对外界的输入事件(触摸屏幕)能够捕获到做出响应。对于屏幕上绘制的每一帧,你都要实现这些功能,通过这种方式来开发的 APP,如果只是在一个蓝色区块上显示一个文本这种简单的界面应用,可能还能实现,但是如果是一个购物应用程序或者一个游戏,这可能就是不可能实现的了,更不要说自己去处理动画和实现复杂精美的界面效果了。我只是把我的经验告诉你,如果用这种方式开发这将是我们开发者的噩梦。
2、 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 所在盒子的约束关系即可。
3、 Widgets 层
Flutter Widgets 框架层
Widgets 这层是很有意思的一层,这层给我们提供了可以直接使用的 UI 组件,这里面所有的组件都可以分成三类,每一类都有对应的 RenderObject 来处理。
- 布局相关(layout)
比如 Column 和 Row ,这两个 Widget 可以帮我们轻松的实现水平或者垂直排列组件。
- 绘制相关(Parning)
比如 Text 和 Image ,这类的组件可以帮我们展示或者说渲染一些内容到屏幕上。
- 手势检测相关 比如 GestureDetector 组件,这个组件能够检测到屏幕点击或者滑动事件。
通常来说,我们都是通过使用这些基础组件来组成我们自己的组件或者组件树,比如我们可以使用 GestureDetector 包裹 Container 来监听点击事件从而实现一个按钮组件。
4、 Material & Cupertino 层
这层的 Widget 是 Widgets 层的封装,只不过是实现了 Material 和 Cupetino 风格的 Widget 提供给我们使用。
总的来说,Flutter 框架的设计思想就是抽象和封装,这可以让我们开发者开发更加方便。这层是 Flutter 框架的第四层,这里面的组件都是 Flutter 框架封装好的提供给我们使用,Material 是材料设计风格的而 Cupetino 是 iOS 风格的,比如 AlertDialog、Switch 和 FloatingActionButton ,如果你是 iOS 开发者,那么你可以使用 CupertinoAlertDialog 、CupertinoButton 和 CupertinoSwitch ,这些组件你可能看起来更熟悉。
Flutter为了减少开发者的负担,创建了这个拥有Material和Cupertino风格的Widgets层。
二、综合分析
思考一下,RenderObject 是如何与 Widgets 联系起来的呢? Flutter是如何创建布局的?Element又是什么呢?
上面已经对框架结构进行了简单的介绍,接下来看一下真的的实践,比如下面简单的 控件树。
ps: 我们实际开发中使用的 widget,比如 Text,都是由许多其他的 Widget 来组成的,为了演示和讲解,这里用抽象的 Widget (SimpleContainer 和 SimpleText) 来代替 。
我们构建的这个 APP 是非常简单的。它由三个Stateless Widget组成:SimpleApp、SimpleContainer、SimpleText。当我们调用 Flutter 的 runApp() 方法会发生什么呢?
当 Flutter 运行 runApp() 之后,这背后将会运行下面一些列事情:
1、Flutter 将会构建包含这三个 Widget 的 Widgets 树。
2、Flutter 遍历 Widget 树,通过 Widget 里面的 createElement 方法来创建其关联的 Element 对象,然后将这些对象组建成 Element 树。
3、最后 ,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 会不同的原因。
三、深入分析
因为 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 做了大量优化工作并且其使用的缓存策略,能够让我们不必手动的对这些对象进行管理。
四、总结
现在你应该对 Flutter 是如何工作的,为什么能够渲染复杂的布局并且运行的还很流畅有了一定的了解,这里面还没有讲到 State,Flutter 引入了 State 来提升了这个过程的整体性能,今天暂时不对此进行讨论了,希望这篇文章能够帮助你理解 Flutter 框架。
推荐阅读
欢迎关注「Flutter 编程开发」微信公众号 。
作者:Flutter编程开发
链接:https://juejin.cn/post/6854573211108966408
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。