引言

Flutter开发中三棵树的重要性显而易见,理解其原理有助于咱们开发出性能更优的App,此文次要从源码角度介绍Element树的治理类BuildOwner。

是什么?

BuildOwner是element的治理类,次要负责dirtyElement、inactiveElement、globalkey关联的element的治理。

final _InactiveElements _inactiveElements = _InactiveElements();//存储inactiveElement。final List<Element> _dirtyElements = <Element>[];//存储dirtyElement,就是那些须要重建的element。final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};//存储所有有globalKey的element。

在哪创立的?

BuildOwner是全局惟一的,当然也能够创立一个buildOwner用来治理离屏的widget。其在widgetsBinding的init办法中创立,并在runApp中的attachRootWidget办法中赋值给root element,子element在其mount办法中能够获取到parent的BuildOwner,达到全局应用惟一BuildOwner的成果。

//WidgetsBinding类mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {  @override  void initInstances() {    super.initInstances();    _instance = this;        _buildOwner = BuildOwner();//创立buildOwner    buildOwner!.onBuildScheduled = _handleBuildScheduled;//赋值buildScheduled办法    // ...  }}//Element类的mount办法void mount(Element? parent, Object? newSlot) {    //...    _parent = parent;    _depth = _parent != null ? _parent!.depth + 1 : 1;    if (parent != null) {      //当parent为null时,这个element必定是root element,      //root element的buildOwner是在runApp中调用assignOwner办法赋值的。      _owner = parent.owner;//与parent专用一个buildOwner    }    //...  }

dirtyElements的治理

增加

增加操作次要用的是BuildOwner的scheduleBuildFor办法,当你应用State类时,一个残缺的链条如下:

//StatfuleWidget的State类中调用setState办法void setState(VoidCallback fn) {  final Object? result = fn() as dynamic;  _element!.markNeedsBuild();}//Element里的markNeedsBuild办法void markNeedsBuild() {  //如果不是沉闷状态,间接返回。    if (_lifecycleState != _ElementLifecycle.active)      return;    if (dirty)      return;    _dirty = true;    owner!.scheduleBuildFor(this);  }//BuildOwner里的scheduleBuildFor办法  void scheduleBuildFor(Element element) {    if (element._inDirtyList) {      _dirtyElementsNeedsResorting = true;      return;    }    ...    _dirtyElements.add(element);//退出到dirtyElement列表里    element._inDirtyList = true;//将element的inDirtyList置为true  }

解决

真正解决的中央是在BuilOwner的buildScope办法里。framework在每次调用drawFrame时都会调用此办法从新构建dirtyElement,能够参考下WidgetsBinding的drawFrame办法,在runApp一开始启动时,也会调用此办法实现element tree的mount操作,具体能够参考
RenderObjectToWidgetAdapter的attachToRenderTree办法。

