乐趣区

关于flutter:Flutter的渲染机制和原理

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 外部应用了很多优化办法和缓存策略来解决,所以你不须要手动来解决这些。

退出移动版