Flutter framework学习

242 阅读10分钟
  • 对Element的新建更新等流程分析
  • 分析BuildOwner的作用
  • State的生命周期与Element的联系

更详细的文章请看这里,详细介绍了Widget、Element...

Element

Element生命周期

被创建

开始于parent调用inflateWidget,随之被挂载到Element Tree,然后递归子节点 image.png

  1. parentElement.inflateWidget新建childElement
  2. childElement.mount挂载

Component Element

  1. childElement._firstBuild->...->build,调用build构建界面
  2. childElement.updateChild继续更新子节点
  3. ...

RenderObject Element

  1. widget.createRenderObject,创建RenderObject对象
  2. attachRenderObject(),将renderObject对象附加render Tree上
  3. 如果是single child,直接更新child,如果是muti child,遍历child执行infalteWidget

被更新

Element Tree上的祖先节点传递下来的更新操作 image.png

  1. parentElement.updateChild根据方法canUpdate(如果为true)更新子元素
  2. childElement.update开始更新子元素(基类Element只把老的widget替换成了新传入的newWidget)

Component Element

  1. childElement.rebuild-> childElement.performRebuild....(下面的细节和被"创建"的流程一样)

RenderObject Element

  1. widget(child).updateRenderObject,当是多子节点元素时调用updateChildren遍历孩子节点执行updateChild;单子节点调用updateChild更新孩子

被重建

更新后的重建操作,重建->递归更新子节点 image.png

被销毁

element 节点所在的子树随着 UI 的变化被移除。 image.png

可以看到updateChild是更新子节点的入口,在这里决定了是走inflateWidget新建,还是走update

  • 如果新建,parentElement.updateChild -> parentElement.inflateWidget -> widget.createElement -> childElement.mount -> .... -> childElemnet.updateChild -> ...
  • 如果更新,parentElement.updateChild -> childElement.update -> ... -> childElemnet.updateChild -> ...

注意,关于Stateful Element的更新和重建操作

  • 更新的关键操作是update()方法,是通过parent调用updateChild中调用
    • 该场景是:parent(或者某个祖先节点)在经过setState后被加入到owner._dirtyElements中,并在下一帧时候调用了rebuild引起的子节点更新
    • 调用链是:rebuild -> performBuild
  • 重建的关键操作是rebuild()方法,是当前节点被加入到_dirtyElements中
    • 该场景是当前节点setState被标记

简而言之,更新是指element当作子节点,重建是指element当作根节点

重要方法

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot){}

更新child,在此方法里面决定对child的操作(新建、更新、移除)

参数

  • child:child element
  • newWidget:对应新的widget
  • newSlot:槽口

Element

Element? updateChild(Element? child, Widget? newWidget, Object? 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;
}
newWidget == nullnewWidget != null
child = nullreturn null新建child,return newChild
child != nullchild移除,return null如果可以更新位置就更新位置;如果需要更新widget就更新widget;否则先移除child再新建child

Element inflateWidget(Widget newWidget, Object? newSlot) {}

通过 Widget 创建对应的 Element,并将其挂载 (mount) 到「Element Tree」上。

Element inflateWidget(Widget newWidget, Object? newSlot) {
  final Key? key = newWidget.key;
  if (key is GlobalKey) {
    final Element? newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      newChild._activateWithParent(this, newSlot);
      final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
      return updatedChild!;
    }
  }
  final Element newChild = newWidget.createElement();
  newChild.mount(this, newSlot);
  return newChild;
}
  • 通过newWidget.createElement()创建element对象newChild
  • 调用newChild.mount(this, newSlot)进行挂载
  • 如果 Widget 带有 GlobalKey,首先在 Inactive Elements 列表中查找是否有处于 inactive 状态的节点 (即刚从树上移除),如找到就直接复活该节点。
  • 此方法一般都是通过updateChild()调用

void mount(Element? parent, Object? newSlot){}

将此元素添加到给定父节点的给定槽位的Element Tree中。

Element

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;//传递BuilderOwner对象
  }
  final Key? key = widget.key;
  if (key is GlobalKey) {
    owner!._registerGlobalKey(key, this);//如果是GlobalKey,将该element注册到owner管理的_globalKeyRegistry中
  }
  _updateInheritance(); //更新Inheritance
}
  • 获取parent的owner(我们知道BuilderOwner是从根节点往传递的,子节点获取owner的方式就是在mount方法)
  • 将GlobalKey类型的widget注册到owner管理的_globalKeyRegistry中
  • 更新Inheritance(获取parent的_inheritedWidgets,inheritedWidegts也是从parent往下传递的)