void buildScope(Element context, [ VoidCallback? callback ]) {  if (callback == null && _dirtyElements.isEmpty)    return;  try {    //先执行回调办法    if (callback != null) {      try {        callback();      } finally {      }    }    //采纳深度排序,排序的后果是parent在child的后面    _dirtyElements.sort(Element._sort);    int dirtyCount = _dirtyElements.length;    int index = 0;    while (index < dirtyCount) {      final Element element = _dirtyElements[index];      try {        // 顺次调用element的rebuild办法,调用完rebuild办法后,        // element的dirty属性会被置为false        element.rebuild();      } catch (e, stack) {      }      index += 1;      // 标记 2      if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {        _dirtyElements.sort(Element._sort);        dirtyCount = _dirtyElements.length;        while (index > 0 && _dirtyElements[index - 1].dirty) {          index -= 1;        }      }    }  } finally {    //最初将dirtyElements清空,并将element的inDirtyList属性置为false    for (final Element element in _dirtyElements) {      element._inDirtyList = false;    }    _dirtyElements.clear();  }}

这个办法会先执行办法入参的回调,回调执行结束后对dirty element列表依据element的depth属性进行排序,depth越低越靠前,也就说parent必定在child后面,而后依照这个程序顺次调用element的rebuild办法。为什么要这么排序呢?如果是先执行child的rebuild办法,当执行其parent的rebuild办法时,外部会间接调用updateChild办法导致child从新build,并不会判断child是否是dirty。而当parent执行完rebuild办法后,其child的dirty会被置为false,再次调用child的rebuild办法时,发现child的dirty为false,那么就间接返回。所以这么排序的目标是避免child屡次执行build操作。上面是rebuild的源码。

void rebuild() {  if (_lifecycleState != _ElementLifecycle.active || !_dirty)//如果dirty为false,间接返回,不再执行build操作。    return;  performRebuild();}

当列表中的所有element都执行完rebuild办法后,就会将其清空,并将dirtyElement的inDirtyList置为false,对应于源码的finally中的代码。

看源码中标记2的中央,dirtyCount不应该等于dirtyElements.length吗?为什么会小于呢?上面具体解释下:

执行element.rebuild办法时,外部还会调用updateChild办法用来更新child,在一些场景下updateChild办法会调用inflateWidget来创立新的element(会在element里具体介绍),如果newWidget的key为GlobalKey,这个GlobalKey也有对应的element,并且Widgets.canUpdate()返回true,那么就调用其_activateWithParent办法。

//Element的inflateWidget办法Element inflateWidget(Widget newWidget, Object? newSlot) {  final Key? key = newWidget.key;  if (key is GlobalKey) {    //从新设置此element的地位,配合上面的代码实现了key为GlobalKey的element在tree上的挪动操作。    final Element? newChild = _retakeInactiveElement(key, newWidget);    if (newChild != null) {      //调用element的activeWithParent办法      newChild._activateWithParent(this, newSlot);      final Element? updatedChild = updateChild(newChild, newWidget, newSlot);      return updatedChild!;    }  }  //...}//Element的retakeInactiveElement办法Element? _retakeInactiveElement(GlobalKey key, Widget newWidget) {    //有对应的element    final Element? element = key._currentElement;    if (element == null)      return null;    //如果Widget.canUpdate的后果是false就间接返回null。    if (!Widget.canUpdate(element.widget, newWidget))      return null;    final Element? parent = element._parent;    //脱离和原来parent的关系,将其退出到_inactiveElements列表里    if (parent != null) {      parent.forgetChild(element);      parent.deactivateChild(element);    }    //将上一步退出到inactiveElements列表里的element再从中remove掉    owner!._inactiveElements.remove(element);    return element;  }//Element的activateWithParent办法void _activateWithParent(Element parent, Object? newSlot) {    _parent = parent;    //更新depth,保障其depth肯定比parent要深,最小为parent.depth+1    _updateDepth(_parent!.depth);    //调用element及其child的active办法    _activateRecursively(this);    attachRenderObject(newSlot);  }//Element的updateDepth办法void _updateDepth(int parentDepth) {    final int expectedDepth = parentDepth + 1;    if (_depth < expectedDepth) {      _depth = expectedDepth;      visitChildren((Element child) {        child._updateDepth(expectedDepth);      });    }  }//Element的activateRecursively办法static void _activateRecursively(Element element) {    //调用本人的activate办法    element.activate();    //调用cihldren的activate办法    element.visitChildren(_activateRecursively);  }

最终调用到了element的activate办法:

void activate() {  //...  if (_dirty)    owner!.scheduleBuildFor(this);  //...}

看到没,如果从新捞起来的element是dirty的,那么会再次调用scheduleBuildFor办法,将此element退出到dirtyElement列表外面。这也就是为什么标记2处dirtyCount会小于dirtyElements.length的起因。此时,因为有新element退出到dirtyElement列表里,所以要从新sort。

总结下,buildScope办法次要是对dirtyElements列表中的每一个element执行了rebuild操作,rebuild会调用updateChild办法,当须要从新调用inflateWidget创立新element时,如果child应用了GlobalKey并且GlobalKey对应的element是dirty状态的,那么就会将其退出到dirtyElements列表中,导致dirtyElements数量的变动。

inactiveElements的治理

inactiveElements次要用来治理非沉闷状态的element,特地是能够用来解决key为GlobalKey的element的move操作。其实inactiveElements是一个对象,外部保护了一个Set以及用于debug模式下asset判断的locked属性,当然还有其余办法,类定义如下:

class _InactiveElements {  bool _locked = false;  final Set<Element> _elements = HashSet<Element>();  .....}

增加

在element的deactivateChild办法里实现了inactiveElement的元素增加操作。

//Element类void deactivateChild(Element child) {  child._parent = null;  child.detachRenderObject();  owner!._inactiveElements.add(child); // add 操作}//InactiveElements类的add办法void add(Element element) {    assert(!_locked);    if (element._lifecycleState == _ElementLifecycle.active)      _deactivateRecursively(element);//递归调用element的child的deactivate办法    _elements.add(element);  }//InactiveElements类的_deactivateRecursively办法,调用element的deactive办法static void _deactivateRecursively(Element element) {    element.deactivate();    element.visitChildren(_deactivateRecursively);  }

deactiveChild调用的两个重要机会:

  • updateChild办法里,介绍element时会具体介绍。
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {  if (newWidget == null) {    if (child != null)      deactivateChild(child);    return null;  }  ....}
  • _retakeInactiveElement办法里(inflateWidget办法里调用的),下面介绍过,次要是用于领有GlobaleKey的element在tree上的挪动操作。

清空

其清空操作是在BuildOwner里的finalizeTree办法外面,此办法里会调用element的unmount办法,源码如下。

//BuildOwner类void finalizeTree() {  lockState(_inactiveElements._unmountAll);}//InactiveElement类void _unmountAll() {    _locked = true;//debug模式下的判断属性    final List<Element> elements = _elements.toList()..sort(Element._sort);    _elements.clear();//源list清空    try {      //反转后调用unmount办法,也就是说先调用的child的unmount办法,而后调用的parent的unmount办法。      elements.reversed.forEach(_unmount);    } finally {      assert(_elements.isEmpty);      _locked = false;    }  }//InactiveElement类void _unmount(Element element) {    //先unmount children,再unmount本人    element.visitChildren((Element child) {      _unmount(child);    });    element.unmount();  }

须要留神的是:

  • unmount时会将列表按着深度优先排序,也就说先unmount depth大的,再unmount depth小的。
  • 真正执行unmount操作时,也是先unmount chidlren 而后unmount本人。
  • 每次渲染完一帧后,都会调用finalizeTree办法,具体的办法是WidgetsBinding的drawFrame办法中。

key为GloablKey的Element的治理

次要有两个办法,一个办法用于注册,一个办法用于解注册,在element的mount办法里,判断是否用的GlobalKey,如果是的话调用注册办法,在element的unmount办法里调用解注册办法。

void _registerGlobalKey(GlobalKey key, Element element) {  _globalKeyRegistry[key] = element;}void _unregisterGlobalKey(GlobalKey key, Element element) {  if (_globalKeyRegistry[key] == element)    _globalKeyRegistry.remove(key);}

总结

BuildOwner是全局惟一的,在WidgetsBinding的init办法中创立,外部次要用来治理dirtyElements、inactiveElements以及key为GlobalKey的element。

  • 在BuildOwner的scheduleBuildFor办法里会向dirtyElements里增加dirty element,在buildScope办法里会调用每一个dirty element的rebuild办法,执行rebuild前会对dirty elements进行按深度排序,先执行parent后执行child,目标是为了防止child的build办法被反复执行。在绘制每一帧时(WidgetsBinding的drawFrame办法),会调用buildScope办法。
  • inactiveElements并不是一个列表,而是一个类,外面用set汇合来保留inactive状态的element,还实现了一些此汇合的操作方法,比方add操作等等。
  • 当调用element的updateChild办法时,某些场景下会调用deactiveChild办法,会将element增加到inaciveElements外面,并调用element的deactive办法,使其变为deactive状态;调用updateChild办法时,在某些场景下会调用inflateWidget办法用来创立新element,如果此element的key是GlobalKey,并且此key有对应的element、widget.canUpdate返回true,那么就会将此element与原parent脱离关系(调用的是parent的forgetChild办法),并且将其从inactiveElements中remove掉,实现了在tree上的move操作。
  • 当绘制完一帧时(WidgetsBinding的drawFrame办法),会调用BuildOwner的finalizeTree办法用来清空inactiveElements,并且调用每一个inactive element的unmount办法。
  • globalKey的治理比较简单,用一个map来记录globalKey和element的对应关系,在element的mount办法里实现注册操作,unmount办法里实现解注册办法。

作者:京东物流 沈亮堂

起源:京东云开发者社区