Flutter渲染机制(二):Element

一、概述

Flutter渲染机制(一):Widget 树 一文中,我们介绍了 Widget 树,发现每一种类型的 Widget 都会创建与之相对应的 Element,那这个 Element 又是什么,有什么作用呢?

Element 是什么:

  • Element 是 Widget 的实际内容,并且会根据 Widget 的位置,配置自己在书中的位置。
  • Element 会形成一棵树,有的 Element 有一个子节点,有的有多个子节点。

二、Element 的分类

Element 的继承关系图:
在这里插入图片描述

结合上图,从功能上看,Element 可以分为 ComponentElement、RenderObjectElement两大类。SingleChildWidgetElementMixin 是做混合时使用的。

  • ComponentElement

    • 组合类 Element,相当于容器,主要用来组装其它 Element,得到功能更加复杂的Element。
    • ComponentElement 包括 StatelessElement、StatefulElement、ProxyElement
      • StatelessElement对应 StatelessWidget
      • StatefulElement对应 StatefulWidget
      • ProxyElement 对应 ProxyWidget
  • RenderObjectElement

    • 渲染类Element,对应Renderer Widget,是框架最核心的Element。
    • RenderObjectElement 主要包括 LeafRenderObjectElementSingleChildRenderObjectElementMultiChildRenderObjectElement
      • LeafRenderObjectElement 对应 LeafRenderObjectWidget,没有子节点。
      • SingleChildRenderObjectElement 对应 SingleChildRenderObjectWidget,有一个子节点。
      • MultiChildRenderObjectElement 对应 MultiChildRenderObjecWidget,有多个子节点。

三、ComponentElement

3.1 Widget、Element、State 之间的关系

在这里插入图片描述
小结:

  • 上图展示了 StatelessElement 与 StatefulElement 在 Element 树中与对应元素的关系。
  • 在 Element 树中,StatelessElement 与 StatefulElement 都是通过 parent 和 child 属性持有它的父/子 Element。
  • 左图显示了 StatelessElement 与 Widget 的引用关系。即 StatelessElement 持有 Widget 引用。
  • 右图显示了 StatefulElement 与 Widget 和 State 的引用关系。即 StatefulElement 持有 Widget 和 State 的引用;同时 State 持有 StatefulElement 和 Widget 的引用。

3.2 核心操作流程

核心操作流程包括:Element 的创建、更新、销毁,下面将分别分析这三个流程。

3.2.1 创建流程

ComponentElement 的创建起点从 父Element 调用 inflateWidget() 方法开始,然后通过mount() 方法将该Element挂载至Element Tree,并递归创建子节点。

  • 绿色流程:Child 为 StatelessWidget 类型时的创建流程。
  • 红色流程:Child 为 StatefulWidget 类型时的创建流程的差异部分。StatefulElement 重写了 _firstBuild()、performRebuild() 等方法,去执行了 State 相关的操作。

在这里插入图片描述

3.2.2 更新流程

父Element 调用 updateChild() 方法执行更新子节点的操作,由于新旧Widget的类型和Key均未发生变化,因此触发了Element的更新操作,并通过 performRebuild 将更新操作传递下去。其核心函数updateChild之后会详细介绍。

在这里插入图片描述

3.2.3 销毁流程

父Element 或更上级的节点执行更新子节点的操作(updateChild),由于新旧Widget的类型或者Key发生变化,或者新Widget被移除,因此导致该Element被转为未激活状态,并被加入未激活列表,并在下一帧被失效。

在这里插入图片描述

3.3 核心方法分析

从上面的三个核心流程中可以看出,他们都触发了 Element.updateChild() 方法,所以我们先来分析一下它的逻辑。

3.3.1 Element.updateChild()

当 Widget 中状态发生变化时,Element 会重新构建状态发生变化的 Widget,因此会得到一棵新的 Widget 树。Element.updateChild() 方法就是用来比较两棵 Widget 树的差异,然后将新Widget树中变化的部分覆盖到老的Widget树中 (其它两棵树会一起改变)。

