一 前言

阐明

本文主要叙述的是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的流程,手势传递等,以及优化等剖析都是必不行少的部分。

这是一篇自己写过的文章,原文地址。为什么要重写一遍呢?一是本来的文章流程图上格式有些混乱,之前的流程图也丢失了,所以重新整理了一遍。二是之前的源码偏多,最近也正好重新看了下源码,所以换个思路再写一篇那样。