likes
comments
collection
share

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

作者站长头像
站长
· 阅读数 3

一 前言

说明

本文主要讲述的是flutter的构建流程。一步步地去分析Flutter是如何从上往下构建Widget,顺便带出Flutter日常开发中的基本概念。

源码警告⚠️ 本文在分析过程中会夹带部分源码,整体边幅较长。友情提示,通过导航栏去到相应的地方

问题

希望你能在阅读的过程能带着以下的一些问题。

  1. Widget,Element,RenderObject是什么,三者的关系是怎样的?
  2. build方法中的BuildContext是什么?
  3. setState发生了什么?
  4. 为什么GlobalKey可以使你在任意地方获取Widget?

正文分割线


二 Widget,Element,RenderObjct

Widget,Element,RenderObject说明

在日常开发中,我们经常接触到都是Widget,包括各种的StalessWidget,StafulWidget,仿佛各种Widget就是界面上的一个节点。但是,Widget并不是界面上的一个节点.它只是一个描述节点的工具。请参照以下案例

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

其实按照我们的开发经验可知。一个节点是无法同时出现在两个地方。就像原生开发中。一个按钮是无法同时添加到两个地方的。Widget就是一个描述文件,负责产生一个节点,以及说明这个节点的功能是什么。

那现在问题来了,到底什么是节点呢?Flutter中的节点是一个比较神奇的存在。它不仅可用于表达UI界面上的一个元素,也可以用来表达一种布局信息,或是存储依赖关系等。

有一种说法是flutter中万物皆是Widget,其内在的意义就是flutter中的节点可以表示开发中的大部分的元素。flutter中的节点是用Element来表示。

既然说到节点Element可以表达大部分元素。那么具体到要产生在我们能看到UI信息,是怎么做到的呢?

首先我们先看看Widget类的定义

abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key? key;
  
  //创建节点Element
  @protected
  @factory
  Element createElement();

 ...
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

  ...
}

Widget类是一个抽象类,其中有一个createElement()方法。这个方法是用来创建节点Element。至于创建什么类型的Element。具体由Widget子类去实现

然后我们先看下Widget和Element这两个类的继承树

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

从图片可以很容易地观察出,每一种类型的Widget都有对应的Element。RenderObjectWidget是用来构建具体显示在界面上的节点的描述信息类。在这里我们重点看下RenderObjectWidget和RenderObjectElement。

abstract class RenderObjectWidget extends Widget {
 
  const RenderObjectWidget({ Key? key }) : super(key: key);
  //创建RenderObjectElement类型的节点
  @override
  @factory
  RenderObjectElement createElement();
  //创建RenderObject
  @protected
  @factory
  RenderObject createRenderObject(BuildContext context);

 
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

在这里,我们可以看到RenderObjectWidget的createElement()方法会创建RenderObjectElement一个RenderObjectElement。然后看下RenderObjectElement的代码

abstract class RenderObjectElement extends Element {
  RenderObjectElement(RenderObjectWidget widget) : super(widget);

  @override
  RenderObjectWidget get widget => super.widget as RenderObjectWidget;

  @override
  RenderObject get renderObject => _renderObject!;
  RenderObject? _renderObject;
  //mount方法极其重要,是所有Elemnt子类都必须需要实现方法
  //就是通过这个方法一层层地往下构建Element节点树
  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
   ...
     #
     _renderObject = widget.createRenderObject(this);
   ...
  }

  @override
  void update(covariant RenderObjectWidget newWidget) {
    super.update(newWidget);
    ...
    _dirty = false;
  }
  ...
}

结合RenderObjectWidget和RenderObjectElement的源码,我们可以看到这么一个基础的流程

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

可以看到Widget,Element和RenerObject三者的关系如下

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

值得一提的是,RenderObjct其实还不是真正显示在界面上的元素,其背后还有一个用于绘制的图层layer。关系有点像iOS开发中的UIView和CALayer。

这里小结一下

  • Element是Flutter中代表一个节点的类。这里的节点不单单指UI上的界面元素
  • Widget是Element的描述类,用于说明创建一个怎样的Element节点,
  • RenderObjct是用于表示显示在UI上的布局和元素

三 Widget树构建

Widget树是什么?

我们平常的学习过程中,经常会遇到Widget,Element和RenderObjct三棵树的概念,那到底这是什么意思呢,三棵树到底又是如果构建呢?结合前面三者的关系,我们接着分析一下。首先我们先看下具体的demo。如下

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

