Flutter Widget构建流程详解

我们在开发Flutter的时候经常会有以下疑问:

  1. mounted什么时候为true
  2. dispose什么时候调用
  3. ChangeNotifier为什么会调用dispose方法。为什么ChangeNotifier有时候又不会调用dispose方法

概念

Flutter中有三棵树: Widget, Element以及RenderObject,它们之间的关系如下图所示


流程图.jpg

widget:

Widget 描述UI数据的组件,作为一个生产者创建Element和RenderObject。相当于是一个配置文件,为构建Element树提供模板

Element:

Element UI真正的节点,主要管理Widget和State,通过Widget和State创建Element树,同时也管理RenderObject的构建

RenderObject:

RenderObject就是真正渲染到屏幕上的组件。只有当Element是RenderObjectElement的时候才会通过RenderObjectWidget创建RenderObject,因此Element树跟RenderObject树并不是一一对应的

BuildOwner:

这里另外单独提下BuildOwner,这是一个调度中心。由它发起树构建和更新以及树的销毁。

element树的构建过程

element树的构建过程先从runApp说起:


构建流程图.jpg
owner.buildScope(element!, () {
  element!.mount(null, null);
});

所以真正开始构建是从buildScope开始,buildScope传入了两个参数:

  1. element就是最顶层element节点即RenderObjectToWidgetElement。
  2. callback会在buildScope里面调用,这里考虑正常情况下,会先执行callback方法。callback里面开始第一个element的mount操作。
    RenderObjectToWidgetElement
@override
void mount(Element? parent, dynamic newSlot) {
  assert(parent == null);
  super.mount(parent, newSlot);
  _rebuild();
}

RenderObjecToWidgetElement由于是最顶层的父节点,因此parent传的是null。

@override
void mount(Element? parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  ...
  _renderObject = widget.createRenderObject(this);
  ...
}

由于RenderObjectToWidgetElement继承的是RootRenderObjectElement,而RootRenderObjectElement又是继承的RenderObjectElement。所以会在mount的时候调用createRenderObject方法,创建对应的RenderObject。
接下来会调用rebuild方法:

void _rebuild() {
  try {
    //widget.child就是runApp时候传入的我们APP的根布局
    _child = updateChild(_child, widget.child, _rootChildSlot);
    ...
  } catch (exception, stack) {
    ....
  }
}

会执行updateChild方法

Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
  if (newWidget == null) {
    if (child != null)
      //如果当前widget是空的,但是element不是空的,那么就把element设置为inactive状态
      //这里仅仅是变成inactive状态,并不会马上执行dispose方法,稍后会讲到什么情况下执行dispose方法
      deactivateChild(child);
    return null;
  }
  final Element newChild;
  if (child != null) {
    //如果element和widget都不为空,那么下面根据不同的条件会做增删改查操作
    bool hasSameSuperclass = true;
    ...
    if (hasSameSuperclass && child.widget == newWidget) {
      //如果当前element的widget和传入的新widget是同一个
      //那么只判断slot是否一致,如果不一致更新下slot信息
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
      //如果当前element的widget和传入的新widget不是同一个,但是属于同一个Widget类
      if (child.slot != newSlot)
        //先判断slot是否一致,不一致的话先更新slot信息
        updateSlotForChild(child, newSlot);
      //更新element持有的element对象
      child.update(newWidget);
      assert(child.widget == newWidget);
      assert(() {
        child.owner!._debugElementWasRebuilt(child);
        return true;
      }());
      newChild = child;
    } else {
      //如果element所持有的widget和新传入的widget不属于同一个Widget类,那么将当前的Element先deactive掉
      //同理这个地方也不会立刻执行dispose方法
      deactivateChild(child);
      assert(child._parent == null);
      //然后会inflateWidget方法,后面再详细讲这个方法,这里会创建一个element的对象
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    //如果Widget不为空,但是element是空,那么也要创建一个element的对象
    newChild = inflateWidget(newWidget, newSlot);
  }
  ...

  return newChild;
}

updateChild是一个比较核心的方法,这里提供一个流程图:


