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