图中左方是demo的效果,中间是demo的代码,右方是demo中的各个Widget。可以看到,右方的所有Widget以一个树状的方式层级展开。形象一点的话,如下

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

可以看到,Widget以一个树状的方式去一级级往下走。我们在写Flutter项目的时候,所有的Widget都会以这样的一个树状层级构建,这个就是Widget树的概念。

Widget树怎么构建?

首先我们从main函数开发分析,我提炼一下重点,main函数执行后的流程如下

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

main函数最终会执行RenderObjectToWidgetAdapter类的attachToRenderTree方法。attachToRenderTree方法如下

 RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      owner.lockState(() {
        //创建RenderObjectToWidgetElement节点
        element = createElement();
        assert(element != null);
        //为节点设置一个BuildOwner
        element!.assignOwner(owner);
      });
      //调用BuildOwner的buildScope方法
      owner.buildScope(element!, () {
        element!.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }

在构建过程中,这个方法做了两个事情,构建RenderObjectToWidgetElement节点,并把节点和回调传入BuildOwner的buildScope方法。这个其实就是构建我们Widget树的开端。

构建管理者BuilderOwner

你可以理解为BuilderOwner为一个Widget树的管理者,负责Widget树的管理和更新。他与Widget,Element和RenderObjct的关系大致如下

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

BuilderOwner不与Widget直接接触,而是通过构建,更新Element树,从而构建和更新Widget树。

除了构建和更新Widget和Element树,BuilderOwner还负责管理GlobalKey等操作

构建流程

从前面我们看到runApp()方法会调用到RenderObjectToWidgetAdapter的attachToRenderTree方法中。这个方法RenderObjectToWidgetElement节点,并把节点和回调传入BuildOwner的buildScope方法。BuildOwner类和buildScope方法因为和更新过程联系比较大,稍后再详细描述,这里需要知道的是,buildScope会执行回调,也就是下面的代码

//调用BuildOwner的buildScope方法
//根据上方代码可知,element是RenderObjectToWidgetElement
owner.buildScope(element!, () {
  element!.mount(null, null);
 });

这里的RenderObjectToWidgetElement其实是一个根节点,也就是Element树的根节点,从这个节点开始,我们将一步步地往下构建子Element树.看下RenderObjectToWidgetElement中mount方法的实现

 @override
  void mount(Element? parent, Object? newSlot) {
    ...
    super.mount(parent, newSlot);
    _rebuild();
    ...
  }

可以看到,这里是调用了父类的mount方法,以及调用_rebuild方法。因为这一小节讲得是Widget树的构建。这里重点关注一下super.mount方法,因为RenderObjectToWidgetElement继承RootRenderObjectElement,而RootRenderObjectElement继承RenderObjectElement。所以调用super.mount会调用到RenderObjectElement中的mount方法。如下

void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    ...
    _renderObject = widget.createRenderObject(this);
    ...
    attachRenderObject(newSlot);
    _dirty = false;
  }