因为是两个 widget 的比较,且它们都存在 “为Null” 和 “不为Null” 两种状态,所以共有4种组合方式。如下表所示:

child 状态newWidget == nullnewWidget != null
Child == null返回null返回新Element
Child != null移除旧的子Element,返回null1. 如果Widget能更新,更新旧的子Element,并返回旧的Element。
2. 否则创建新的子Element并返回。
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  // 1.Condition2 & Condition3两个场景.
  // 即newWidget==null时,不管child!是否有值,都直接返回null。
  if (newWidget == null) {
    // 如果child有值,则移除child这个Element。
    if (child != null)
      deactivateChild(child);
    return null;
  }
  final Element newChild;
  if (child != null) {
    bool hasSameSuperclass = true;
    // 2.Condition5的场景:同一个widget对象。
    if (hasSameSuperclass && child.widget == newWidget) {
      if (child.slot != newSlot) {
      	// 同一个widget,newSlot位置可能发生变化,所以更新一下Element中的_slot值。
        updateSlotForChild(child, newSlot);
      }
      newChild = child;
    
    // 3.Condition4的场景
    // widget是新建的,但是widget的runtimeType和key相同。
    } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
      if (child.slot != newSlot) {
        updateSlotForChild(child, newSlot);
      }
      // 因为widget是新建的,所以需要
      child.update(newWidget);
      newChild = child;
      
    } else {
      // 4.Condition5的场景:
      // newWidget与child.widget的runtimeType或key不相同,所以要先移除child(Element),然后新建newWidget对应的Element。
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
    
  } else {
    // 5.Condition1场景:
    // 即child==null && newWidget==null成立,相当于新增widget。
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}

/**
 * Widget.class
 * 作用:比较两个widget的runtimeType和key是否相同。
 */
static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
}

上面代码中展示了5种Condition的触发场景。

小结:

  • 当 newWidget == null 时,说明是移除当前位置的Element。
    - Condition1:此时如果 child(Element) == null,直接返回 null。
    - Condition2:此时如果 child != null,说明之前这个位置有Element,则先移除 Element,再返回 null。
  • 当 newWidget != null 时,说明是在新增/修改当前位置Element。
    - Condition3:此时如果 child(Element) == null,说明是新增Element的场景,因此直接使用newWidget构建Element。
    - 此时如果 child != null,说明之前这个位置有Element,这是又分为两种情况。
    - Condition4:当Element可以复用时(即newWidget的runtimeType和key相同与child.widget相同时),所以直接更新旧的 Element 即可。
    - Condition5:当Element不可以复用时,使用newWidget重新构建 Element 并返回。

3.3.2 Element.inflateWidget()

Element inflateWidget(Widget newWidget, Object? newSlot) {
  final Key? key = newWidget.key;
  // 1.复用Element的逻辑。
  if (key is GlobalKey) {
  	// 1.1 查找复用Element.
    final Element? newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      // 1.2 将复用的Element关联到Element树上,即与parent建立关系。
      newChild._activateWithParent(this, newSlot);
      // 1.3 因为是复用的,所以只需要执行更新逻辑即可。
      final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
      return updatedChild!;
    }
  }
  // 2.创建 newWidget 的 Element。
  final Element newChild = newWidget.createElement();
  // 3. 触发Element.mount()方法,执行构建逻辑。
  newChild.mount(this, newSlot);
  return newChild;
}

Element? _retakeInactiveElement(GlobalKey key, Widget newWidget) {
  // 1.先从BuildOwner中查找对应的GlobalKey.
  final Element? element = key._currentElement;
  if (element == null)
    return null;
  // 2.查找GlobalKey相同的Element后,需要对比两个widget的runtimeType或key是否相同.
  if (!Widget.canUpdate(element.widget, newWidget))
    return null;
    
  // 3.到这里说明已经找到了可以复用的Element。
  // 因为复用的Element是在Element树上的,所以要断开它原先的父/子节点。相当于双链表删除一个Node节点的操作。
  // 3.1 查找当前节点的父节点。
  final Element? parent = element._parent;
  if (parent != null) {
  	// 3.2 父节点断开对当前节点的引用信息。
    parent.forgetChild(element);
    // 3.3 子节点断开与父节点的引用关系。
    parent.deactivateChild(element);
  }
  // 4.Element被复用了,所以从_inactiveElements移除。
  owner!._inactiveElements.remove(element);
  return element;
}