updatechild流程图.jpg

由于是第一次构建,RootRenderObjectElement是没有子Element的,所以这里会直接走inflateWidget的方法。

Element inflateWidget(Widget newWidget, dynamic newSlot) {
  assert(newWidget != null);
  final Key? key = newWidget.key;
  if (key is GlobalKey) {
    //这里先从inactiveElement列表中找对应的缓存,如果存在。那么将element重新取出来变成active状态
    //(这也就是为什么Element在deactivie掉以后,不一定会指定dispose方法销毁。inactiveElement也是有可能会被再次使用的)
    final Element? newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      //更新element的slot信息
      ...
      newChild._activateWithParent(this, newSlot);
      final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
      assert(newChild == updatedChild);
      return updatedChild!;
    }
  }
  //如果没有符合条件的inactiveElement,调用createElement方法创建element
  final Element newChild = newWidget.createElement();
  assert(() {
    _debugCheckForCycles(newChild);
    return true;
  }());
  //将element挂载到element树上
  newChild.mount(this, newSlot);
  assert(newChild._lifecycleState == _ElementLifecycle.active);
  return newChild;
}

接下来我们可以看下Widget的createElement方法和Element的mount方法

image.png

由于Widget的createElement方法比较简单,Widget会产生对应的Element(一般情况下Widget和Element的名字是类似的),这里就不详细展开了。


image.png

继承Element的类比较多,我们详细展开讲下

Element

ComponentElement

mount方法:

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

在执行ComponentElement的mount方法的时候会先将Element挂载到树上,然后执行_firstBuild方法,最终会执行performRebuild方法:

void performRebuild() {
  ...
  Widget? built;
  try {
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    built = build();
    assert(() {
      _debugDoingBuild = false;
      return true;
    }());
    debugWidgetBuilderValue(widget, built);
  } catch (e, stack) {
    ...
  } finally {
    ...
  }
  try {
    _child = updateChild(_child, built, slot);
    assert(_child != null);
  } catch (e, stack) {
    ...
  }
  ...
}

会先执行build()方法,build方法由ComponentElement子类实现,然后执行updateChild方法(就是Element的updateChild方法,从而进行Element的增删改操作)

StatefulElement

当执行到firstBuild的时候,StatefulElement会重写这个方法,先执行state的initState方法,然后执行didChangeDependencies方法。

void _firstBuild() {
  assert(state._debugLifecycleState == _StateLifecycle.created);
  try {
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
    final dynamic debugCheckForReturnedFuture = state.initState() as dynamic;
    ...
  } finally {
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
  }
  ...
  state.didChangeDependencies();
  ...
  super._firstBuild();
}

之后执行父类的firstBuild方法,随后就是rebuild->performRebuild->build()。
StatefulElement会通过State的build方法构建Widget, 而state是在StatefulElement的构造方法中初始化的。而StatefulElement是在StatefulWidget的createElement方法中创建的

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

StatelessElement

StatelessElement比较简单,就直接调用Widget的build方法。同理这个Widget就是StatelessWidget。

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

ProxyElement

ProxyElement以及其子类ParentDataElement,InheritedElement会获取对应Widget的child。不过这几个类并不是本篇重点,就不再赘述

Widget build() => widget.child;

RenderObjectElement

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

在执行RenderObjectElement的时候,先将Element挂载到Element树上,然后执行RenderObjectWidget的createRenderObject方法创建renderObject。然后将RenderObject附着到最近的RenderObjectElement祖先节点上。至于为什么要用祖先节点来绑定RenderObject之后再做解释。

void attachRenderObject(dynamic newSlot) {
  assert(_ancestorRenderObjectElement == null);
  _slot = newSlot;
  //这里先获取第一个祖先节点的RenderObjectElement
  //(因为Element树不一定是RenderObjectElement,而RenderObject必须附着到RenderObjectElement上)
  _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
  //绑定对应的RenderObject,这个方法是个空实现,主要是交给了子RenderObjectElement来做。
  _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
  final ParentDataElement? parentDataElement = _findAncestorParentDataElement();
  if (parentDataElement != null)
    _updateParentData(parentDataElement.widget);
}