这里做了一件事,就是为RenderObjectToWidgetElement绑定一个RenderObject,用于表示这个节点是需要显示在界面。然后执行完mount方法以后,会调用_rebuild()方法。如下

 void _rebuild() {
    try {
      // 在这个案例中,widget.child 就是我们在runApp方法中传入的MyApp()
      _child = updateChild(_child, widget.child, _rootChildSlot);
    } catch (exception, stack) {
     ...
  }

_rebuild方法中会调用Element这个基类的updateChild方法。如下

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    if (newWidget == null) {
      if (child != null)
        //先使子节点child设置为一个不可用状态
        deactivateChild(child);
      return null;
    }
    final Element newChild;
    if (child != null) {
      //如果子节点非空,
      //则再判断是更新,删除子节点或是不对子节点进行操作
      bool hasSameSuperclass = true;
     ...
      if (hasSameSuperclass && child.widget == newWidget) {
        //widget和久的widget相等的情况下
        //如果只是slot改变,则更新slot。否则widget无需更改
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        //Widget更新
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        assert(child.widget == newWidget);
        assert(() {
          child.owner!._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      } else {
        //其他情况,使子节点child设置为一个不可用状态,再重新构建一个子节点
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      //如果子节点为空,则代表是子节点不存在,则为插入一个子节点
      newChild = inflateWidget(newWidget, newSlot);
    }
  ...
    return newChild;
  }

针对我们runApp方法,其实是一次初次构建。那么这时候RenderObjectElement这时候是没有子节点。也就是说child为空。所以会直接走到newChild = inflateWidget(newWidget, newSlot) 这个方法里面,newWidget就是案例中的MyApp()这个实例 。inflateWidget如下

  Element inflateWidget(Widget newWidget, Object? newSlot) {
    assert(newWidget != null);
    final Key? key = newWidget.key;
    if (key is GlobalKey) {
      //Widget的key是否是GlobalKey,
      final Element? newChild = _retakeInactiveElement(key, newWidget);
      //找到原来的Widget对应的Element,如果在Element树的位置有改变,则更新位置
      if (newChild != null) {
        ...
        newChild._activateWithParent(this, newSlot);
        final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
        assert(newChild == updatedChild);
        return updatedChild!;
      }
    }
    //调用newWidget,创建一个Element
    final Element newChild = newWidget.createElement();
    ...
    //调用mount方法,把newChild这个element插入到Element树对应的位置
    newChild.mount(this, newSlot);
    ...
    return newChild;
  }

可以看到,上面的代码中调用了newWidget.createElement()。也就是MyApp().createElement()的方法。这时候,就终于调用到我们自己写的代码了。从我们的代码可以看出,MyApp继承StatelessWidget。

StatelessWidget的createElement方法会创建一个StatelessElement。所以上方代码中的newChild就是一个StatelessElement类型的Element。而StatelessElement继承ComponentElement。当调用mount方法时,会执行ComponentElement的mount方法。

  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    ...
    _firstBuild();
    ...
  }

这里可以看到,执行了_firstBuild方法。__firstBuild方法最终会调用performRebuild方法。performRebuild如下

void performRebuild() {
   ...
      built = build();
   ...
      _child = updateChild(_child, built, slot);
  ...
  }

这个方法做的事情,就是调用build()方法产生一个Widget。然后再调用updateChild方法,传入Widget,然后去创建,或是更新一个Element。

ComponentElement中的build()方法不做任何事情,交由子类去实现。那么对于刚才的MyApp()这个Widget。对应的就是StatelessElement。StatelessElement的build方法如下。

Widget build() => widget.build(this);

这个方法就只是简单的返回widget.build(this)。但是,widget.build(this)这个方法是否似曾相识。没错,他其实就是我们平常写StatelessWidget或是State时候的build方法。对于我们的demo,就是MyApp的build方法,如下

 Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }

留意一下,这里的build方法的参数是BuildContext。结合我们上面的流程可知,BuildContext其实就是Element。理解这一点非常的重要。

结合上面的分析可知,我们平常开发中的BuildContext其实就是父Element节点。通过这个父Element节点,我们可以对节点树进行一些操作,具体看BuildContext的定义。

梳理一下我们刚才的分析,可以大概得出以下这么一个流程。

  1. runApp()方法创建RenderObjectToWidgetAdapter,并传入MyApp这个Widget,作为RenderObjectToWidgetAdapter的child。
  2. RenderObjectToWidgetAdapter创建RenderObjectToWidgetElement。
  3. 调用RenderObjectToWidgetElement的mount方法
  4. mount方法中调用child或是children的build方法去创建子Widget
  5. 调用子Widget的createElement,产生一个子Element.
  6. 调用子Element的mount方法

然后不断的重复4~6这三个步骤,不断的产生子Widget和子Element,直到Widget树和Element树完全构建为止。

构建流程图

结合上面的分析以及不同类型的Widget和Element(具体可以看framework.dart实现,这里不做详细分析)。可以得出以下的一个构建流程

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

四 Widget树是怎么更新的?

State

我们知道,一个界面不是一成不变的。当Widget和Element树构建完以后,我们需要更新界面。在Flutter开发中,我们一般会通过setState()方法去刷新界面。那调用setState方法发生了什么事情呢?

setState()方法是State里面的一个方法。首先我们先了解一下State是什么。要了解State,要先了解一下StatelessWidget和StatefulWidget。StatelessWidget和StatefulWidget都是继承于Widget。

StatelessWidget代表没有状态的Widget,也就是说这个Widget是不会改变的。StatefulWidget代表的是有状态的Widget,也就是这个Widget是会随着状态的改变。前面提到,Widget终归是只是一个描述类。作用是用来产生节点的。所以我们看下StatelesslElement和StatefulElement这两个类

class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    _dirty = true;
    rebuild();
  }
}


/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : state = widget.createState(),
        super(widget) {
   
    state._element = this;
    
    state._widget = widget;
  }

  @override
  Widget build() => state.build(this);

  final State<StatefulWidget> state;
  ...

}