小结: Element.inflateWidget() 方法执行 Element 的构建,在构建时会尝试复用之前的 Element (优化的手段),避免重复创建 ELement。

  1. 判断 newWidget 是否有 GlobalKey,如果有GlobalKey,则尝试从 InactiveElements 列表中查找 Element 并进行复用。
    1. 先从BuildOwner中查找对应的GlobalKey。
    2. 查到GlobalKey相同的Element后,对比两个widget的runtimeType或key是否相同。
    3. 到这里已经可以拿到复用的Element了,因此将复用的Element从Element树上卸载再来。
    4. 将拿到的Element从BuildOwner._inactiveElements 中移除。
    5. 执行完上面步骤后,Element 就从 Element 树上剥离了,此时需要将它挂载到新的 parent 节点下。
    6. 执行 Element 的更新逻辑。
  2. 无可复用Element,则根据 newWidget 创建对应的 Element,并将其挂载至 Element 树。

3.3.3 Element.mount()

// Element.class
abstract class Element extends DiagnosticableTree implements BuildContext {
	void mount(Element? parent, Object? newSlot) {
	  _parent = parent;
	  _slot = newSlot;
	  // 设置当前Element的状态为active。
	  _lifecycleState = _ElementLifecycle.active;
	  // Element元素的层级。
	  _depth = _parent != null ? _parent!.depth + 1 : 1;
	  if (parent != null) { //传递BuildOwner
	    _owner = parent.owner;
	  }
	  final Key? key = widget.key;
	  if (key is GlobalKey) {
	    // 这里就是Element.inflateWidget()方法中复用Element的逻辑。
	    owner!._registerGlobalKey(key, this);
	  }
	  _updateInheritance();
	}

	void rebuild() {
		// 通过判断当前Element状态以及_dirty的状态,来减少不必要的绘制。
	    if (_lifecycleState != _ElementLifecycle.active || !_dirty)
	      return;
    	performRebuild();
	}
}


// ComponentElement.class
abstract class ComponentElement extends Element {
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    // Element挂载到Element树上的时候,会触发首次的构建流程。
    _firstBuild(); 
  }

  void _firstBuild() {
    rebuild(); // Element.rebuild()
  }
}

小结:

当 Element 第一次被插入Element 树时,会调用Element.mount()方法。其功能如下:

  • 将给 Element 挂载到 Element 树上,然后更新_parent,_slot,层级等树相关的属性。
  • 如果新Widget有GlobalKey,将该 Element 注册进 GlobalKey 中,在Element.inflateWidget()方法的复用Element的地方会被使用。
  • Element 的子类会重载 mount() 方法,如在 ComponentElement.mount() 方法会调用_firstBuild函数,触发子Widget的创建和更新。

3.3.4 ComponentElement.performRebuild()

void performRebuild() {
  Widget? built;
  try {
    // 1.触发ComponentElement.build()逻辑,生成子Widget。
    // 这里会触发StatelessWidget.build() 或 State.build()的调用。
    built = build();
  } catch (e, stack) {
    built = ErrorWidget.builder(...);
  } finally {
    _dirty = false;
  }
  try {
	// 2.根据新的子Widget更新子Element
    _child = updateChild(_child, built, slot);
  } catch (e, stack) {
    built = ErrorWidget.builder(...);
    _child = updateChild(null, built, slot);
  }
}

小结:

ComponentElement.performRebuild() 会做两件事。

  1. 构建子 Widget。
  2. 根据新的子Widget 更新子Element。

3.3.5 Element.update()

