一文读懂Flutter的三棵树渲染机制和原理

Flutter是一个优秀的UI框架,借助它开箱即用的Widgets咱们可以构建出漂亮和高性能的用户界面。那这些Widgets究竟是如何工做的又是如何完成渲染的。html

在本文中呢,咱们就来探析Widgets背后的故事-Flutter渲染机制之三棵树。缓存

目录

  • 什么是三棵树?
  • 三棵树的协同
  • 三棵树的工做原理

什么是三棵树?

在Flutter中和Widgets一块儿协同工做的还有另外两个伙伴:Elements和RenderObjects;因为它们都是有着树形结构,因此常常会称它们为三棵树。markdown

  • Widget:Widget是Flutter的核心部分,是用户界面的不可变描述。作Flutter开发接触最多的就是Widget,能够说Widget撑起了Flutter的半边天;
  • Element:Element是实例化的 Widget 对象,经过 Widget 的 createElement() 方法,是在特定位置使用 Widget配置数据生成;
  • RenderObject:用于应用界面的布局和绘制,保存了元素的大小,布局等信息;

初次运行时的三棵树的

初步认识了三棵树以后,那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()被调用时,第一时间会在后台发生如下事件:less

  • Flutter会构建包含这三个Widget的Widgets树;
  • Flutter遍历Widget树,而后根据其中的Widget调用createElement()来建立相应的Element对象,最后将这些对象组建成Element树;
  • 接下来会建立第三个树,这个树中包含了与Widget对应的Element经过createRenderObject()建立的RenderObject;

下图是Flutter通过这三个步骤后的状态:ide

Flutter三棵树

从图中能够看出Flutter建立了三个不一样的树,一个对应着Widget,一个对应着Element,一个对应着RenderObject。每个Element中都有着相对应的Widget和RenderObject的引用。能够说Element是存在于可变Widget树和不可变RenderObject树之间的桥梁。Element擅长比较两个Object,在Flutter里面就是Widget和RenderObject。它的做用是配置好Widget在树中的位置,而且保持对于相对应的RenderObject和Widget的引用。工具

三棵树的做用

简而言之是为了性能,为了复用Element从而减小频繁建立和销毁RenderObject。由于实例化一个RenderObject的成本是很高的,频繁的实例化和销毁RenderObject对性能的影响比较大,因此当Widget树改变的时候,Flutter使用Element树来比较新的Widget树和原来的Widget树:oop

//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。

更新时的三棵树

由于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的配置也会发生对应的变化,而后它会从新渲染,其余的对象都保持不变。

Flutter三棵树

注意这三个树,配置发生改变以后,Element和RenderObject实例没有发生变化。

上面这个过程是很是快的,由于Widget的不变性和轻量级使得他能快速的建立,这个过程当中那些重量级的RenderObject则是保持不变的,直到与其相对应类型的Widget从Widget树中被移除。

当Widget的类型发生改变时

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

和刚才流程同样,Flutter会重新Widget树的顶端向下遍历,与原有树中的Widget类型进行对比。

Flutter三棵树

由于FlatButton的类型与Element树中相对应位置的Element的类型不一样,Flutter将会从各自的树上删除这个Element和相对应的ContainerRender,而后Flutter将会重建与FlatButton相对应的Element和RenderObject。

Flutter三棵树

当新的RenderObject树被重建后将会计算布局,而后绘制在屏幕上面。Flutter内部使用了不少优化方法和缓存策略来处理,因此你不须要手动来处理这些。

以上即是Flutter的总体渲染机制,能够看出Flutter利用了三棵树很巧妙的解决的性能的问题。

参考

相关文章
相关标签/搜索