可以看到StatefulElement比StatelessWidget多了一个state。state的代码如下

abstract class State<T extends StatefulWidget> with Diagnosticable {
  ...
  T get widget => _widget!;
  T? _widget;
  bool get mounted => _element != null;
  ...
  BuildContext get context {
   ...
    return _element!;
  }
  StatefulElement? _element;
  ...
   void setState(VoidCallback fn) {
    ...
    final dynamic result = fn() as dynamic;
   ...
    _element.markNeedsBuild();
  }
  ...
}

可以看到StatefuleElement,StatefuleWidget与State的关系如下

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程 widget去创建一个Element,Element的创建时调用Widget的createState方法创建一个State。这个State就代表着Element的状态,它内部持有Widget和Element,可以同时访问到Widget和Element里的内容。


从最熟悉的方法,也就是State的setState方法开始讲起。setState方法定义如下

 void setState(VoidCallback fn) {
    ...
    final dynamic result = fn() as dynamic;
   ...
    _element.markNeedsBuild();
  }

可以看出,这个方法先是我们调用了传入的方法,执行方法后,就调用了State中的_element的markNeedsBuild()方法,markNeedsBuild()方法是将一个Element标记为需要更新。方法的定义如下

void markNeedsBuild() {
    ...
    if (!_active) return;
    ...
    _dirty = true;
    owner.scheduleBuildFor(this); //加入build owner的rebuild计划
  }

markNeedsBuild方法首先判断一个Elemnt是否是_active,如果不是,则不做任何处理,因为一个非活跃的状态不会显示在界面上,所以不需做处理。然后将其_dirty值设置为true,标记这个Element 需要更新。然后调用BuildOwner的scheduleBuildFor方法,并传入需要更新的Element。BuildOwner的scheduleBuildFor定义如下

 void scheduleBuildFor(Element element) {
    ...
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled(); // 通知Engine 在下一帧需要做更新操作;
    }
    _dirtyElements.add(element);
    ...
  }

方法里的_scheduledFlushDirtyElements表明是否是在更新过程中,可以看到,这里先判断_scheduledFlushDirtyElements是否为true,就是不在更新过程中,则调用onBuildScheduled通知框架需要进行更新树。 然后将刚才传入的element添加到BuildOwner的_dirtyElements中。这个_dirtyElements是一个列表,存储着需要所有更新的Elemnt。

当框架收到需要更新树的信息后,就会调用BuildOwner的buildScope()方法,前面在构建过程中提到过这个buildScope方法,但是没有细说,在这里我们看一下buildScope方法的实现