在ComponentElement.performRebuild()中,我们看到最终又调用 Element.updateChild() 方法进行子节点的创建, Element.updateChild() 方法我们已经分析过了新建子节点的过程,下面我们来分析下子节点的更新逻辑。

Element.updateChild() 方法中 Condition4 即为更新子节点的逻辑,他会调用 Element.update() 方法来执行更新逻辑。

// Element.class
void update(covariant Widget newWidget) {
  _widget = newWidget;
}

// StatelessElement.class
void update(StatelessWidget newWidget) {
  super.update(newWidget);
  // 1._dirty标记为true,在Element.rebuild()方法中会对这个标识进行判断。
  _dirty = true;
  // 2.执行构建逻辑。
  rebuild();
}

// StatefulElement.class
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = state._widget!;
  // 1._dirty标记为true,在Element.rebuild()方法中会对这个标识进行判断。
  _dirty = true;
  state._widget = widget as StatefulWidget;
  // 2.触发State.didUpdateWidget()进行更新。
  final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
  // 3.执行构建逻辑。
  rebuild();
}

小结:

  • 不同的 Element 会重写 update() 方法,执行特定的逻辑。
  • 基类中的 Element.update() 方法主要是将对应的Widget更新为新的Widget。
  • 在 StatelessElement 中,会将当前 Element 标识为 dirty,并调用 rebuild 方法触发对子Widget的重建。
  • 在 StatefulElement 中,会将当前 Element 标识为 dirty,然后调用 State.didUpdateWidget() 方法执行对应的更新逻辑, 最后调用 rebuild 方法触发对子Widget的重建。

3.3.6 Element 的销毁流程

Element 的销毁流程主要分为两步:

  • Step1:标记Element状态。将要销毁的 Element 从 Element树中移除,并将要销毁的 Element 及其子Element 的状态改为 inactive。
  • Step2:卸载Element。当触发下一帧的渲染回调时,会调用到 BuildOwner.finalizeTree() 方法,最终会调用到 Element.unmount() 方法。

Step1 的调用流程如下:

Element.deactivateChild()
-> BuildOwner._inactiveElements.add(Element) (即:_InactiveElements.add())
-> _InactiveElements._deactivateRecursively():
-> Element.deactivate()

Step2 的调用流程如下:

RendererBinding.initInstances()
-> RendererBinding._handlePersistentFrameCallback()
-> Widget.drawFrame()
-> buildOwner!.finalizeTree()
-> _InactiveElements._unmountAll()
-> _InactiveElements._unmount()
-> Element.unmount()

Step1的流程:

void deactivateChild(Element child) {
// 断开child与parent的关联,即从Element树中移除。
  child._parent = null;
  // 触发RenderObject组件对应的声明周期。
  child.detachRenderObject();
  // 放入未激活状态的Element回收池。
  owner!._inactiveElements.add(child); // this eventually calls child.deactivate()
}

// _InactiveElements.class
class _InactiveElements {

  void add(Element element) {
    if (element._lifecycleState == _ElementLifecycle.active)
      // 当前Element状态如果为active,需要将它及其子节点的状态都变成inactive。
      _deactivateRecursively(element);
    // 将传入的Element放入_elements中,当下一帧绘制开始时,会触发BuildOwner.finalizeTree()方法,在这个方法中通过递归遍历的方式将_elements中的Element及其子节点全部卸载。
    _elements.add(element);
  }
  
  /**
   * 递归遍历,将传入的Element及其所有子Element状态都变成inactive。
   * 遍历方式:树的先序遍历。
   */
  static void _deactivateRecursively(Element element) {
    element.deactivate(); //将 Element 状态标记为inactive
    element.visitChildren(_deactivateRecursively);
  }
}

/**
 * Element.class
 * 将 Element 状态标记为inactive
 */ 