ComponentElement (extends Element)

void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _firstBuild();
}
  • 通过上面的流程图我们知道,在Element被创建时候会调用mount -> _firstBuild - rebuild -> performRebuild -> build -> updateChild

RenderObjectElement (extends Element)

@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}
  • widget.createRenderObject(this)创建RenderObject对象
  • attachRenderObject(newSlot),将RenderObject添加到Render Tree中的newSolt位置

SingleChildRenderObjectElement(extends RenderObjectElement)

void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _child = updateChild(_child, widget.child, null);
}
  • 和流程图一致,single child:createRenderObject -> attachRenderObject -> updateChild

MultiChildRenderObjectElement(extends RenderObjectElement)

void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance);
  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;
}
  • 和流程图一致,multi child:createRenderObject -> attachRenderObject -> 遍历children调用inflateWidget

void update(covariant Widget newWidget)

Element

void update(covariant Widget newWidget) {
  _widget = newWidget;
}
  • Element实现的update只有一个作用,更改用于配置此element的widget

ComponentElement

未实现

StatelessElement (extends ComponentElement)

void update(StatelessWidget newWidget) {
  super.update(newWidget);
  _dirty = true;
  rebuild();
}
  • 将其标记为_dirty
  • 从流程图中的“被更新”步骤我们看出,update()会执行rebuild(),完整流程是:StatelessElement.update -> Element.rebuild -> Component.performRebuild -> StatelessWidget.build -> updateChild

StatefulElement(extends ComponentElement)

void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = state._widget!;
  _dirty = true;
  state._widget = widget as StatefulWidget;
  try {
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
    final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
  } finally {
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
  }
  rebuild();
}
  • 和StatelessElement差不多,不同的是
    • state._widget = widget(众所周知,StatefulElement的widget是被state持有的)
    • 调用didUpdateWidget方法(这下应该清楚State生命周期里的didUpdateWidget是什么时候被调用了,当element被parent更新时)

ProxyElement (extends ComponentElement)

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

@protected
void updated(covariant ProxyWidget oldWidget) {
  notifyClients(oldWidget);
}
  • rebuild逻辑与StatelessElememt一样,唯一不同的是build方法直接返回widget.child
  • 更需要关注updated方法,其主要用于通知关联对象 Widget 有更新。 具体通知逻辑在子类中处理
    • InheritedElement,会触发所有依赖者 rebuild (对于 StatefulElement 类型的依赖者,会调用State.didChangeDependencies
    • ParentDataWidget,会触发parentDataWidget.applyParentData(renderObject)更新renderObject的信息时
InheritedElement
void notifyClients(InheritedWidget oldWidget) {
  assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
  for (final Element dependent in _dependents.keys) {
    notifyDependent(oldWidget, dependent);
  }
}

void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  dependent.didChangeDependencies();
}
  • 很明显了,遍历依赖对它的对象,并调用didChangeDependencies()(现在我们知道Widget生命周期的didChangeDependencies什么时候会被调用了)

ParentDataElement (extends ComponentElement)

void notifyClients(ParentDataWidget<T> oldWidget) {
  _applyParentData(widget);
}

void _applyParentData(ParentDataWidget<T> widget) {
  void applyParentDataToChild(Element child) {
    if (child is RenderObjectElement) {
      child._updateParentData(widget);
    } else {
      assert(child is! ParentDataElement<ParentData>);
      child.visitChildren(applyParentDataToChild);
    }
  }
  visitChildren(applyParentDataToChild);
}

void _updateParentData(ParentDataWidget<ParentData> parentDataWidget) {
  bool applyParentData = true;
  if (applyParentData)
    parentDataWidget.applyParentData(renderObject);
}
  • 如果child是RenderObjectElement执行_updateParentData方法,
    • _updateParentData调用到widget里面,并传入renderObject
  • 如果child不是RenderObjectElement,递归子元素

RenderObjectElement(extends Element)

@override
void update(covariant RenderObjectWidget newWidget) {
  super.update(newWidget);
  _performRebuild(); // calls widget.updateRenderObject()
}

@override
void performRebuild() {
  _performRebuild(); // calls widget.updateRenderObject()
}

void _performRebuild() {;
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}
  • 很好理解,更新RenderObject
  • 注意updateRenderObject是被widget调用的
SingleChildRenderObjectElement (extends RenderObjectElement)
@override
void update(SingleChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  assert(widget == newWidget);
  _child = updateChild(_child, widget.child, null);
}
  • 和流程图一致
