【Flutter核心类分析】深入理解Element

背景

通过前面的文章深入理解Widget我们知道,Widget本质上是Element的数据配置项,Element是通过Widget.createElement生成实例。同一个Widget可以创建多个Element。

Element分类

我们首先来从Element的继承关系着手

Element分类

根据继承关系图Element可以分为两类:

  • Component Element

    Component Element,对于的Widget是Component Widget和Proxy Widget,特点是子节点对于的Widget都需要通过build方法去创建。该类型的Element都只有一个子节点(single child);

  • RenderObjectElement

    Renderer WidgetRenderObjectWidget是它的配置信息,RenderObjectElement根据子类的不同包含的子阶段数量也不同:LeafRenderObjectElement 没有子节点,RootRenderObjectElement、SingleChildRenderObjectElement 有一个子节点,MultiChildRenderObjectElement 有多个子节点。

核心源码分析

由于Element的源码比较冗长,这里例举几个核心方法进行分析:

Element.updateChild

Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  final Element newChild;
  if (child != null) {
    bool hasSameSuperclass = 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);
      newChild = child;
    } else {
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}

该方法子类一般不需要复写,Element.updateChild方法第一次调用是由RenderObjectToWidgetAdapter.attachToRenderTreeRenderObjectToWidgetElement.mount→RenderObjectToWidgetElement._rebuildElement.updateChild

整个流程的方法后面都会讲到

该方法的主要作用就是根据对于的Widget的子Widget,来创建或者更新对于子Widget的ELement。根据上面的if条件 整理逻辑为:

  • newWIdget == null

    说明子节点对应的Widget已经被Remove,直接Remeove child

  • child == null

    说明 newWidget 是新插入的,创建子节点 (inflateWidget);

  • child != null 再分为三种情况

    1. child.widget == newWidget

      说明 child.widget 前后没有变化,若 child.slot != newSlot 表明子节点在兄弟结点间移动了位置,通过updateSlotForChild修改 child.slot 即可;

    2. Widget.canUpdate

      canUpdate判断是否可以用 newWidget 修改 child element,若可以,则调用update方法;

    3. 其他情况

      Remove child然后再调用ifnflateWidget创建新的child

Element.inflateWidget

Element inflateWidget(Widget newWidget, dynamic newSlot) {
  final Key? key = newWidget.key;
  if (key is GlobalKey) {
    final Element? newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      newChild._activateWithParent(this, newSlot);
      // 将inactive状态下的Element再次插入到Element树中
      final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
      return updatedChild!;
    }
  }
  //创建Element
  final Element newChild = newWidget.createElement();
  newChild.mount(this, newSlot);
  return newChild;
}

该方法的主要职责:通过 Widget 创建对应的 Element,并将其挂载 (mount) 到Element Tree上。调用路径来自上面的updateChild方法。

Element.upate

从上面的Element.updateChild方法中,我们知道当Widget.canUpdate返回为true的时候,会调用到该方法。子类子类需要重写该方法以处理具体的更新逻辑,同时该方法注解为:@mustCallSuper,也就是说子类重写该方法还需要手动调用super方法:

@mustCallSuper
void update(covariant Widget newWidget) {
  _widget = newWidget;
}

可以看到基类中update很简单,只是对_widget做了赋值。

StatelessElement.update

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

该方法的作用首先就是标记该Element的_dirty为true,再调用rebuild()方法重建child element。该方法会调用到Element.build方法,也就是会调用到Element的子类StatelessWidget.build方法。(不同子类的update方法会调用到自己的build方法)

StatefulElement.update

@override
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = state._widget!;
  final dynamic debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
  _dirty = true;
  state._widget = widget as StatefulWidget;
  rebuild();
}