void deactivate() {
    if (_dependencies != null && _dependencies!.isNotEmpty) {
      for (final InheritedElement dependency in _dependencies!)
        dependency._dependents.remove(this);
    _inheritedWidgets = null;
    _lifecycleState = _ElementLifecycle.inactive;
  }

小结:

Step1的执行流程:将要销毁的 Element 从 Element树中移除,并将要销毁的 Element 及其子Element 的状态改为 inactive。

Step2的流程:

// BuildOwner.class
void finalizeTree() {
  lockState(() {
  	// 在Element.deactivateChild()中,会将移除的Element放入_inactiveElements。
    _inactiveElements._unmountAll(); // this unregisters the GlobalKeys
  });
}

class _InactiveElements {
  void _unmountAll() {
    _locked = true;
    final List<Element> elements = _elements.toList()..sort(Element._sort);
    _elements.clear();
    try {
      elements.reversed.forEach(_unmount); //_unmount是个方法。
    } finally {
      _locked = false;
    }
  }

  /**
   * 卸载传入Element下的所有子Element。
   * 通过递归方式进行遍历,先卸载叶子节点,在卸载父节点。
   */
  void _unmount(Element element) {
  	// 访问当前Element的所有子Element。
    element.visitChildren((Element child) {
      _unmount(child);
    });
    // 调用 Element.unmount()方法进行资源释放。
    element.unmount();
  }
}

小结:

Step2的执行流程:卸载Element。当触发下一帧的渲染回调时,会调用到 BuildOwner.finalizeTree() 方法,最终会调用到 Element.unmount() 方法。

到这里 Element 的创建流程、更新流程和销毁流程就已经介绍完了。


四、RenderObjectElement

4.1 Widget、Element、RenderObject 之间的关系

在这里插入图片描述

小结:

  • RenderObjectElement 持有 Parent Element,但是不一定持有Child Element,有可能无Child Element,有可能持有一个Child Element(Child),有可能持有多个Child Element(Children)。
  • RenderObjectElement 持有对应的 Widget 和 RenderObject,将Widget、RenderObject串联起来,实现了 Widget、Element、RenderObject 之间的绑定。

4.2 核心操作流程

与 ComponentElement 一样,RenderObjectElement 的核心操作流程包括:Element 的创建、更新、销毁,下面将分别分析这三个流程。

4.2.1 创建流程

RenderObjectElement 的创建流程和 ComponentElement 的创建流程基本一致,其最大的区别是ComponentElement在mount后,会调用 build 创建子Widget,而RenderObjectElement 则是 create 和 attach 其 RenderObject。

在这里插入图片描述

4.2.2 更新流程

RenderObjectElement 的更新流程和 ComponentElement 的更新流程也基本一致,区别在于:

  • ComponentElement 的 update() 方法会调用 build() 方法,重新触发子Widget的构建。
  • RenderObjectElement 则是调用 updateRenderObject 对绑定的 RenderObject 进行更新。

在这里插入图片描述

4.2.3 销毁流程

RenderObjectElement 的销毁流程和 ComponentElement 的销毁流程也基本一致。都是由父Element或更上级的节点执行更新子节点的操作(updateChild),导致该Element被停用,并被加入未激活列表,在下一帧触发回调时进行卸载。RenderObjectElement 的销毁操作与 StatefulElement 比较相似:

  • RenderObjectElement 调用 unmount() 方法时,会调用 Widget.didUnmountRenderObject() 方法进行资源的销毁。
  • StatefulElement 调用 unmount() 方法时,会调用 State.dispose() 方法进行资源的销毁。

在这里插入图片描述

4.3 核心方法分析

4.3.1 Element.inflateWidget()

该方法属于Element,所以RenderObjectElement子类都没有重写。在 ComponentElement 部分已经分析过了,此处不在赘述。

4.3.2 Element.mount()

// RenderObjectElement.class
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  // 1.Widget触发RenderObject的创建。
  _renderObject = widget.createRenderObject(this);
  // 2.将当前的RenderObject关联到RenderObject树中。
  attachRenderObject(newSlot);
  _dirty = false;
}

// SingleChildRenderObjectElement.class
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  // 调用 Element.updateChild()方法。
  _child = updateChild(_child, widget.child, null);
}

