导言

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类时,一个完好的链条如下:

Flutter三棵树系列之BuildOwner | 京东云技术团队

//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办法里完结解注册办法。

作者:京东物流 沈亮堂

来历:京东云开发者社区

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。