这里我们看到RenderObjectElement并没有对子节点有处理,是因为RenderObjectElement子类既存在没有孩子节点的Element也存在多个孩子节点的Element,所以对于子节点的处理交给各自的子类来实现。

RootRenderObjectElement

这个Element就是作为最顶层Element节点存在,所以没有特殊的处理逻辑(文章最开始讲的RenderObjectToWidgetElement就是继承的RootRenderObjectElement)

LeafRenderObjectElement

顾名思义,叶子节点的Element,那么这个Element就不存在子节点。因此不会有子节点的处理

SingleChildRenderObjectElement

单孩子节点的Element
mount方法:

void mount(Element? parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _child = updateChild(_child, widget.child, null);
}

首先指定父类的mount方法,将Element挂载到Element的树上,然后执行updateChild方法。开始往下遍历增删改孩子节点。
insertRenderObjectChild方法:

void insertRenderObjectChild(RenderObject child, dynamic slot) {
  //找到当前Element持有RenderObject对象
  final RenderObjectWithChildMixin renderObject = this.renderObject as RenderObjectWithChildMixin;
  assert(slot == null);
  assert(renderObject.debugValidateChild(child));
  //把子RenderObject挂载到RenderObject树上
  renderObject.child = child;
  assert(renderObject == this.renderObject);
}

这里也可以解释刚才的一个问题:「为什么RenderObject附着到最近的RenderObjectElement祖先节点」, 原因是RenderObjectElement祖先节点会有一个对应的RenderObject,这个RenderObject已经挂在了RenderObject树上面,当前RenderObjectElement的RenderObject需要挂到RenderObject树上就必须先找到最近的RenderObjectElement祖先节点。

MultiChildRenderObjectElement

多孩子节点的Element
mount方法:

void mount(Element? parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  final List children = List.filled(widget.children.length, _NullElement.instance, growable: false);
  Element? previousChild;
  for (int i = 0; i < children.length; i += 1) {
    //遍历孩子数组,创建对应的Element。
    final Element newChild = inflateWidget(widget.children[i], IndexedSlot(i, previousChild));
    children[i] = newChild;
    previousChild = newChild;
  }
  _children = children;
}

inserRenderObjectChild方法:

void insertRenderObjectChild(RenderObject child, IndexedSlot slot) {
  //获取当前RenderObjectElement对应的RenderObject
  final ContainerRenderObjectMixin> renderObject = this.renderObject;
  assert(renderObject.debugValidateChild(child));
  //根据当前RenderObjectElement的位置信息挂载到对应位置的RenderObject树中
  renderObject.insert(child, after: slot.value?.renderObject);
  assert(renderObject == this.renderObject);
}

总结:

至此,Element树以及RenderObject树的构建过程全部结束。


构建完整流程图.jpg

element树的更新流程

树更新流程

更新的源头需要从platform_dispatcher说起:当硬件发出VSync信号时,会调用platformDispatcher的onDrawFrame


更新流程图.jpg

发现最后还是会执行buildScope方法进行更新。

_dirtyElements[index].rebuild();

发现buildScope在更新的时候会获取_dirtyElements来进行遍历执行rebuild。_dirtyElements列表里面存放着的是Element的对象。

void rebuild() {
  ...
  performRebuild();
  ...
}

最终还是会执行performRebuild()方法。
ComponentElement会重新build Widget,RenderObjectElement则会执行updateRenderObject更新RenderObject。

在执行完buildScope方法以后,会执行finalizeTree方法。 这个方法会将_inactiveElements的所有element对象都执行一遍unmount方法。StatefulElement会重载这个方法,调用state的dispose方法,并且将state的element置空

dirtyElement数组

setState

最常见的要属setState方法,这个方法是State类的方法,主要配合StatefulWidget进行一个更新

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

这个方法传入一个callback,会先执行这个callback,然后执行element的markNeedsBuild()方法

