Flutter核心类分析深入理解Element

Posted 牧羊人.阿标

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter核心类分析深入理解Element相关的知识,希望对你有一定的参考价值。

背景

通过前面的文章深入理解Widget我们知道,Widget本质上是Element的数据配置项,Element是通过Widget.createElement生成实例。同一个Widget可以创建多个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生命周期原有比这复杂得多,这里只是列出了大概的流程。

  • 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 是有状态的。

以上是关于Flutter核心类分析深入理解Element的主要内容,如果未能解决你的问题,请参考以下文章

Flutter核心类分析深入理解Element

Flutter核心类分析深入理解RenderObject

Flutter核心类分析深入理解RenderObject

Flutter核心类分析深入理解RenderObject

Flutter核心类分析深入理解Widget

Flutter核心类分析深入理解Widget