[fluent core class analysis] deeply understand BuildOwner

background

In previous articles [fluent principle] birth and core process of three trees In the article, we first came into contact with buildOwner, that is, in the attachRootWidget(widget rootWidget) method of the WidgetsBinding object, let's review:

  void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);

This article only briefly introduces that BuildOwner can be understood as widgets manager. As for its specific source and function, it is the main content of our analysis in this paper.

BuildOwner source

Next, the above code continues to see how buildOwner is initialized

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  // .......
  @override
  void initInstances() {
    super.initInstances();
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
  }
  // ......
}

We can see that the initialization of buildOwner is in the initInstances method of mixin class WidgetsBinding, and its caller is the runApp() method started by the fluent app we analyzed earlier.

Since_ buildOwner is initialized in WidgetsBinding, then_ Who will use the buildouwner? Who manages and what does it do? Let's continue to look at the attachToRenderTree method called at the top

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      //element is empty, indicating rootElement
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        //Step 1 assign Owner to rootElement
        element!.assignOwner(owner);
      });
      //Step 2 call buildScope
      owner.buildScope(element!, () {
        //Step 3
        element!.mount(null, null);
      });
      SchedulerBinding.instance!.ensureVisualUpdate();
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }
  • Step 1: if element == null, we will create a rootElement, call rootEelement.assignOwner(owner), and give the owner to element,
  • Step 2: call the buildScope method. Let's continue to look at the owner.buildScope method
// Remove a lot of assert methods and retain the core logic
void buildScope(Element context, [ VoidCallback callback ]) {
  if (callback == null && _dirtyElements.isEmpty)
    return;

  try {
    if (callback != null) {
      //Execute callback first
      callback();
    }

    _dirtyElements.sort(Element._sort);
    int dirtyCount = _dirtyElements.length;
    int index = 0;
    while (index < dirtyCount) {
      _dirtyElements[index].rebuild();
      index += 1;
    }
  } finally {
    for (Element element in _dirtyElements) {
      element._inDirtyList = false;
    }
    _dirtyElements.clear();
  }
}
  • If there is a callback, execute the callback first, that is, execute the above element mount(null, null); method.

  • Right_ dirtyElements are sorted according to the depth of the Element and the depth on the element tree

    The advantage of this is to ensure that the parent is rebuilt prior to the child and avoid the child from being rebuilt repeatedly (because the parent will update the child recursively when rebuild ing)

  • Right_ Element exception in dirtyElement calls rebuild (_dirtyElements[index].rebuild())

  • Clean up_ dirtyElement, all_ dirtyElement is set to false.

Let's look at element Mount method:

  @override
  void mount(Element? parent, dynamic newSlot) {
    assert(parent == null);
    // Step 1
    super.mount(parent, newSlot);
    // Step 2
    _rebuild();
  }

Call super.mount () here, that is, finally call the base class element.mount method. We'll continue to see it later

 void mount(Element? parent, dynamic newSlot) {
    // ... a lot of assert
   
    _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
      // Here, parent.owner is directly assigned to child
      _owner = parent.owner;
    final Key? key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
  }

Well, here we come to a phased summary

The BuildOwner instance is created by WidgetsBinding and assigned to the root node RenderObjectToWidgetElement of "Element Tree", and then passed to the child node level by level with the creation of "Element Tree". The entire "Element Tree" shares the same BuildOwner instance.
Generally, we don't need to instantiate BuildOwner manually, unless we need off screen immersion (in this case, we need to build off screen element tree)

Main functions of BuildOwner

To analyze the role of BuildOwner, let's first look at its two member variables

//Used to store collected Inactive Elements
final _InactiveElements _inactiveElements = _InactiveElements();
//DirtyElement for storing mobile phone
final List<Element> _dirtyElements = <Element>[];

Dirty Elements

Literally, Dirty Elements are Dirty Elements. It's easy to understand, that is, the Elements that need to be updated.

Here's the question. How does BuildOwner collect Dirty Elements? We need to follow the State.setState method:

void setState(VoidCallback fn) {
   final dynamic result = fn() as dynamic;
   _element!.markNeedsBuild();
}

Next, let's look at markNeedsBuild()

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

The main function of markNeedsBuild is to set_ The dirty property is true, and then execute the BuildOwner.scheduleBuildFor method

void scheduleBuildFor(Element element) {
    if (element._inDirtyList) {
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
}

The logic of scheduleBuildFor is also very clear. It only does two things:

  • Call onBuildScheduled. This method is actually a callback (that is, buildouwner!. onBuildScheduled = _handlebuildscheduled; this method mentioned at the beginning of the article) to notify the Engine to update at the next frame
  • Will set_ element with dirty of true, added to_ dirtyElements.

When the next frame arrives, WidgetsBinding.drawFrame will be called

void drawFrame() {
    try {
      if (renderViewElement != null)
        buildOwner!.buildScope(renderViewElement!);
      super.drawFrame();
      buildOwner!.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
  }

Inside the WidgetsBinding.drawFrame method, the buildOwner.buildScope method is called. The purpose of buildScope is to rebuild all dirty elements and clean up dirty elements again. Here, a complete closed loop is formed.

Let's continue to look at inactive element

InactiveElement

The so-called inactive element literally means an inactive element, which refers to the element removed from the Element Tree. Let's look at the Element.deactivateChild method

  void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject();
    owner!._inactiveElements.add(child); // this eventually calls child.deactivate()
  }

The function of deactivateChild is obvious, that is, move the given Element to the inactive Element list, and the Element will be collected when it is in the detach_ inactiveElements.

inactive elements will be uniformly cleaned up when manually calling finalizeTree.

summary

The main function of BuildOwner is

  • Track and manage the elements (dirty elements) that need to be rebuilt during UI update
  • When there are dirty elements, notify the engine in time to arrange the rebuild of dirty elements in the next frame to update the UI
  • Manage element s in inactive state

Tags: Flutter

Posted on Wed, 10 Nov 2021 10:12:30 -0500 by Xephon