// MultiChildRenderObjectElement.class
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  // 我们在Widget中设置的children属性。
  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) {
  	// 遍历生成所有的子Widget.
    final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
    children[i] = newChild;
    previousChild = newChild;
  }
  _children = children;
}

小结:

  • RenderObjectElement.mount() 方法中调用 createRenderObject 创建 RenderObject,并使用 attachRenderObject 将 RenderObject 通过Element树关联到 RenderObject 树上。
  • SingleChildRenderObjectElement 调用 updateChild() 方法创建/更新子节点。
  • MultiChildRenderObjectElement 调用每个子节点的 inflateWidget 重建所有子Widget和子Element。

4.3.3 RenderObjectElement.performRebuild()

void performRebuild() {
  // 触发Widget对应的声明周期函数。
  // 即在updateRenderObject方法中更新对应的RenderObject。
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

小结:

  • 调用 Widget.updateRenderObject() 方法更新对应的RenderObject。

4.3.4 RenderObjectElement.update()

// RenderObjectElement.class
void update(covariant RenderObjectWidget newWidget) {
  super.update(newWidget);
  // 触发Widget对应的声明周期函数。
  // 即在updateRenderObject方法中更新对应的RenderObject。
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

// SingleChildRenderObjectElement.class
void update(SingleChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  // 调用Element.updateChild()触发更新逻辑。该方法在ComponentElement部分已经分析过。
  _child = updateChild(_child, widget.child, null);
}

// MultiChildRenderObjectElement.class
void update(MultiChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  // 更新子节点,这部分比较复杂,后面单独分析。
  _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
  _forgottenChildren.clear();
}

小结:

  • RenderObjectElement.update() 方法将对应的 Widget 更新为新的 Widget。
  • RenderObjectElement.update() 方法内调用 updateRenderObject 更新对应的RenderObject。

4.3.5 RenderObjectElement.updateChildren()

List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren, List<Object?>? slots }) {

  Element? replaceWithNullIfForgotten(Element child) {
    return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
  }

  Object? slotFor(int newChildIndex, Element? previousChild) {
    return slots != null
      ? slots[newChildIndex]
      : IndexedSlot<Element?>(newChildIndex, previousChild);
  }

  // ------------ START --------------
  
  int newChildrenTop = 0;
  int oldChildrenTop = 0;
  int newChildrenBottom = newWidgets.length - 1;
  int oldChildrenBottom = oldChildren.length - 1;

  final List<Element> newChildren = oldChildren.length == newWidgets.length ?
      oldChildren : List<Element>.filled(newWidgets.length, _NullElement.instance, growable: false);

  Element? previousChild;
  
  // 1.从上到下开始遍历新旧两棵树,比较同一个位置的Widget是否可以复用。
  // - 如果可以复用Widget,就复用Widget进行更新,然后比较下一个位置。
  // - 如果不可以复用,则退出循环。
  // 复用的标准是Widget的runtimeType和key都相同
  // Update the top of the list.
  while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
    final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
    final Widget newWidget = newWidgets[newChildrenTop];
    if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
      break;
    final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
    previousChild = newChild;
    newChildrenTop += 1;
    oldChildrenTop += 1;
  }

  // 2.从下到上开始遍历新旧两棵树,比较同一个位置的Widget是否可以复用。
  // - 如果可以复用Widget,就复用Widget进行更新,然后比较下上个位置。
  // - 如果不可以复用,则退出循环。
  // Scan the bottom of the list.
  while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
    final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
    final Widget newWidget = newWidgets[newChildrenBottom];
    if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
      break;
    oldChildrenBottom -= 1;
    newChildrenBottom -= 1;
  }

  // 3.尝试将新旧两棵树不一样的Element进行回收,等待复用。
  // haveOldChildren为true表示新旧两棵树存在差异,需要进行额外处理。
  // Scan the old children in the middle of the list.
  final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
  // Element复用的缓存池。
  Map<Key, Element>? oldKeyedChildren;
  if (haveOldChildren) {
    oldKeyedChildren = <Key, Element>{};
    while (oldChildrenTop <= oldChildrenBottom) {
      final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
      if (oldChild != null) {
        // 存放有key的的Element.
        if (oldChild.widget.key != null)
          oldKeyedChildren[oldChild.widget.key!] = oldChild;
        else
          deactivateChild(oldChild); //释放掉没有设置Key的Element
      }
      oldChildrenTop += 1;
    }
  }

  // 4.更新新旧两棵树差异的部分。
  // 4.1 先尝试从Element复用池oldKeyedChildren中获取,满足条件了就直接复用。
  // 4.2 不满足复用条件,则直接新建ELement。
  // Update the middle of the list.
  while (newChildrenTop <= newChildrenBottom) {
    Element? oldChild;
    final Widget newWidget = newWidgets[newChildrenTop];
    if (haveOldChildren) {
      final Key? key = newWidget.key;
      if (key != null) {
        oldChild = oldKeyedChildren![key];
        if (oldChild != null) {
          if (Widget.canUpdate(oldChild.widget, newWidget)) {
            // we found a match!
            // remove it from oldKeyedChildren so we don't unsync it later
            oldKeyedChildren.remove(key);
          } else {
            // Not a match, let's pretend we didn't see it for now.
            oldChild = null;
          }
        }
      }
    }
    final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
    newChildren[newChildrenTop] = newChild;
    previousChild = newChild;
    newChildrenTop += 1;
  }

  // 5.复位newChildrenBottom和oldChildrenBottom指向的位置。
  // We've scanned the whole list.
  newChildrenBottom = newWidgets.length - 1;
  oldChildrenBottom = oldChildren.length - 1;

  // 6.遍历:从第二步的从下往上更新停止的位置开始,到最后一个位置。
  // 为什么第二步更新过之后,这里还需要更新呢?
  // 因为在新旧两棵树差异化的部分,可能存在ELement所在的slot位置发生变化,所以需要重新执行更新的逻辑。
  // Update the bottom of the list.
  while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
    final Element oldChild = oldChildren[oldChildrenTop];
    final Widget newWidget = newWidgets[newChildrenTop];
    final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
    newChildren[newChildrenTop] = newChild;
    previousChild = newChild;
    newChildrenTop += 1;
    oldChildrenTop += 1;
  }

  // 7.oldKeyedChildren中存在部分没有复用的元素,需要对这部分元素进行回收。
  // Clean up any of the remaining middle nodes from the old list.
  if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
    for (final Element oldChild in oldKeyedChildren.values) {
      if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
        deactivateChild(oldChild);
    }
  }
  return newChildren;
}

