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:

  • ElementinflateWidget(...)methods
  • RenderObjectToWidgetAdapterattachToRenderTree(...)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 currentWidgetThe correspondingRenderObject, if the currentWidgetnotRenderObjectWidgetLook for children
  • GetElementForInheritedWidgetOfExactType () : in maintenanceMap<Type, InheritedElement>Look forInheritedElement, which we are familiar withProviderIn theProvider.of<T>(context)This is how we get our data classes
  • FindAncestorWidgetOfExactType () : by traversalElementparentTo 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 currentElementThe depth of the node in the tree is increasing and must be greater than 0
  • _active: by defaultfalsewhenElementBecomes after being added to the treetrue
  • _inheritedWidgets:parentAll the way down, maintaining everythingInheritedElementBut I wonder why it’s not used herestaticIs it for recycling?
  • Dirty: if it istrueIt means needreBuildIn themarkNeedsBuild()Will be set totrue
  • _inDirtyList: whenElementIs marked asdirtyAfter, will followElementIn theBuildOwnerIn the_dirtyElementsAnd 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 nodeElementRenderObjectToWidgetElementRenderObjectToWidgetAdapterattachToRenderTree(...)Is created in the
  • The other is everything elseElementinflateWidget(...)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

  • ElementIs the real data holder, andStateIt’s also created in its constructor, and its life cycle is less thanStateSlightly longer.
  • Every time I refresh,WidgetWill be recreated while inElementAfter the creation process is complete,ElementOnly in thecanUpdate(...)returnfalseIs created again, otherwise it is usually calledupdate(...)Update.StatelessElementSame thing.
  • GlobalKeyIn addition to being able to crossWidgetIn addition to passing data, you can also pairElementTo 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