void buildScope(Element context, [VoidCallback callback]) {
    if (callback == null && _dirtyElements.isEmpty) return;
    ...
 
    try {
      _scheduledFlushDirtyElements = true;
      if (callback != null) {
       ...
         callback(); //callback先执行
      }
      while (index < dirtyCount) {
        ...
         _dirtyElements[index].rebuild(); //进行rebuild
        ...
      }
          _dirtyElements.clear(); 
  }
复制代码

buildScope()方法中先是执行了callback方法,对于首次构建的流程,这个callback方法就是调用RootRenderObjectElement,也就是根Elemnt的mount方法进行构建。

然后这个方法主要就做了一件事,就是从_dirtyElements列表中取出每一个需要更新的Element,然后对所有Elemnt调用rebuild()方法,再清空_dirtyElements列表,标志着这一轮更新完成。

Element中的rebuild方法会调用performRebuild方法。performRebuild在Element中没有具体的实现。对于不同的Element子类,有着不一样的额外的实现。

首先对于RenderObjctElemnt及其子类,performRebuild只会调用updateRenderObject去更新RenderObjct对象,不会涉及任何的子节点的更新.那这里就有疑问了,该怎么对有子节点的RenderObjctElemnt进行更新子树呢?这里先留一下小疑问,等讲完ComponetElemnt然后再解答。

对于ComponetElemnt及其子类,调用performRebuild方法会调用updateChild方法。前面在构建过程中,就贴出了updateChild这两个方法,这里就不再重复去贴代码。在这里重点说一下updateChild这个方法

updateChild

updateChild中传入了(Element child, Widget newWidget, dynamic newSlot) 三个参数。child代表的是该ComponetElemnt的子节点,newWidget是子节点对应的Widget,newSlot是子节点的位置信息

在执行updateChild过程中根据传入的参数做了以下的一些处理。

  1. 如果new widget为空,但是element不为空(也就是原有的widget被删除了)。首先deactivateChild(child),如果child不为空,则解绑child的renderobjce,并添加到build owner中的_inactiveElements列表中,并返回函数。deactivateChild(child会把child添加到BuildOwner_inactiveElements中)
  2. 如果new widget非空,child不为空
    1. 如果传入的widget和原来的widget是同一个,但是slot不一样,则调用updateSlotForChild更新位置
    2. 如果不是同一个widget,则判断widget的canUpdate是否是true,如果是true的话(代表element的可以重用),先判断slot是否和原来的slot相等,不相等,则调用updateSlotForChild更新位置。然后调用elemnt的update方法进行更新
    3. 如果不符合上面2.1,2.2的情况(则代表element不可重用,newWidget需要用例外一个新的element),则调用deactivateChild(child)方法,并调用inflateWidget(newWidget, newSlot)产生新的child
  3. newWidget为空,child为空。则表明该Wiget还不存在Element,则调用inflateWidget去创建一个新的Element。

上面的1,2,3分别代表着删除,修改,增加 Elemnt子节点的三种情况。当对一个element(ComponetElemnt及其子类的实例)调用markNeedsBuild方法的时候,会调用到updateChild方法去更新该element。

对于步骤1,当一个Widget不再使用的时候,会调用deactivateChild方法,这个方法会把对应的Element放入到BuildlOwner的_inactiveElements列表中,如果Element被再次使用到(如使用了GlobalKey),就会从_inactiveElements列表中移除。如果没有被再次用到,再一次更新树的时候就会被销毁。

对于步骤2.2,elemnt的update方法只会简单的设置widget。具体的实现由各个子类实现。这一步可以buid方法可以沿下更新树

如StatelessElement会调用rebuild方法,RenderObjectElemnt会更新renderobjct。像SingleChildRenderObject和MutilChildRederObjectElemnt等有子elemnt的还会调用updateChild方法(MutilChildRederObjectElemnt 是调用updateChildren,但是updateChildren是对updateChild的一个包装,对传入的列表逐个调用updateChild)更新子elemnt,

在说RenderObjctElemnt的performRebuild时候,留了一个小疑问:如何更新有子节点的RenderObjcetElement的子树。

其实在开发过程中,我们是不会直接调用RenderObjcetElement的markNeedsBuild方法的。就拿Stack这个Wigdet来说。Stack继承自MultiChildRenderObjectWidget。MultiChildRenderObjectWidget对应的是MutilChildRederObjectElemnt。

但会发现,Stack是没有setState方法的,也就说不会直接调用对应Element的markNeedsBuild方法。Stack一般是被ComponetWidget及其子类对应的Widget(如StatefuleWidget)所包裹着,也就是Stack的一般都有一个父Widget包裹着。更新的时候,就会调用父Widget对应Element的updateChild更新子树。

假设你要在更新流程中,保留Stack,只删除Stack里的一个子节点,会走到父Widget对应的Element的updateChild方法中,也就是上方的步骤2.2。

步骤2.2中调用了child.update(newWidget)。也就是Stack对应的Element的update方法

Stack对应的Element是MutilChildRederObjectElement,update方法如下

  @override
  void update(MultiChildRenderObjectWidget newWidget) {
    super.update(newWidget);
	...
    _children = updateChildren(_children, widget.children,
        forgottenChildren: _forgottenChildren);
    _forgottenChildren.clear();
  }
}

可以看到,update方法调用了updateChildren方法,updateChildren这个方法会遍历子节点,对每一个子节点调用updateChild方法,从而完成RenderObjcetElement子树的更新。

验证这一个流程也很简单,在MutilChildRederObjectElement那里的update方法打个断点即可,如下。观察左方的调用栈即可得到具体的流程

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

更新流程图

综合上面的流程可以看出,调用State的setState方法,其实就是标记State对应的Element需要更新,通过调用markNeedsRebuild方法标记Element需要更新并通知到Flutter框架需要更细。当框架更新该Element的时候,会调用updateChild方法向下递归更新子树,直到叶子节点为止

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

五 Widget,Eleemtn,RenderObject树的补充

上方的三,四小节标题虽然是Widget的构建和更新。但是其实Widget的构建和更新其实都是依赖Element的。在实际内容中其实是Widget和Element树的更新。但是对于RenderObject树的构建没有太多的描述。