MultiChildRenderObjectElement (extends RenderObjectElement)
void update(MultiChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
  _forgottenChildren.clear();
}
  • 上述实现看似简单,实则非常复杂,在updateChildren方法中处理了子节点的插入、移动、更新、删除等所有情况。

markNeedsBuild

将元素标记为dirty,并将其添加到widget的全局列表(owner)中 在下一帧重建。

子类无需重写。

调用该方法的情况:

  • setState
  • reassemble (热重载)
  • Element.didChangeDependencies (依赖变更)
  • StatefulElement.activate (重新被激活)

rebuild

对于active&&dirty的节点调用performRebuild(), 调用该方法的情况:

  • Element.mount(挂载时)
  • BuildOwner.buildScope (新的一帧时,owner在该方法中对dirtyElements数组遍历调用rebuild)
  • Element.update

performRebuild

ELement基类未实现

ComponentElement

void performRebuild() {
  Widget built;
  built = build();

  _child = updateChild(_child, built, slot);
}

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

RenderObjectElement

void performRebuild() {
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}
  • 在渲染型 Element 基类中只是用 Widget 更新了对应的「Render Object」。 在相关子类中可以执行更具体的逻辑

State

State的生命周期是比较重要的知识点,借图

image.png

关于Widget、State和Element的关系:

  • Widget只是Element的UI配置,Element会根据Widget的变化而更新(节点插入、更新、删除、移动等)
  • Element通过parent,child组成了Element Tree
  • Element持有Widget,Render Object
  • State是绑定在Element上的,而不是Stateful Widget上的

为什么要用StatefulWidget,和Stateless的区别,以及State的作用

众所周知,widgets are immutable,所以如果我们想修改StatelessWidget是不允许的; 这时候我们可以使用StatefulWidget,

  • StatefulWidget本身也是不可变的,通过createState创建State对象并将其绑定在对应的Element上,
  • State对象管理了StatefulWidget的逻辑和内部状态,并维护了一系列的生命周期(上图),它的生命周期都是和Elemetn关联的
  • 当需要改变UI时,在State里面调用setState方法将当前Element标记,并在下一帧重回
  • 当有状态组件的配置发生更改时,StatefulWidget 将会被丢弃并重建,而 State 不会重建,框架会更新 State 对象中 widget 的引用,这极大的减轻了系统重建有状态组件的工作。此方式对动画来说极为重要,由于 State 不会被重建,保留了前面的状态,不断的根据前一个状态计算下一个状态并重建其widget,达到动画的效果。

通过上面的总结,我们应该明白,为了减少不必要的重绘,尽量将需要改变状态的小部件封装成单个的StatefulWidget,而不是无所顾忌的在比较“高层”的地方调用setState

为什么能够在State中访问widge

StatefulElement(StatefulWidget widget)
    : state = widget.createState(),
      super(widget) {
  state._element = this;
  state._widget = widget;
}
  • 在StatefulElement初始化时候,将当前widget绑定给state

State里面访问的context是什么

abstract class Element extends DiagnosticableTree implements BuildContext 
  • Element实现了BuildContext,所以BuildContext就是Element

State生命周期与Element

State的生命周期都是被Element管理的

  1. State():在StatefulElement被创建时创建State
    • StatefulElement(StatefulWidget widget) -> state = widget.createState()
  2. initState():在StatefulElement被mount(挂载)到树上后,调用initState
    • mount() -> _firstBuild() -> state.initState()
  3. didChangeDependencies():依赖变更,触发场景:
    • 在StatefulElement被挂载完成时(_firstBuild)
    • 在StatefulElement被触发更新时
      • update -> rebuild -> performRebuild -> didChangeDependencies
    • 依赖了InheritedWidget的widget,在InheritedWidget被更新时会被调用
  4. build():StatefulWidget构建子widget的方法,触发场景有:
    • StatefulElement被挂载时(mount)
      • mount -> _firstBuild -> rebuild -> performRebuild -> build
    • StatefulElement被重建时
      • rebuild -> performRebuild -> build
    • StatefulElement被更新时
      • update -> rebuild -> performRebuild -> build
  5. didUpdateWidget():State要更新了,有个注意点,这个方法只有在element作为子节点被更新时才会触发;当element被当作根节点重建时不会触发(更新和重建的区别看上面)
    • StatefulElement被更新时
      • update -> state.didUpdateWidget
  6. deactivate():Element被设置为不活跃时调用(即将被移除)
    • 不活跃是指StatefulElement的状态为inactive,此时是指该element从树上取下,并即将销毁
    • 当Element需要被移除时,加入到BuildOwner管理的_inactiveElements中,在加入的过程中会执行deactivate()
      • updateChild -> deactivateChild -> owner!._inactiveElements.add(child) -> _deactivateRecursively(element) -> element.deactivate() -> state.deactivate()
  7. build():注意流程图里面还可能会调用一次build,是因为inactive状态的element可能还会被复活(前提是没被丢弃)
  8. dispose:走到这里就说明StatefulElement彻底被丢弃了,调用时机是:
    • 被加入到_inactiveElements的element在当前帧结束时执行element.unmount()
      • _inactiveElements._unmountAll() -> element.unmount() -> state.dispose();