void markNeedsBuild() {
  ...
  if (dirty)
    return;
  _dirty = true;
  owner!.scheduleBuildFor(this);
}

将当前Element的_dirty属性设置为true,然后执行BuildOwner的scheduleBuildFor

void scheduleBuildFor(Element element) {
  ...
  _dirtyElements.add(element);
  element._inDirtyList = true;
  ...
}

最终会将需要更新的element加入到_dirtyElements的列表中

Provider

主要是ChangeNotifierProvider、ChangeNotifer、Consumer。

ChangeNotifer

ChangeNotifier就是比较常规的监听器, 实现了Listenable接口,增加了notifyListener的方法。

class ChangeNotifier implements Listenable {
  LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();

  ...
  @override
  void addListener(VoidCallback listener) {
    assert(_debugAssertNotDisposed());
    _listeners!.add(_ListenerEntry(listener));
  }

  @override
  void removeListener(VoidCallback listener) {
    assert(_debugAssertNotDisposed());
    for (final _ListenerEntry entry in _listeners!) {
      if (entry.listener == listener) {
        entry.unlink();
        return;
      }
    }
  }
  
  @mustCallSuper
  void dispose() {
    assert(_debugAssertNotDisposed());
    _listeners = null;
  }

  @protected
  @visibleForTesting
  void notifyListeners() {
    ...

    final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);

    for (final _ListenerEntry entry in localListeners) {
      try {
        if (entry.list != null)
          //包装了对应的Callback。在执行notifyListeners的时候会触发对应的callback方法
          entry.listener();
      } catch (exception, stack) {
        ...
      }
    }
  }
}

ChangeNotifierProvider

ChangeNotifierProvider继承于ListenableProvider

class ListenableProvider extends InheritedProvider {
  /// Creates a [Listenable] using [create] and subscribes to it.
  ///
  /// [dispose] can optionally passed to free resources
  /// when [ListenableProvider] is removed from the tree.
  ///
  /// [create] must not be `null`.
  ListenableProvider({
    Key? key,
    required Create create,
    Dispose? dispose,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  }) : super(
          key: key,
          startListening: _startListening,
          create: create,
          dispose: dispose,
          lazy: lazy,
          builder: builder,
          child: child,
        );

  ....

  static VoidCallback _startListening(
    InheritedContext e,
    Listenable? value,
  ) {
    value?.addListener(e.markNeedsNotifyDependents);
    return () => value?.removeListener(e.markNeedsNotifyDependents);
  }
}

ListenableProvider会将_startListening方法传入到父类里面。这个方法里面是给Listenable(一般情况下都是ChangeNotifier)的对象添加了callback,这个callback会存放到ChangeNotifier的_listeners。所以一旦这个value调用了notifierListeners的方法,就会调用这个callback方法即e.markNeedsNotifyDependents

@override
void markNeedsNotifyDependents() {
  if (!_isNotifyDependentsEnabled) {
    return;
  }

  markNeedsBuild();
  _shouldNotifyDependents = true;
}

这个方法就会触发markNeedsBuild()方法,当前element就会被加入到dirtyElement数组当中。
再来看下_startListening的链路,ListenableProvider继承于InheritedProvider,InheritedProvider又有对应的delegate,最终_startListening会被传入到_CreateInheritedProvider当中。