如果对上方Element的构建流程理解以后,再结合以下的代码,你就知道RenderObjct树是怎么构建的。

RenderObject树的构建

首先看下RenderObjectElement的mount方法,如下

void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
   ...
     //创建RenderObjct
    _renderObject = widget.createRenderObject(this);
   ...
    attachRenderObject(newSlot);
   ...
  }
   
...
void attachRenderObject(Object? newSlot) {
    assert(_ancestorRenderObjectElement == null);
    //找到父RenderObjct节点,并在父RenderObjct节点的字节点中加入该RenderObject节点
    _slot = newSlot;
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertRenderObjectChild(
        renderObject, newSlot);
    final ParentDataElement<ParentData>? parentDataElement =
        _findAncestorParentDataElement();
    if (parentDataElement != null) _updateParentData(parentDataElement.widget);
  }

首先在Element的构建过程中,去创建RenderObject。然后在通过Element树找到父节点,并通过_ancestorRenderObjectElement指向父节点。这样在Element树构建的过程中,RenderObject树也会不断的构建。

RenderObject树更新

看下RenderObjectElement的update方法,如下

  void update(covariant RenderObjectWidget newWidget) {
    super.update(newWidget);
    ...
    widget.updateRenderObject(this, renderObject);
    ...
  }

RenderObject树的更新也是依赖于Element树的更新。当Element更新的时候,会调用RenderObjectElement的update方法,然后调用对应Widget的updateRenderObject方法。

对于开发者而言,对RenderObject的操作基本都是通过Widget去实现,基本屏蔽了Element的实现。

六 拓展

Flutter中的Key

Flutter中有各种各样的Key,作用大致都是用来标志一个对象的。集成体系大致如下

Flutter源码阅读(1)-Widget,Element,RenderObject树的构建和更新流程

大家可以直接看下注释就可以知道不同的Key是用来做什么的。这里想重点说一下GlobalKey。很多人用来做跨Widget访问状态使用。

先看下它的定义

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
 ...
  factory GlobalKey({String? debugLabel}) => LabeledGlobalKey<T>(debugLabel);
 ...
  const GlobalKey.constructor() : super.empty();

  Element? get _currentElement =>
      WidgetsBinding.instance!.buildOwner!._globalKeyRegistry[this];
 ...
  BuildContext? get currentContext => _currentElement;
 ...
  Widget? get currentWidget => _currentElement?.widget;

 ...
  T? get currentState {
    final Element? element = _currentElement;
    if (element is StatefulElement) {
      final StatefulElement statefulElement = element;
      final State state = statefulElement.state;
      if (state is T) return state;
    }
    return null;
  }
}

可以看到,GlobalKey是用于标记一个State的。同时它有currentContext,currentWidget,和currentState。允许你在Widget树上的任何一个节点获取到其持有的Widget,State,Context。那是如何做到的呢?

当我在Widget中声明一个GlobalKey时,随着Widget树的构建,会调用Element树的mount方法

看下Element的mount方法,如下

 void mount(Element? parent, Object? newSlot) {
   ...
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null) {
     ...
      _owner = parent.owner;
    }
     ...
    final Key? key = widget.key;
   //是否是GlobalKey,如果是,向BuildOwner中注册一个GlobalKey
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
    _updateInheritance();
  }

可以看到,如果一个Widget声明了一个GlobalKey,那么对应的Element就会在BuildOwner中调用_registerGlobalKey注册。BuildOwner的_registerGlobalKey如下

void _registerGlobalKey(GlobalKey key, Element element) {
    ...
    _globalKeyRegistry[key] = element;
  }

_globalKeyRegistry是BuildOwner的一个Map,用于存储所有的GlobalKey和其对应的Element.

final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};

当调用_registerGlobalKey的时候,实际上就是把GlobalKey和Element绑定在一起,放入到BuildOwner的一个Map当中。

前面提到过BuildOwner,BuildOwner负责Widegt树的构建和更新的,一般在Flutter的声明周期中,一般只会有一个BuildOwner实例(这一块在讨论Flutter中的runApp流程的时候再讨论)

当我们去使用GlobalKey去拿Widget,Context,State的时候,其实就是以这个GlobalKey为键,去BuildOwner中取出对应的Element。再通过Element 获取到对应的Widget,State,Context。

就是那么的简单!!!

总结

其实理解Widget,Element,RenderObject树的概念不是必要的,但是很重要。对于后续理解app的流程,手势传递等,以及优化等分析都是必不可少的部分。