小结:

RenderObjectElement.updateChildren() 方法的主要职责:

  • 复用能复用的子节点,并调用updateChild对子节点进行更新。
  • 对不能更新的子节点,调用deactivateChild对该子节点进行失效。

步骤如下:

  1. 从顶部向下更新子Element。
  2. 从底部向上扫描子Element。
  3. 扫描旧的子Element列表里面中间的子Element,保存Widget有Key的Element到oldKeyChildren,其他的失效。
  4. 对于新的子Element列表,如果其对应的Widget的Key和oldKeyChildren中的Key相同,更新oldKeyChildren中的Element。
  5. 从下到上更新底部的Element。
  6. 清除旧子Element列表中其他所有剩余Element。

动图效果可以参考:Flutter 必知必会系列—— Element 的更新复用机制


五、小结

本文主要介绍了Element相关知识:

  1. 介绍了 Element 的分类 (ComponentElement、RenderObjectElement),及其不同类型的特点。
    2. ComponentElement 负责组合子Element。
    3. RenderObjectElement 后者负责渲染。
  2. 介绍了 Element 的生命周期,以及核心流程 (创建、更新、销毁)。
  3. Element 树的三棵树的核心,通过 Element 树可以构建 Widget 树和 RenderObject 树。
  4. Element 的主要复用和更新逻辑由其核心方法 updateChild 实现,具体逻辑见上文。

Element树与Widget树关联关系:

在这里插入图片描述

六、参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值