Read the Flutter collection from source code
The opening
Element is created by the createElement() method of the Widget. This method can be used to create a flutter.
So, who called the createElement() method? A lookup shows that this method is called in only two places. Respectively is:
Element
的inflateWidget(...)
methodsRenderObjectToWidgetAdapter
的attachToRenderTree(...)
methods
The first method is inside an Element and is not static. Obviously an Element cannot create itself by calling its own methods out of thin air, so it is used to generate other Element objects. The first Element is created in the second method.
Before we get to the Element object, let’s take a quick look at how the first Element was created
RenderObjectToWidgetElement
We know that the entry to a flutter is in the runApp(Widget) method. We can look at this:
runApp(app)
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() .. scheduleAttachRootWidget(app) .. scheduleWarmUpFrame(); }Copy the code
This is where all the initialization takes place, and the root Widget we pass in via runApp(app) is called from the second scheduleAttachRootWidget(app) method
scheduleAttachRootWidget(app)
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding { ... @protected void scheduleAttachRootWidget(Widget rootWidget) { Timer.run(() { attachRootWidget(rootWidget); }); }... void attachRootWidget(Widget rootWidget) { _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription:'[root]', child: rootWidget, ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>); }... }Copy the code
As you can see, the final by creating RenderObjectToWidgetAdapter object and call its attachToRenderTree (…). Method to create the RenderObjectToWidgetElement, we simply look at
attachToRenderTree(…)
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget { ... RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) { ... element = createElement(); .returnelement; }... }Copy the code
The createElement() here is the second call we mentioned earlier. All subsequent elements call inflateWidget(…) via their parent Element. Method is created
Next, we begin the formal introduction of the Element object
Element
The inheritance relationship of the Element object corresponding to the commonly used StatefulWidget and StatelessWidget is as follows:
xxxElement -> ComponentElement -> Element
Many other Element objects also inherit directly or indirectly from ComponentElement, but RenderObjectWidget’s Element inheritance is as follows:
RenderObjectElement -> Element
Next, we start with the Element constructor
Element(widget)
Element(Widget widget) : assert(widget ! = null), _widget = widget;Copy the code
In the constructor, the Widget object corresponding to Element is assigned. Let’s take a look at the structure of Element
abstract class Element extends DiagnosticableTree implements BuildContext {
...
}
Copy the code
DiagnosticableTree was introduced in the first article and will not be covered here. You can see that there is a familiar object called BuildContext, which can often be built in Widget or State (…). Method, let’s look at it briefly
BuildContext
abstract class BuildContext { Widget get widget; BuildOwner get owner; . RenderObject findRenderObject(); . InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>(); T findAncestorWidgetOfExactType<T extends Widget>(); . void visitChildElements(ElementVisitor visitor); . }Copy the code
Some of the more typical methods are listed above. BuildContext is an abstract class because dart doesn’t have an Interface, and BuildContext here essentially just provides calling methods, so you can think of it as an Interface in Java
The BuildOwner object is initialized only once in initInstances() of the WidgetsBinding, which means that there are only unique instances globally. It is the widget Framework’s management class, which does many things, such as managing the widget lifecycle in Element
Some other methods:
- FindRenderObject (): Returns the current
Widget
The correspondingRenderObject
, if the currentWidget
notRenderObjectWidget
Look for children - GetElementForInheritedWidgetOfExactType () : in maintenance
Map<Type, InheritedElement>
Look forInheritedElement
, which we are familiar withProvider
In theProvider.of<T>(context)
This is how we get our data classes - FindAncestorWidgetOfExactType () : by traversal
Element
的 parentTo find the Widget of the specified type - VisitChildElements (): Used to traverse child elements
BuildContext covers that, but let’s look at some of the member variables in Element
Element member variables
abstract class Element extends DiagnosticableTree implements BuildContext { ... Element _parent; . dynamic _slot; . int _depth; . Widget _widget; . BuildOwner _owner; . bool _active =false; . _ElementLifecycle _debugLifecycleState = _ElementLifecycle.initial; . Map<Type, InheritedElement> _inheritedWidgets; . bool _dirty =true; . bool _inDirtyList =false;
}
Copy the code
The main member variables are listed above
Element holds the parent object by default, and slot is used to indicate its position in the parent child list. If the parent has only one child, slot should be null. Let’s look at the remaining variables
- The depth, the current
Element
The depth of the node in the tree is increasing and must be greater than 0 - _active: by defaultfalsewhen
Element
Becomes after being added to the treetrue - _inheritedWidgets:
parent
All the way down, maintaining everythingInheritedElement
But I wonder why it’s not used herestaticIs it for recycling? - Dirty: if it istrueIt means needreBuildIn the
markNeedsBuild()
Will be set totrue - _inDirtyList: when
Element
Is marked asdirty
After, will followElement
In theBuildOwner
In the_dirtyElements
And set totrueAnd wait forreBuild
There is also a life cycle object _debugLifecycleState
enum _ElementLifecycle {
initial,
active,
inactive,
defunct,
}
Copy the code
It is hidden from the outside, and the life cycle is similar to State’s, except that active and inactive can be switched back and forth, where Element reuse is involved, as described later
Then we’ll take a quick look at some of Element’s main methods
The method of Element
RenderObject get renderObject { ... }
void visitChildren(ElementVisitor visitor) { }
@override
void visitChildElements(ElementVisitor visitor) {
...
visitChildren(visitor);
}
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) { ... }
@mustCallSuper
void mount(Element parent, dynamic newSlot) { ... }
@mustCallSuper
void update(covariant Widget newWidget) {
...
_widget = newWidget;
}
Element _retakeInactiveElement(GlobalKey key, Widget newWidget) { ... }
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) { ... }
@protected
void deactivateChild(Element child) { ... }
@mustCallSuper
void activate() {... } @mustCallSuper voiddeactivate() {... } @mustCallSuper voidunmount() {... } @mustCallSuper voiddidChangeDependencies() {... markNeedsBuild(); } voidmarkNeedsBuild(){ ... }
void rebuild() {... } @protected void performRebuild();Copy the code
Of the main methods above, the most central are mount(), unmount() and inflateWidget(…). , updateChild (…). These, rebuild ()
We are not going to go into the functions of these methods directly, because reading them out of context might not be a good experience. We will walk through the process of creating Element and explain the functions of each method in the process.
But let’s take a look at one of the methods, renderObject, and see how Element interacts with renderObject
RenderObject get renderObject {
RenderObject result;
void visit(Element element) {
assert(result == null); // this verifies that there's only one child if (element is RenderObjectElement) result = element.renderObject; else element.visitChildren(visit); } visit(this); return result; }Copy the code
If the current element is RenderObjectElement, return the renderObject it holds, otherwise iterate over children to get the nearest renderObject
RenderObject is only one-to-one with RenderObjectElement, and one-to-many with other elements
However, there is a point to ridicule here is that the reading experience is not particularly good when the method is directly defined in the method, and there will be a lot of such situations later
Next, we are ready to enter the Element creation process entry
Element creates a process entry
Since you want to go to create the process, it is natural to find a starting point. In the previous article, we learned that the method to create an Element with createElement() is called in only two places:
- One is as the root node
Element
的RenderObjectToWidgetElement
在RenderObjectToWidgetAdapter
的attachToRenderTree(...)
Is created in the - The other is everything else
Element
在inflateWidget(...)
Is created in the
Let’s take the second method as an entry point into the Element creation process, and briefly take a look at the second method
abstract class Element extends DiagnosticableTree implements BuildContext { ... @protected Element inflateWidget(Widget newWidget, dynamic newSlot) { ... final Element newChild = newWidget.createElement(); . newChild.mount(this, newSlot); assert(newChild._debugLifecycleState == _ElementLifecycle.active);returnnewChild; }... }Copy the code
As you can see, the last call to Element’s mount(…) Method, so this method is an entry point for each Element.
As we mentioned in the last article, different widgets have different implementations for elements, and the two most widely used implementations are ComponentElement and RenderObjectElement.
We can start with the first one
The creation process for ComponentElement
Enter its mount(…) methods
mount(…)
void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); . _firstBuild(); . }Copy the code
Call the parent, Element’s mount(…)
Element -> mount(…)
@mustCallSuper void mount(Element parent, dynamic newSlot) { ... _parent = parent; _slot = newSlot; _depth = _parent ! = null ? _parent.depth + 1 : 1; _active =true;
if(parent ! = null) // Only assign ownershipif the parent is non-null
_owner = parent.owner;
final Key key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();
assert(() {
_debugLifecycleState = _ElementLifecycle.active;
return true; } ()); }Copy the code
As you can see, at mount(…) A number of column initializations are performed in.
If the key passed in is GlobalKey, the current Element is stored in the Map
object maintained in GlobalKey.
Finally, the lifecycle is set to _ElementLifecycle. Active
Next, look at _firstBuild() for ComponentElement
_firstBuild()
void _firstBuild() {
rebuild();
}
Copy the code
Rebuild () is called, which is implemented in Element
Element -> rebuild()
void rebuild() {... performRebuild(); . } @protected void performRebuild();Copy the code
And then finally we call the performRebuild() method, which in Element does nothing but leave it to subclasses, and then we go back to ComponentElement, okay
performRebuild()
@override
void performRebuild() {
if(! kReleaseMode && debugProfileBuildsEnabled) Timeline.startSync('${widget.runtimeType}', arguments: timelineWhitelistArguments); . Widget built; try { built = build(); . } catch (e, stack) { built = ErrorWidget.builder(...) ; } finally { ... _dirty =false;
...
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(...);
_child = updateChild(null, built, slot);
}
if(! kReleaseMode && debugProfileBuildsEnabled) Timeline.finishSync(); }Copy the code
As you can see, both the beginning and the end of the debug mode is determined, and the use of the Timeline object, which is actually used for DevTool performance detection as we described earlier
As you can see, the Widget is created by calling the familiar build() method. If an exception occurs, an ErrorWidget is created in the catch statement.
The next step will be updateChild(…). To assign the current Element’s _child value
While updateChild (…). Located in the Element
Element -> updateChild(…)
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
if (newWidget == null) {
if(child ! = null) deactivateChild(child);return null;
}
if(child ! = null) {if (child.widget == newWidget) {
if(child.slot ! = newSlot) updateSlotForChild(child, newSlot);return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if(child.slot ! = newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); assert(child.widget == newWidget); assert(() { child.owner._debugElementWasRebuilt(child);return true; } ());return child;
}
deactivateChild(child);
assert(child._parent == null);
}
return inflateWidget(newWidget, newSlot);
}
Copy the code
updateChild(…) This method takes three arguments: Element Child, Widget newWidget, and Dynamic newSlot. The function of this method varies depending on the parameters passed in.
NewWidget null | NewWidget not null | |
---|---|---|
The child is null | (1). Returns null | ②. Return to new Elment |
The child is not null | Remove the passed child and return NULL | (4). According to canUpdate (…). Decide to return the updated child or new Element |
The deactivateChild(Child) removes the passed Element
The value created in performRebuild() is null for child and not null for newWidget. Go directly to the inflateWidget(…) methods
Element -> inflateWidget(…)
Back to where we started
@protected Element inflateWidget(Widget newWidget, dynamic newSlot) { assert(newWidget ! = null); final Key key = newWidget.key;if (key is GlobalKey) {
final Element newChild = _retakeInactiveElement(key, newWidget);
if(newChild ! = null) { assert(newChild._parent == null); assert(() { _debugCheckForCycles(newChild);return true; } ()); newChild._activateWithParent(this, newSlot); final Element updatedChild = updateChild(newChild, newWidget, newSlot); assert(newChild == updatedChild);returnupdatedChild; }} // Create a new Element to start the next loopreturn newChild;
}
Copy the code
The logic for the latter part of the method has been explained before, and here you can take a look at the previous section on GlobalKey. If the key obtained from the widget is GlobalKey and the widget is already mounted on Element (…) If GlobalKey is registered in, it is retrieved and reused here. This part is in _retakeInactiveElement(…) Finished, can be briefly looked at:
Element _retakeInactiveElement(GlobalKey key, Widget newWidget) {
final Element element = key._currentElement;
if (element == null)
return null;
if(! Widget.canUpdate(element.widget, newWidget))returnnull; .return element;
}
Copy the code
When an Element does not exist or cannot be updated, it is not reused and null is returned
If the result is not null, call updateChild(…) again. Method, where none of the arguments passed are NULL, so the fourth case is entered:
Element updateChild(Element child, Widget newWidget, dynamic newSlot) { ... // It must be heretruetheif (Widget.canUpdate(child.widget, newWidget)) {
if(child.slot ! = newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); assert(child.widget == newWidget); assert(() { child.owner._debugElementWasRebuilt(child);return true; } ());returnchild; }...return inflateWidget(newWidget, newSlot);
}
Copy the code
Notice that the child’s update(newWidget) method is called. This method updates the Widget currently held by Element, leaving the rest of the logic to subclasses
So that’s pretty much the ComponentElement creation process
Next, we can look at the two subclasses of ComponentElement, StatelessElement and StatefulElement
StatelessElement
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true; rebuild(); }}Copy the code
StatelessElement very simple, overridden update(…) It just calls rebuild() and feels like there’s nothing to say.
Next, look at the StatefulElement
StatefulElement
class StatefulElement extends ComponentElement { StatefulElement(StatefulWidget widget) : _state = widget.createState(), super(widget) { ... _state._element = this; . _state._widget = widget; . } @override void_firstBuild() {... } @override void update(StatefulWidget newWidget) { ... } @override voidunmount() {... }}Copy the code
Some of the main methods for StatefulElement are shown here, and you can see that Element and Widget objects are put into State in the constructor
Now what do the other three methods do
_firstBuild()
@override
void _firstBuild() {... final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic; . _state._debugLifecycleState = _StateLifecycle.initialized; . _state.didChangeDependencies(); . _state._debugLifecycleState = _StateLifecycle.ready; . super._firstBuild(); }Copy the code
ComponentElement is mounted (…) Call _firstBuild(), which overrides the method and does some initialization init, and calls the initState() method of State
update(…)
@override void update(StatefulWidget newWidget) { super.update(newWidget); . final StatefulWidget oldWidget = _state._widget; . _dirty =true; _state._widget = widget as StatefulWidget; . final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic; . rebuild(); }Copy the code
The main thing here is to call State’s didUpdateWidget(…). Method, other content is similar to StatelessElement
The setState() method used in StatefulWidget will walk through the steps of StatelessElement
StatefulElement refresh process
We know that State’s setState() method calls Element’s markNeedsBuild().
Element -> markNeedsBuild()
BuildOwner get owner => _owner;
void markNeedsBuild() {...if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
Copy the code
Let’s go to BuildOwner and see what scheduleBuildFor(Element) does
BuildOwner -> scheduleBuildFor(element)
final List<Element> _dirtyElements = <Element>[];
void scheduleBuildFor(Element element) {
...
if (element._inDirtyList) {
...
_dirtyElementsNeedsResorting = true;
return; }...if(! _scheduledFlushDirtyElements && onBuildScheduled ! = null) { _scheduledFlushDirtyElements =true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true; . }Copy the code
As you can see, scheduleBuildFor(Element) adds the element that needs to be refreshed to _dirtyElements and marks the element’s _inDirtyList to true
But nothing else is done, so how does the refresh work? This comes back to a method called onBuildScheduled() earlier
BuildOwner -> onBuildScheduled()
This method was set when BuildOwner was created
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
...
@override
void initInstances() {... _buildOwner = BuildOwner(); buildOwner.onBuildScheduled = _handleBuildScheduled; . }... }Copy the code
Take a look at _handleBuildScheduled
WidgetsBinding -> _handleBuildScheduled
void _handleBuildScheduled() {... ensureVisualUpdate(); }Copy the code
EnsureVisualUpdate () is defined in the SchedulerBinding
mixin SchedulerBinding on BindingBase, ServicesBinding {
...
void ensureVisualUpdate() {
switch (schedulerPhase) {
...
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return; . }}... }Copy the code
The scheduleFrame() method follows
SchedulerBinding -> scheduleFrame()
@protected
void ensureFrameCallbacksRegistered() { window.onBeginFrame ?? = _handleBeginFrame; window.onDrawFrame ?? = _handleDrawFrame; } voidscheduleFrame() {... ensureFrameCallbacksRegistered(); window.scheduleFrame(); _hasScheduledFrame =true;
}
Copy the code
Window.scheduleframe () is called
Window -> scheduleFrame()
class Window {
...
/// Requests that, at the next appropriate opportunity, the [onBeginFrame]
/// and [onDrawFrame] callbacks be invoked.
///
/// See also:
///
/// * [SchedulerBinding], the Flutter framework class which manages the
/// scheduling of frames.
void scheduleFrame() native 'Window_scheduleFrame';
}
Copy the code
At this point, it’s time to do something about Engine. After various operations, the dart layer’s _drawFrame() method is called back
sky_engine -> ui -> hooks.dart -> _drawFrame()
If you are interested in this part of the engine operation, take a look at this article about the Flutter rendering mechanism – UI threads. Since they are all in C++, they are beyond my reach, so I will go directly to the great god here
The _drawFrame() method reads as follows:
void _drawFrame() {
_invoke(window.onDrawFrame, window._onDrawFrameZone);
}
Copy the code
Finally, the _handleDrawFrame method registered with onDrawFrame in SchedulerBinding is called, which calls handleDrawFrame().
SchedulerBinding -> handleDrawFrame()
void handleDrawFrame() {... try { // PERSISTENT FRAME CALLBACKS _schedulerPhase = SchedulerPhase.persistentCallbacks;for (FrameCallback callback in_persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); . }... }Copy the code
Here will traverse _persistentCallbacks to perform the corresponding methods, it is through RendererBinding addPersistentFrameCallback to add, and then every frame callback traversal to perform again
Here will execute method, is in RendererBinding initInstances _handlePersistentFrameCallback () to add
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_mouseTracker.schedulePostFrameCheck();
}
Copy the code
Finally, a WidgetBinding drawFrame() is called
WidgetBinding -> drawFrame()
@override
void drawFrame() {... try {if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
}
...
}
Copy the code
RenderViewElement is the root Element created in the runApp() process we looked at earlier
Finally the call goes back to the BuildOwner object and calls its buildScope(…). methods
BuildOwner -> buildScope(…)
buildScope(…) Used to locally update Element Tree
void buildScope(Element context, [ VoidCallback callback ]) {
...
_debugBuilding = true; . _scheduledFlushDirtyElements =true; . _dirtyElements.sort(Element._sort); _dirtyElementsNeedsResorting =false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while(index < dirtyCount) { ... _dirtyElements[index].rebuild(); . index += 1; . // If _dirtyElements changes during a local update // for example, a new object may be inserted into _dirtyElements, it is processed here}...for (Element element in _dirtyElements) {
assert(element._inDirtyList);
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false; _dirtyElementsNeedsResorting = null; . _debugBuilding =false; . }Copy the code
So we can see from the above that the principle of local update is to store the objects that need to be updated in _dirtyElements, and then when it needs to be updated, iterate over them and reBuild().
Before traversal, sort(…) is called. Method to sort, judge the condition is the depth of the elements, in order from smallest to largest, that is, for the elements updated from top to bottom.
After the update, _dirtyElements is cleared and the flag bits are reset
Now that you’ve seen the refresh process for StatefulElement, let’s look at its destruction process and, by the way, the destruction process for Element
StatefulElement destruction process
DrawFrame () buildScope(…) The buildowner.finalizetree () method that follows the buildowner.finalizetree () method is used to destroy elements
Instead of using it as an entry point, remember we used Element’s updateChild(…) The deactivateChild(Child) method is used to destroy elements in two places.
We’ll use this as the entrance
Element -> deactivateChild(child)
@protected void deactivateChild(Element child) { ... child._parent = null; child.detachRenderObject(); owner._inactiveElements.add(child); . }Copy the code
Here the reference to the parent child is cleared, and the RenderObject is destroyed with a call to detachRenderObject(), the details of which will be covered in the next section.
The main thing is to add the currently destroyed child to BuildOwner’s _inactiveElements
Let’s start with _inactiveElements
_InactiveElements
class _InactiveElements {
bool _locked = false; final Set<Element> _elements = HashSet<Element>(); static void _deactivateRecursively(Element element) { ... element.deactivate(); . element.visitChildren(_deactivateRecursively); . } void add(Element element) { ...if(element._active) _deactivateRecursively(element); _elements.add(element); }}Copy the code
As you can see, _InactiveElements uses Set to hold all objects that need to be destroyed
In the add(Element) method, if the current object to be destroyed is still active, the add(Element) method recursively iterates through its children, calling deactivate() on each child element to set the destruction state. Take a look at the deactivate() method
///Element
@mustCallSuper
void deactivate() {... _inheritedWidgets = null; _active =false; . _debugLifecycleState = _ElementLifecycle.inactive; . }Copy the code
The life cycle of the Element to be destroyed becomes Inactive
After we have collected the Element to destroy, finalizeTree() is called when the WidgetsBinding drawFrame() is triggered to do the actual destruction
BuildOwner -> finalizeTree()
void finalizeTree() {... lockState(() { _inactiveElements._unmountAll(); // this unregisters the GlobalKeys }); . }Copy the code
The destruction is done by calling _unmountAll() of _InactiveElements
_InactiveElements -> _unmountAll()
void _unmountAll() {
_locked = true; final List<Element> elements = _elements.toList().. sort(Element._sort); _elements.clear(); try { elements.reversed.forEach(_unmount); } finally { assert(_elements.isEmpty); _locked =false; }}Copy the code
Here the destruction is also top-down, calling the _unmount(Element) method
void _unmount(Element element) { ... element.visitChildren((Element child) { assert(child._parent == element); _unmount(child); }); element.unmount(); . }Copy the code
I have to say that DART passes methods as arguments, and in some cases omits input method arguments, which is nice, but probably sacrifices a bit of readability
_unmount(element) also iterates over children and calls child Element’s unmount() for destruction
There is also a remove method in _InactiveElements, which we looked at before we introduced unmount()
_InactiveElements -> remove()
void remove(Element element) { ... _elements.remove(element); . }Copy the code
In _unmountAll(), all elements in the Set were cleared by clear(). Why is that? Previously we looked at Element’s inflateWidget(…) As mentioned, GlobalKey can be used to reuse Element objects without having to recreate them. Let’s look at it again
Element -> _retakeInactiveElement(…)
@protected
Element inflateWidget(Widget newWidget, dynamic 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); assert(newChild == updatedChild);returnupdatedChild; }}... } Element _retakeInactiveElement(GlobalKey key, Widget newWidget) { ... final Element element = key._currentElement;if (element == null)
return null;
if(! Widget.canUpdate(element.widget, newWidget))returnnull; . final Element parent = element._parent;if (parent != null) {
...
parent.forgetChild(element);
parent.deactivateChild(element);
}
...
owner._inactiveElements.remove(element);
return element;
}
Copy the code
You can see that in _retakeInactiveElement(…) At the end, the overused Element is removed from _inactiveElements. Once the Element is retrieved, _activateWithParent(…) is called. Method to activate Element again
Element -> _activateWithParent(…)
void _activateWithParent(Element parent, dynamic newSlot) { ... _parent = parent; . _updateDepth(_parent.depth); _activateRecursively(this); attachRenderObject(newSlot); . } static void _activateRecursively(Element element) { ... element.activate(); . element.visitChildren(_activateRecursively); }Copy the code
Here, Element and its children are activated by recursive calls. Take a look at the active() method
Element -> activate()
@mustCallSuper
void activate() {
...
final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
_active = true; . _dependencies? .clear(); _hadUnsatisfiedDependencies =false; _updateInheritance(); . _debugLifecycleState = _ElementLifecycle.active; .if (_dirty)
owner.scheduleBuildFor(this);
if (hadDependencies)
didChangeDependencies();
}
///StatefulElement
@override
void activate() { super.activate(); . markNeedsBuild(); }Copy the code
In Activate (), Element’s life cycle changes to active again. This is what we said earlier, and it is possible to switch between active and inactive in any of the four life cycles of an Element.
So under what circumstances will this reuse be triggered? It’s simple: it works when the same type of Widget at different depths uses the same GlobalKey, such as the following:
Center(
child: changed
? Container(
child: Text('aaaa', key: globalKey),
padding: EdgeInsets.all(20))
: Text('bbb', key: globalKey),
)
Copy the code
When changed is changed through setState, reuse can be triggered
With that said, we continue with the unmount() method
Element -> unmount()
@mustCallSuper
void unmount() {... final Key key = widget.key;if (key is GlobalKey) {
key._unregister(this);
}
assert(() {
_debugLifecycleState = _ElementLifecycle.defunct;
return true; } ()); }Copy the code
As you can see in unmount(), if the current Element registers a Globalkey it is cleared, and the lifecycle is set to defunct, which is overridden in StatefulElement
StatefulElement -> unmount()
@override
void unmount() { super.unmount(); _state.dispose(); . _state._element = null; _state = null; }Copy the code
The Dispose () method of State is called, and the Element references held in State are then cleaned up, and State is finally empty
At this point, the StatefulElement destruction process is over, and this article comes to a close
Of course, RenderObjectElement has not been analyzed yet, because all RenderObject related content will be covered in Chapter 3, so I will skip it here
conclusion
Element
Is the real data holder, andState
It’s also created in its constructor, and its life cycle is less thanState
Slightly longer.- Every time I refresh,
Widget
Will be recreated while inElement
After the creation process is complete,Element
Only in thecanUpdate(...)
returnfalseIs created again, otherwise it is usually calledupdate(...)
Update.StatelessElement
Same thing. GlobalKey
In addition to being able to crossWidget
In addition to passing data, you can also pairElement
To reuse
For the rest of the summary, just look at the picture
Note: The above source code analysis is based on the new version of Flutter Stable 1.13.6 which may have code inconsistencies, such as updateChild(…). But the logic is the same and it’s safe to eat