class _CreateInheritedProviderState
    extends _DelegateState> {
  VoidCallback? _removeListener;
  bool _didInitValue = false;
  bool _didSucceedInit = false;
  T? _value;
  _CreateInheritedProvider? _previousWidget;

  @override
  T get value {
    if (_didInitValue && !_didSucceedInit) {
      throw StateError(
        'Tried to read a provider that threw during the creation of its value.\n'
        'The exception occurred during the creation of type $T.',
      );
    }
    bool? _debugPreviousIsInInheritedProviderCreate;
    bool? _debugPreviousIsInInheritedProviderUpdate;
    
    ...
    
    if (!_didInitValue) {
      _didInitValue = true;
      if (delegate.create != null) {
        assert(debugSetInheritedLock(true));
        try {
          ...
          //create方法创建对应的ChangeNotifier赋值给value
          //因此_CreateInheritedProviderState就会持有ChangeNotifier的对象
          _value = delegate.create!(element!);
          _didSucceedInit = true;
        } finally {
          ...
        }
        ...
      }
      ...

    element!._isNotifyDependentsEnabled = false;
    //此处会执行startListening方法,将value(即ChangeNotifier)传入
    _removeListener ??= delegate.startListening?.call(element!, _value as T);
    element!._isNotifyDependentsEnabled = true;
    assert(delegate.startListening == null || _removeListener != null);
    return _value as T;
  }

  @override
  void dispose() {
    super.dispose();
    _removeListener?.call();
    if (_didInitValue) {
      delegate.dispose?.call(element!, _value as T);
    }
  }
}

发现是在获取value的时候会执行startListening,从而将callback注册到ChangeNotifier当中。
另外我们可以看到只有当_didInitValue为true的时候才会在dispose的时候调用ChangeNotifier的dispose方法,而_didInitValue这个只有在value的get方法调用过后才会置true。

Consumer

class Consumer extends SingleChildStatelessWidget {
  /// {@template provider.consumer.constructor}
  /// Consumes a [Provider]
  /// {@endtemplate}
  Consumer({
    Key? key,
    required this.builder,
    Widget? child,
  }) : super(key: key, child: child);

  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    return builder(
      context,
      Provider.of(context),
      child,
    );
  }
}

consumer逻辑相对比较简单,继承于SingleChildStatelessWidget。所以在build的时候会执行buildWithChild方法。这里面的重点是Provider.of(context),泛型一般会传入对应的ChangeNotifier。那么再来看下Provider.of(context)的逻辑

static T of(BuildContext context, {bool listen = true}) {
    ...
    //此方法会获取遍历获取对应的element
    final inheritedElement = _inheritedElementOf(context);

    if (listen) {
      // bind context with the element
      // We have to use this method instead of dependOnInheritedElement, because
      // dependOnInheritedElement does not support relocating using GlobalKey
      // if no provider were found previously.
      context.dependOnInheritedWidgetOfExactType<_InheritedProviderScope>();
    }

    final value = inheritedElement?.value;

    if (_isSoundMode) {
      if (value is! T) {
        throw ProviderNullException(T, context.widget.runtimeType);
      }
      return value;
    }

    return value as T;
  }

每个InheritedElement都会在_updateInheritance方法执行的时候获取parent的_inheritedWidgets的Map,将parent的map赋值给自己,然后再将自己也存入到map中,这样就可以获取到对应的InheritedElement。也因此在此处会调用startListening方法将callback注册到ChangeNotifier当中。


更新时序图.jpg

总结

现在我们可以回答开头的三个问题

  1. mounted什么时候为true
    由于构建开始的时候就会调用buildScope方法,因此可以认为只要sync信号过来,那么创建好的Widget就会执行mount方法,然后执行inflateWidget的时候会创建对应的Element,此时mounted即为true。在下一次sync信号过来的时候如果当前element不在使用,那么在dispose调用完成之后就会变成false
  2. dispose什么时候调用
    在下一次sync信号来临时,在updateChild方法中,当前element与对应的Widget不再匹配时,那么会调用deactiveChild方法将element放入到InactiveElements数组中,等待执行finalizeTree的时候将InactiveElements的所有Element都unmount掉。
  3. ChangeNotifier为什么会调用dispose方法。为什么Widget树发生了变化,ChangeNotifier有时候又不会调用dispose方法
    1. 首先Provider本身就是个Widget,那么也会创建对应的Element,只要是Element一旦与当前Widget树不匹配,就会调用unmount,而ChangeNotifierProvider对应的_InheritedProviderScopeElement在执行unmount的时候会调用dispose方法,从而最终会执行ChangeNotifier的dispose方法。
    2. 因为如果当前的ChangeNotifier没有主动调用过Provider.of或者是使用过Consumer,那么ChangeNotifier就不会被调用dispose方法

你可能感兴趣的:(Flutter Widget构建流程详解)