相比上面的StatelessElement.update方法,StatefulElement.update多完成了一些任务,它需要处理一些State的逻辑:

  • 修改state._widget的属性
  • 调用state.didUpdateWidget方法(不熟悉可以看上文:深入理解Widget
  • 最后再rebuild阶段会调用到自己StatefulElement.build方法,间接调用到了State.build方法

ProxyElement.update

  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }

  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }
  @protected
  void notifyClients(covariant ProxyWidget oldWidget);

ProxyElement.update方法需要关注的是对update的调用链,通过源码可以看出它的作用主要是用于通知关联对象Widget有更新。具体通知可在子类中处理,详细分析见深入理解数据共享InheritedWidget.

RenderObjectElement.update

@override
void update(covariant RenderObjectWidget newWidget) {
  super.update(newWidget);
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

RenderObjectElement.update方法调用了widget.updateRenderObject来更新Render Object。

SingleChildRenderObjectElement.update

@override
void update(SingleChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  _child = updateChild(_child, widget.child, null);
}

SingleChildRenderObjectElement继承自RenderObjectElement直接调用updateChild递归修改子节点

MultiChildRenderObjectElement.update

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

updateChildren方法中处理了子节点的插入、移动、更新、删除等所有情况。

Element.mount

@mustCallSuper
void mount(Element? parent, dynamic newSlot) {
  _parent = parent;
  _slot = newSlot;
  _lifecycleState = _ElementLifecycle.active;
  _depth = _parent != null ? _parent!.depth + 1 : 1;
  if (parent != null) // Only assign ownership if the parent is non-null
    _owner = parent.owner;
  final Key? key = widget.key;
  if (key is GlobalKey) {
    key._register(this);
  }
  _updateInheritance();
}

BuildOwner对象会在这里传递给child element。最后继承来自父节点的InheritedWidget(将_inheritedWidgets指向_parent的_inheritedWidgets对象),该方法被注释为@mustCallSuper,子类重写该方法时必须调用super。

ComponentElement.mount

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

void _firstBuild() {
  rebuild();
}

Comonent Element 在挂载时会执行_firstBuild->rebuild操作。

RenderObjectElement.mount

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

从源码可以看到RenderObjectElement.mount方法的核心作用就两个:

  • widget.createRenderObject(this)创建RenderObject
  • 通过attachRenderObject(newSlot);将RenderObject插入到RenderObject Tree中。

SingleChildRenderObjectElement.mount

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

该方法直接调用updateChild返回一个新的element实例;

MultiChildRenderObjectElement.mount

@override
void mount(Element? parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
  Element? previousChild;
  for (int i = 0; i < children.length; i += 1) {
    final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
    children[i] = newChild;
    previousChild = newChild;
  }
  _children = children;
}

该方法循环为每个子节点调用inflateWidget返回一个新的element实例;

Element.markNeedsBuild

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

markNeedsBuild的主要作用就是将当前 Element 加入_dirtyElements中,以便在下一帧可以rebuild。

调用markNeedsBuild场景:

  • State.setState

    详细分析见文章深入理解Widget

  • Element.reassemble

    只在开发过程中调用,调用于热重载齐肩

  • Element.didChangeDependencies

    前面介绍过当依赖的「Inherited Widget」有变化时会导致依赖者 rebuild,就是从这里触发的

  • StatefulElement.activate

    当 Element 从 “inactive” 到 “active” 时(inflateWidget),会调用该方法。为什么StatefulElement要重写activate?因为StatefulElement有附带的 State,需要给它一个activate的机会。

Element.rebuild

void rebuild() {
  if (_lifecycleState != _ElementLifecycle.active || !_dirty)
    return;
  performRebuild();
}

该方法逻辑简单,对于活跃的脏节点调用performRebuild,在下面三种情况下被调用:

  • 对于dirty element,在新一帧的绘制过程中有BUildOwner.buildScope;
  • 在element挂在时候,由Element.mount调用;
  • 在update方法内被调用。

Element.performRebuild

/// Called by rebuild() after the appropriate checks have been made.
@protected
void performRebuild();

基类Element中的performRebuild是一个空实现。

CommponentElement.performRebuild

@override
void performRebuild() {
  Widget? built;
  try {
    built = build();
    debugWidgetBuilderValue(widget, built);
  } catch (e, stack) {
  } finally {
    _dirty = false;
  }
  try {
    _child = updateChild(_child, built, slot);
  } catch (e, stack) {
  }
}

对于组合型 Element,rebuild 过程其实就是调用build方法生成child widget,再调用updateChild更新child element

  • StatelessElement.build

    build => widget.build(this)

  • StatefulElement.build

    Build => state.build(this)

  • ProxyElement.build

    widget.child

RenderObjectElement.performRebuild

@override
void performRebuild() {
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

在渲染型 Element 基类中只是用 Widget 更新了对应的Render Object。在相关子类中可以执行更具体的逻辑。

Element生命周期

Element生命周期

Element生命周期原有比这复杂得多,这里只是列出了大概的流程。

  • parent 通过Element.inflateWidget->Widget.createElement创建 child element,触发场景有:UI 的初次创建、UI 刷新时新老 Widget 不匹配(old element 被移除,new element 被插入);

  • parent 通过Element.mount将新创建的 child 插入「Element Tree」中指定的插槽处 (slot);

    dynamic Element.slot——其含意对子节点透明,父节点用于确定其下子节点的排列顺序 (兄弟节点间的排序)。因此,对于单子节点的节点 (single child),child.slot 通常为 null。
    另外,slot 的类型是动态的,不同类型的 Element 可能会使用不同类型的 slot,如:Sliver 系列使用的是 int 型的 index,MultiChildRenderObjectElement 用兄弟节点作为后一个节点的 slot。
    对于「component element」,mount方法还要负责所有子节点的 build (这是一个递归的过程),对于「render element」,mount方法需要负责将「render object」添加到「render tree」上。其过程在介绍到相应类型的 Element 时会详情分析。

  • 此时,(child) element 处于 active 状态,其内容随时可能显示在屏幕上;

  • 此后,由于状态更新、UI 结构变化等,element 所在位置对应的 Widget 可能发生了变化,此时 parent 会调用Element.update去更新子节点,update 操作会在以当前节点为根节点的子树上递归进行,直到叶子节点;(执行该步骤的前提是新老 Widget.[key && runtimeType] 相等,否则创建新 element,而不是更新现有 element);

  • 状态更新时,element 也可能会被移除 (如:新老 Widget.[key || runtimeType] 不相等),此时,parent 将调用deactivateChild方法,该方法主要做了 3 件事:

    1. 从「Element Tree」中移除该 element (将 parent 置为 null);
    2. 将相应的「render object」从「render tree」上移除;
    3. 将 element 添加到owner._inactiveElements中,在添加过程中会对『以该 element 为根节点的子树上所有节点』调用deactivate方法 (移除的是整棵子树)。
  • 此时,element 处于 “inactive” 状态,并从屏幕上消失,该状态一直持续到当前帧动画结束;

  • 从 element 进入 “inactive” 状态到当前帧动画结束期间,其还有被『抢救』的机会,前提是『带有「global key」&& 被重新插入树中』,此时:

    1. 该 element 将会从owner._inactiveElements中移除;
    2. 对该 element subtree 上所有节点调用activate方法 (它们又复活了!);
    3. 将相应的「render object」重新插入「render tree」中;
    4. 该 element subtree 又进入 “active” 状态,并将再次出现在屏幕上。
  • 对于所有在当前帧动画结束时未能成功『抢救』回来的「Inactive Elements」都将被 unmount;

总结

Element介绍到这里就结束了。这里做个总结:

  • Element 与 Widget 一一对应
  • 只有Render Element才有对应的Render Object
  • Element 作为 Widget 与 RenderObejct 间协调者,会根据 UI(Widget Tree) 的变化对Element Tree作出相应的调整,同时对RenderObject Tree进行必要的修改;
  • Widget 是不可变的、无状态的,而 Element 是有状态的。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>