InheritedWidget

详细看这里juejin.cn/post/708128…

  • 传递:InheritedElement是从根节点往下传递的(是放入Map中传递)
  • 获取:通过BuildContext(就是Element对象),查找自身存储InheritedElement的Map,返回相应的InheritedWidget,并且会把当前element加入到InheritedElement管理的_dependents中
  • 更新:InheritedElement会遍历_dependents调用didChangeDependencies(),随即出发state的didChangeDependencies()

BuildOwner

BuildOwner是管理Widget的对象

主要有三个作用:

  • 在 UI 更新过程中跟踪、管理需要 rebuild 的 Element (「dirty elements」);
  • 在有「dirty elements」时,及时通知引擎,以便在下一帧安排上对「dirty elements」的 rebuild,从而去刷新 UI;
  • 管理处于 "inactive" 状态的 Element。

创建及传递时机

  1. 在WidgetsBinding的初始化方法里创建一个_buildOwner实例。
  2. 传递给RootElement
    • 通过WidgetsBinding.attachRootWidget创建一个RenderObjectToWidgetAdapter对象
    • RenderObjectToWidgetAdapter.attachToRenderTree将owner分配给RenderObjectToWidgetElement
  3. 传递给child
    • 在每次将Element对象挂载(mount)到树上时,会将parent.owner传递给child

管理需要rebuild的Element

添加标记并加入_dirtyElements数组

在State里面调用setState(){}时,会将该Element对象_element标记为dirty(脏的),并将该_element加入到BuildOwner对象管理的数组中(_dirtyElements)

void setState(VoidCallback fn) {
  _element!.markNeedsBuild();
}

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

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

rebuild

随后在下一帧时候对_dirtyElements中的Element对象调用rebuild()方法(WidgetsBinding.drawFrame -> WidgetsBinding.buildScop

  • WidgetsBinding.drawFrame:生成每帧的方法
  • WidgetsBinding.buildScope:更新widget Tree的方法
void drawFrame() 
  buildOwner!.buildScope(renderViewElement!);
}

void buildScope(Element context, [ VoidCallback? callback ]) {
  _dirtyElements[index].rebuild();
}

(以上代码段并非这么简单,比如buildScope有其他逻辑(排序、将Element对象解除标记等等)。);

管理inactive的Element

BuildOwner管理了非活跃的element,并将其存放于_inactiveElements

添加到_inactiveElements

当一个element需要移除时,BuildOwner将该element加入_inactiveElements

class _InactiveElements {
 
 //添加element到_InactiveElements管理的_elements中
  void add(Element element) {
  if (element._lifecycleState == _ElementLifecycle.active)
    _deactivateRecursively(element);
    _elements.add(element);
  }
  
  //递归方法:以该element为根节点的子树的所有节点调用`deactivate`(移除的是整颗子树)
  static void _deactivateRecursively(Element element) {
    element.deactivate();
    element.visitChildren(_deactivateRecursively);
  }
}

从_inactiveElements移除

当deactivate的element又需要使用时(前提是没有被丢弃),将其从_inactiveElements中移除

class _InactiveElements {
  void remove(Element element) {
    _elements.remove(element);
  }
}

丢弃element

当element需要彻底丢弃时(当前帧结束,并且element在_inactiveElements中),调用element及其child的unmount

_inactiveElements._unmountAll();

class _InactiveElements {

  void _unmount(Element element) {
    element.visitChildren((Element child) {
    _unmount(child);
    });
    element.unmount();
  }

void _unmountAll() {
  final List<Element> elements = _elements.toList()..sort(Element._sort);
  _elements.clear();
  try {
    elements.reversed.forEach(_unmount);
  } finally {
    assert(_elements.isEmpty);
    _locked = false;
  }
 }
}

参考:juejin.cn/post/684490…