1. Flutter渲染机制之三棵树

在Flutter中和Widgets一起协同工作的还有另外两个搭档:Elements和RenderObjects;因为它们都是有着树形构造,所以常常会称它们为三棵树。

  • Widget:Widget是Flutter的外围局部,是用户界面的不可变形容。做Flutter开发接触最多的就是Widget,能够说Widget撑起了Flutter的半边天;
  • Element:Element是实例化的 Widget 对象,通过 Widget 的 createElement() 办法,是在特定地位应用 Widget配置数据生成;
  • RenderObject:用于利用界面的布局和绘制,保留了元素的大小,布局等信息;

2. 首次运行时的三棵树

初步意识了三棵树之后,那Flutter是如何创立布局的?以及三棵树之间他们是如何协同的呢?接下来就让咱们通过一个简略的例子来分析下它们外在的协同关系:

class ThreeTree extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Container(      color: Colors.red,      child: Container(color: Colors.blue)    );  }}

下面这个例子很简略,它由三个Widget组成:ThreeTree、Container、Text。那么当Flutter的runApp()办法被调用时会产生什么呢?

当runApp()被调用时,第一工夫会在后盾产生以下事件:

Flutter会构建蕴含这三个Widget的Widgets树;
Flutter遍历Widget树,而后依据其中的Widget调用createElement()来创立相应的Element对象,最初将这些对象组建成Element树;
接下来会创立第三个树,这个树中蕴含了与Widget对应的Element通过createRenderObject()创立的RenderObject;
下图是Flutter通过这三个步骤后的状态:

从图中能够看出Flutter创立了三个不同的树,一个对应着Widget,一个对应着Element,一个对应着RenderObject。每一个Element中都有着绝对应的Widget和RenderObject的援用。能够说Element是存在于可变Widget树和不可变RenderObject树之间的桥梁。Element善于比拟两个Object,在Flutter外面就是Widget和RenderObject。它的作用是配置好Widget在树中的地位,并且放弃对于绝对应的RenderObject和Widget的援用。

3. 三棵树的作用

简而言之是为了性能,为了复用Element从而缩小频繁创立和销毁RenderObject。因为实例化一个RenderObject的老本是很高的,频繁的实例化和销毁RenderObject对性能的影响比拟大,所以当Widget树扭转的时候,Flutter应用Element树来比拟新的Widget树和原来的Widget树:

//framework.dart @protected  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {    if (newWidget == null) {      if (child != null)        deactivateChild(child);      return null;    }    Element newChild;    if (child != null) {      assert(() {        final int oldElementClass = Element._debugConcreteSubtype(child);        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);        hasSameSuperclass = oldElementClass == newWidgetClass;        return true;      }());      if (hasSameSuperclass && child.widget == newWidget) {        if (child.slot != newSlot)          updateSlotForChild(child, newSlot);        newChild = child;      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget))       {        if (child.slot != newSlot)          updateSlotForChild(child, newSlot);        child.update(newWidget);        assert(child.widget == newWidget);        assert(() {          child.owner._debugElementWasRebuilt(child);          return true;        }());        newChild = child;      } else {        deactivateChild(child);        assert(child._parent == null);        newChild = inflateWidget(newWidget, newSlot);      }    } else {      newChild = inflateWidget(newWidget, newSlot);    }    assert(() {      if (child != null)        _debugRemoveGlobalKeyReservation(child);      final Key key = newWidget?.key;      if (key is GlobalKey) {        key._debugReserveFor(this, newChild);      }      return true;    }());    return newChild;  }  static bool canUpdate(Widget oldWidget, Widget newWidget) {    return oldWidget.runtimeType == newWidget.runtimeType        && oldWidget.key == newWidget.key;  }

如果某一个地位的Widget和新Widget不统一,才须要从新创立Element;
如果某一个地位的Widget和新Widget统一时(两个widget相等或runtimeType与key相等),则只须要批改RenderObject的配置,不必进行消耗性能的RenderObject的实例化工作了;
因为Widget是十分轻量级的,实例化消耗的性能很少,所以它是形容APP的状态(也就是configuration)的最好工具;
重量级的RenderObject(创立非常消耗性能)则须要尽可能少的创立,并尽可能的复用;
看到这里你是否会感觉整个Flutter APP就像是一个RecycleView呢?

因为在框架中,Element是被抽来到来的,所以你不须要常常和它们打交道。每个Widget的build(BuildContext context)办法中传递的context就是实现了BuildContext接口的Element。

4. 更新时的三棵树

因为Widget是不可变的,当某个Widget的配置扭转的时候,整个Widget树都须要被重建。例如当咱们扭转一个Container的色彩为橙色的时候,框架就会触发一个重建整个Widget树的动作。因为有了Element的存在,Flutter会比拟新的Widget树中的第一个Widget和之前的Widget。接下来比拟Widget树中第二个Widget和之前Widget,以此类推,直到Widget树比拟实现。

class ThreeTree extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Container(      color: Colors.orange,      child: Container(color: Colors.blue,),    );  }}

Flutter遵循一个最根本的准则:判断新的Widget和老的Widget是否是同一个类型:

如果不是同一个类型,那就把Widget、Element、RenderObject别离从它们的树(包含它们的子树)上移除,而后创立新的对象;
如果是一个类型,那就仅仅批改RenderObject中的配置,而后持续向下遍历;
在咱们的例子中,ThreeTree Widget是和原来一样的类型,它的配置也是和原来的ThreeTreeRender一样的,所以什么都不会产生。下一个节点在Widget树中是Container Widget,它的类型和原来是一样的,然而它的色彩变动了,所以RenderObject的配置也会产生对应的变动,而后它会从新渲染,其余的对象都放弃不变。

留神这三个树,配置产生扭转之后,Element和RenderObject实例没有发生变化。

下面这个过程是十分快的,因为Widget的不变性和轻量级使得他能疾速的创立,这个过程中那些重量级的RenderObject则是放弃不变的,直到与其绝对应类型的Widget从Widget树中被移除。

5. 当Widget的类型产生扭转时的三棵树

class ThreeTree extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Container(      color: Colors.orange,      child: FlatButton(        onPressed: () {},        child: Text('三棵树'),      ),    );  }}

和方才流程一样,Flutter会从新Widget树的顶端向下遍历,与原有树中的Widget类型进行比照。

因为FlatButton的类型与Element树中绝对应地位的Element的类型不同,Flutter将会从各自的树上删除这个Element和绝对应的ContainerRender,而后Flutter将会重建与FlatButton绝对应的Element和RenderObject。

当新的RenderObject树被重建后将会计算布局,而后绘制在屏幕下面。Flutter外部应用了很多优化办法和缓存策略来解决,所以你不须要手动来解决这些。