從原始碼看flutter(二):Element篇

安卓小哥發表於2020-04-17

開篇

上一篇 從原始碼看flutter(一):Widget篇 我們瞭解到了關於 Widget 的相關知識, 知道了 Element 都是通過 WidgetcreateElement() 方法來建立的。

那麼,是誰呼叫了 createElement() 方法?通過查詢, 發現只有兩處呼叫了這個方法。分別是:

  • ElementinflateWidget(...) 方法
  • RenderObjectToWidgetAdapterattachToRenderTree(...) 方法

第一個方法在 Element 內部,並且不是 static 方法,顯然 Element 不可能憑空呼叫自己的方法建立自己, 所以它是用來生成其他 Element 物件的。而第一個 Element 就是在第二個方法被建立出來的。

在我們介紹 Element 物件之前,我們可以先簡單瞭解一下第一個 Element 的建立過程

RenderObjectToWidgetElement

我們知道,flutter的入口在 runApp(widget) 方法裡,我們可以看一下:

runApp(app)

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製程式碼

在這裡進行了所有的初始化操作,而我們通過 runApp(app) 傳入的根 Widget 被第二個方法 scheduleAttachRootWidget(app) 所呼叫,從這個方法進入

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>);
  }
  ...
}
複製程式碼

可以看到,最終通過建立 RenderObjectToWidgetAdapter 物件,並呼叫其 attachToRenderTree(...) 方法建立了 RenderObjectToWidgetElement,我們簡單瞭解一下

attachToRenderTree(...)

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  ...
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
    ...
        element = createElement();
    ...
    return element;
  }
  ...
}
複製程式碼

這裡的 createElement() 也就是我們之前提到過的,第二個呼叫的地方。之後所有的 Element 都是通過其 父Element 呼叫 inflateWidget(...) 方法所建立了

接下來,我們開始正式介紹 Element 物件

Element

我們常用的 StatefulWidgetStatelessWidget 所對應的 Element 物件,繼承關係如下:

xxxElement -> ComponentElement -> Element

許多其他的 Element 物件也都是直接或者間接繼承於 ComponentElement ,不過 RenderObjectWidgetElement 繼承關係如下:

RenderObjectElement -> Element

下面,我們從 Element 的建構函式開始

Element(widget)

  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;
複製程式碼

在建構函式裡面,進行了 Element 所對應的 Widget 物件的賦值。接下來看一看 Element 的結構

abstract class Element extends DiagnosticableTree implements BuildContext {
    ...
}
複製程式碼

DiagnosticableTree 在第一篇已經介紹過,這裡不再贅述。可以看到這裡有個我們熟悉的物件 BuildContext , 經常可以在 WidgetStatebuild(...) 方法中看到它,我們先來簡單的瞭解一下它

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);
  ...
}
複製程式碼

上面列出了比較典型的一些方法。BuildContext 是一個抽象類,因為 dart 中沒有 Interface ,而這裡的 BuildContext 本質上只提供各種呼叫方法,所以完全可以把它當成 java 中的介面

其中 BuildOwner 物件只在 WidgetsBindinginitInstances() 中初始化過一次,也就是說全域性只有唯一的例項。他是 widget framework 的管理類,實際上的的作用有很多,比如在 Element 中,就負責管理它的生命週期

其他的一些方法:

  • findRenderObject(): 用於返回當前 Widget 對應的 RenderObject ,如果當前 Widget 不是 RenderObjectWidget 則從children中尋找
  • getElementForInheritedWidgetOfExactType(): 在維護的 Map<Type, InheritedElement> 中查詢 InheritedElement,在我們熟知的 Provider 中的 Provider.of<T>(context) 就是通過這種方法獲取資料類的
  • findAncestorWidgetOfExactType(): 通過遍歷 Elementparent 來找到指定型別Widget
  • visitChildElements(): 用於遍歷 子Element

BuildContext 大致就介紹這些,接下來我們來看 Element 中的一些成員變數

Element的成員變數

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;
}
複製程式碼

上面列舉出了主要的一些成員變數

Element 中預設持有 parent 物件,而 slot 用於表示它在 parentchild列表 的位置,如果 parent 只有一個 childslot 應該為 null ,再來看看剩下的一些變數

  • depth : 當前 Element 節點在樹中的深度,深度是遞增的,且必須大於0
  • _active: 預設為 false, 當 Element 被新增到樹後,變為 true
  • _inheritedWidgets: 從 parent 一直傳遞下來,維護了所有 InheritedElement ,不過很好奇為什麼這裡不直接用 static 修飾,是為了方便垃圾回收嗎?
  • dirty: 如果為 true 就表示需要 reBuild 了, 在 markNeedsBuild() 中會被設為 true
  • _inDirtyList: 當 Element 被標記為 dirty 後,隨之會將 Element 放入 BuildOwner 中的 _dirtyElements ,並設定為 true ,等待 reBuild

還有一個 生命週期物件 _debugLifecycleState

enum _ElementLifecycle {
  initial,
  active,
  inactive,
  defunct,
}
複製程式碼

它對外部是隱藏的,這個生命週期和 State 的有點類似,不過其中的 activeinactive 是可以來回切換的,這裡就涉及到 Element 的複用了,後面會說

然後是 Element 的一些主要方法,我們簡單的看一下

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
  void deactivate() { ... }
  
  @mustCallSuper
  void unmount() { ... }
  
  @mustCallSuper
  void didChangeDependencies() {
    ...
    markNeedsBuild();
  }
  
  void markNeedsBuild(){ ... }
  
  void rebuild() { ... }
  
  @protected
  void performRebuild();
複製程式碼

上面的主要方法中,最核心的是 mount()unmount()inflateWidget(...)updateChild(...)rebuild() 這些

這裡我們不去直接介紹這些方法的作用,因為脫離上下文單獨看的話可能閱讀體驗不會太好,後面會走一遍 Element 的建立流程,在這個過程中去闡述各個方法的作用。

不過我們可以先看其中一個方法 renderObject 瞭解一下 ElementRenderObject 的對應關係

  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;
  }
複製程式碼

解釋一下就是,如果當前 elementRenderObjectElement 的話,直接返回它持有的 renderObject ,否則遍歷 children 去獲取最近的 renderObject 物件

從這裡也可以知道 RenderObject 只與 RenderObjectElement 是一一對應的,與其他 Element 則是一對多的關係,也驗證了我們上一篇中的判定

不過這裡有一點需要吐槽的是,在方法裡面直接定義方法,閱讀體驗不是特別好,而後面這樣的情況還會很多

接下來,我們準備進入 Element 的建立流程入口

Element 建立流程入口

既然要走建立流程,自然是要找個起點的。在上一篇中,我們知道通過 createElement() 建立 Element 的方法只在兩個地方被呼叫:

  • 其一是作為根節點 ElementRenderObjectToWidgetElementRenderObjectToWidgetAdapterattachToRenderTree(...) 中被建立
  • 另一個是其他所有 ElementinflateWidget(...) 方法中被建立

我們以第二個方法為入口,進入 Element 的建立流程,先簡單的看一下第二個方法

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);
    return newChild;
  }
  ...
}
複製程式碼

可以看到,上面最後呼叫了 Elementmount(...) 方法,所以這個方法算是各個 Element 的入口了。

上一篇我們提到過,不同 Widget 對應 Element 的實現都不一樣,其中最廣泛的兩種實現分別是 ComponentElementRenderObjectElement

我們可以從第一個開始瞭解

ComponentElement 的建立流程

進入它的 mount(...) 方法

mount(...)

  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    ...
    _firstBuild();
    ...
  }
複製程式碼

呼叫了父類,也就是 Elementmount(...)

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 ownership if 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;
    }());
  }
複製程式碼

可以看到,在 mount(...) 中,進行了一些列的初始化操作。

其中如果傳入的 keyGlobalKey ,會將當前 Element 存入 GlobalKey 中維護的 Map<GlobalKey, Element> 物件。

最後會將生命週期設定為 _ElementLifecycle.active

接下來,可以看一下 ComponentElement_firstBuild()

_firstBuild()

  void _firstBuild() {
    rebuild();
  }
複製程式碼

呼叫了 rebuild() ,它是在 Element 中實現的

Element -> rebuild()

  void rebuild() {
    ...
    performRebuild();
    ...
  }
  
  @protected
  void performRebuild();
複製程式碼

最後呼叫到 performRebuild() 方法,Element 中這個方法什麼都沒做,就是交由子類去實現的,接下來回到 ComponentElement

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();
  }
複製程式碼

可以看到,開頭和結尾都做了debug模式的判斷,並使用了 Timeline 這個物件,它的作用其實就是我們之前介紹過的,用於在 DevTool 中檢測效能表現

可以看到,上面通過呼叫我們最熟悉的 build() 方法來建立 Widget ,如果發生異常的話,就會在 catch 語句中建立一個 ErrorWidget, 也就是我們常常遇見的那個紅色的錯誤介面啦!

後面會通過 updateChild(...) 來給當前 Element_child 賦值

updateChild(...) 位於 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);
  }
複製程式碼

updateChild(...) 是非常重要的一個方法,它接受三個引數,分別是 Element childWidget newWidget 以及 dynamic newSlot,傳入的引數不同,這個方法的作用也不一樣,主要分為下面幾種情況:

newWidget為null newWidget不為null
child為null ①.返回null ②.返回新的Elment
child不為null ③.移除傳入child,返回null ④.根據 canUpdate(...) 決定返回更新後的child或者新Element

其中的 deactivateChild(child) 就是將傳入 Element 移除掉

而我們在 performRebuild() 中創來的值是: child為null 並且 newWidget不為null,屬於第二種情況。直接進入 inflateWidget(...) 方法

Element -> inflateWidget(...)

又回到最初的起點

  @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);
        return updatedChild;
      }
    }
    //建立新的Element,開始下一輪迴圈
    return newChild;
  }
複製程式碼

方法後部分的邏輯之前已經說過,這裡可以看一下前部分關於 GlobalKey 的部分。如果獲取到 widgetkeyGlobalKey, 並且之前 Widget 已經在 Elementmount(...) 中註冊到了 GlobalKey的話,就會在這裡取出並且複用。這部分是在 _retakeInactiveElement(...) 完成的,可以簡單看一下:

  Element _retakeInactiveElement(GlobalKey key, Widget newWidget) {
    final Element element = key._currentElement;
    if (element == null)
      return null;
    if (!Widget.canUpdate(element.widget, newWidget))
      return null;
    ...
    return element;
  }
複製程式碼

Element不存在或者無法更新時,則不會進行復用,返回 null

如果結果不為null,後面再呼叫 updateChild(...) 方法,這裡傳入的引數都不為null,所以會進入之前所說的第四種情況:

  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    ...
      //這裡一定是trueif (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;
      }
    ...
    return inflateWidget(newWidget, newSlot);
  }
複製程式碼

注意到上面的部分,呼叫 childupdate(newWidget) 方法,這個方法除了更新當前 Element 持有的 Widget 外,剩下的邏輯都交給子類去實現了

那麼 ComponentElement 的建立流程大致就講到這裡

下面,我們可以看一下 ComponentElement 的兩個子類 StatelessElementStatefulElement

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();
  }
}
複製程式碼

StatelessElement 非常簡單,重寫的 update(...) 也只是呼叫了 rebuild(),感覺沒有什麼可說的。

接下來看看 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
  void unmount() { ... }
}
複製程式碼

這裡展示了 StatefulElement 一些主要的方法,可以看到在建構函式中把 ElementWidget 物件放入了 State

接下來看一下剩下三個方法都做了什麼

_firstBuild()

  @override
  void _firstBuild() {
      ...
      final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
      ...
      _state._debugLifecycleState = _StateLifecycle.initialized;
    ...
    _state.didChangeDependencies();
      ...
      _state._debugLifecycleState = _StateLifecycle.ready;
      ...
    super._firstBuild();
  }
複製程式碼

ComponentElement 中是在 mount(...)裡呼叫 _firstBuild() 的,這裡重寫了這個方法,並且在裡面進行了一些初始化的操作,並且呼叫了 StateinitState() 方法

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();
  }
複製程式碼

在這裡主要是呼叫了 StatedidUpdateWidget(...) 方法,其他內容和 StatelessElement 差不多

那麼到了這裡,關於 StatefulWidget中我們常用的 setState() 方法,它具體會走過 StatelessElement 的哪些過程呢,下面我們就來看一下

StatefulElement的重新整理流程

我們知道 StatesetState() 方法會呼叫 ElementmarkNeedsBuild()

Element -> markNeedsBuild()

  BuildOwner get owner => _owner;

  void markNeedsBuild() {
    ...
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }
複製程式碼

下面進入 BuildOwner 看看 scheduleBuildFor(element) 做了些什麼

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;
    ...
  }
複製程式碼

可以看到,scheduleBuildFor(element) 後面會將需要重新整理的 Element 新增到 _dirtyElements 中,並將該 Element_inDirtyList 標記為 true

但之後並沒有做其他的操作,那重新整理到底是如何進行的呢?這就要看前面呼叫的一個方法 onBuildScheduled()

BuildOwner -> onBuildScheduled()

這個方法是在 BuildOwner 被建立時設定的

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

複製程式碼

來看一下 _handleBuildScheduled

WidgetsBinding -> _handleBuildScheduled

  void _handleBuildScheduled() {
    ...
    ensureVisualUpdate();
  }
複製程式碼

ensureVisualUpdate()SchedulerBinding 中被定義

mixin SchedulerBinding on BindingBase, ServicesBinding {
  ...
  void ensureVisualUpdate() {
    switch (schedulerPhase) {
      ...
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      ...
    }
  }
  ...
}
複製程式碼

後面會進入 scheduleFrame() 方法

SchedulerBinding -> scheduleFrame()

  @protected
  void ensureFrameCallbacksRegistered() {
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
  }
  
  void scheduleFrame() {
    ...
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }
  
複製程式碼

這裡會呼叫 window.scheduleFrame()

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';
}
複製程式碼

到了這裡,就是對 engine 的相關操作了。其中,經過各種各樣的操作之後,會回撥到 dart 層的 _drawFrame() 方法

sky_engine -> ui -> hooks.dart -> _drawFrame()

如果你對 engine 中的這一部分操作感興趣的話,可以看一下這一篇文章 Flutter渲染機制—UI執行緒, 因為都是C++的內容,超越了我的能力範疇,所以這裡直接去看大神的吧

_drawFrame() 方法內容如下:

void _drawFrame() {
  _invoke(window.onDrawFrame, window._onDrawFrameZone);
}
複製程式碼

最後,會呼叫到之前在 SchedulerBindingonDrawFrame 所註冊的 _handleDrawFrame 方法, 它會呼叫 handleDrawFrame()

SchedulerBinding -> handleDrawFrame()

  void handleDrawFrame() {
    ...
    try {
      // PERSISTENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
      ...
    }
    ...
  }
複製程式碼

在這裡會遍歷 _persistentCallbacks 來執行對應方法,它是通過 RendererBindingaddPersistentFrameCallback 新增,並且之後的每一次 frame 回撥都會遍歷執行一次

這裡將要執行的方法,是在 RendererBindinginitInstances() 中新增的 _handlePersistentFrameCallback

  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _mouseTracker.schedulePostFrameCheck();
  }
複製程式碼

最後,會呼叫到 WidgetBindingdrawFrame()

WidgetBinding -> drawFrame()

  @override
  void drawFrame() {
    ...
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    }
    ...
  }
複製程式碼

renderViewElement 就是我們之前看 runApp() 流程中所建立的 根Element

最後調又回到了 BuildOwner 物件,並呼叫它的 buildScope(...) 方法

BuildOwner -> buildScope(...)

buildScope(...) 用於對 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;
        ...
        //如果在區域性更新的過程中,_dirtyElements發生了變化
        //比如可能有新的物件插入了_dirtyElements,就在這裡進行處理
      }
    ...
      for (Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
    ...
        _debugBuilding = false;
    ...
  }
複製程式碼

所以其實從上面我們就能知道,區域性更新的原理就是把需要更新的物件存入了 _dirtyElements 中,然後在需要更新的時候,遍歷它們,進行 reBuild()

遍歷之前,會呼叫 sort(...) 方法進行排序,判斷條件是 Element 的深度,按照從小到大排列,也就是對於 Element 是從上往下更新的。

更新結束後,_dirtyElements 會被清空,各個標誌位也會被重置

到這裡,StatefulElement 的重新整理流程我們已經瞭解到了,接下來我們瞭解一下它的銷燬流程,同時順便也可以知道 Element 的銷燬流程

StatefulElement的銷燬流程

其實在 drawFrame()buildScope(...) 方法後緊跟著的 buildOwner.finalizeTree() 就是用於進行 Element 的銷燬的

不過這裡不將它作為入口,記得我們之前 ElementupdateChild(...) 方法裡面,有兩處會對 Element 進行銷燬,而呼叫銷燬的方法就是 deactivateChild(child)

下面就從這裡作為入口

Element -> deactivateChild(child)

  @protected
  void deactivateChild(Element child) {
    ...
    child._parent = null;
    child.detachRenderObject();
    owner._inactiveElements.add(child);
    ...
  }
複製程式碼

在這裡會清除掉 child 對於 parent 的引用,同時也呼叫 detachRenderObject() 去銷燬 RenderObject,關於它的細節下一片蘸再說。

最主要的還是向 BuildOwner_inactiveElements 中新增了當前要銷燬的 child

我們可以先了解一下 _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);
  }
}
複製程式碼

可以看到,_InactiveElements 中使用 Set 存放了所有需要銷燬的物件

add(element) 方法中,如果當前要銷燬的物件還處於活躍狀態,則通過遞迴的方式,遍歷它的 children ,對每個 child Element 呼叫 deactivate() 來設為銷燬狀態,可以看一下 deactivate() 方法

  ///Element
  @mustCallSuper
  void deactivate() {
    ...
    _inheritedWidgets = null;
    _active = false;
    ...
      _debugLifecycleState = _ElementLifecycle.inactive;
    ...
  }
複製程式碼

將要被銷燬的 Element 生命週期會變為 inactive

這裡我們收集完要銷燬的 Element 之後呢,就會在WidgetsBindingdrawFrame() 被觸發時呼叫 finalizeTree() 來進行真正的銷燬了

BuildOwner -> finalizeTree()

  void finalizeTree() {
    ...
      lockState(() {
        _inactiveElements._unmountAll(); // this unregisters the GlobalKeys
      });
    ...
  }
複製程式碼

這裡呼叫了 _InactiveElements_unmountAll() 來進行銷燬

_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;
    }
  }
複製程式碼

這裡的銷燬也是由上到下的,呼叫了 _unmount(element) 方法

  void _unmount(Element element) {
    ...
    element.visitChildren((Element child) {
      assert(child._parent == element);
      _unmount(child);
    });
    element.unmount();
    ...
  }
複製程式碼

不得不說,dart 將方法作為引數傳遞,並且在某些情況下可以省略輸入方法的引數真是好用,不過可能就犧牲了一點可讀性

_unmount(element) 也是遍歷 children ,然後呼叫 child Elementunmount() 來進行銷燬

_InactiveElements 中還有一個 remove 方法,介紹 unmount() 前我們先把這個給看了

_InactiveElements -> remove()

  void remove(Element element) {
    ...
    _elements.remove(element);
    ...
  }
複製程式碼

這裡就是從 Set 中移除傳入的 Element 物件,在之前的 _unmountAll() 中會通過 clear() 清楚 Set 內所有的元素,那為什麼這裡會有這樣一種情況呢?之前我們在 ElementinflateWidget(...) 中提到過,GlobalKey 是可以用於複用 Element 的,被複用的 Element 物件無需重新建立。我們再來看一下

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);
        return updatedChild;
      }
    }
    ...
  }

  Element _retakeInactiveElement(GlobalKey key, Widget newWidget) {
    ...
    final Element element = key._currentElement;
    if (element == null)
      return null;
    if (!Widget.canUpdate(element.widget, newWidget))
      return null;
    ...
    final Element parent = element._parent;
    if (parent != null) {
      ...
      parent.forgetChild(element);
      parent.deactivateChild(element);
    }
    ...
    owner._inactiveElements.remove(element);
    return element;
  }
複製程式碼

可以看到,在 _retakeInactiveElement(...) 末尾處,會將被複用了的 Element_inactiveElements 中移除掉。獲取到這個 Element 後,會呼叫 _activateWithParent(...) 方法再次將 Element 啟用

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);
  }
複製程式碼

這裡會通過遞迴呼叫,啟用 Element 和它的 children ,來看一下 active() 方法

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();
  }
複製程式碼

activate() 中,Element 的生命週期再次變為了 active, 這也就是我們之前所說過的, Element 的四個生命週期中可能會出現 activeinactive 切換的情況。

那麼在哪種情況下會觸發這種複用呢?其實很簡單:當處於不同深度的同一型別 Widget 使用了同一個 GlobalKey 就可以, 比如下面這樣:

Center(
          child: changed
              ? Container(
                  child: Text('aaaa', key: globalKey),
                  padding: EdgeInsets.all(20))
              : Text('bbb', key: globalKey),
        )
複製程式碼

當通過 setState 修改 changed 時,就可以觸發複用

插曲就說到這裡,我們繼續之前的 unmount() 方法

Element -> unmount()

  @mustCallSuper
  void unmount() {
    ...
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._unregister(this);
    }
    assert(() {
      _debugLifecycleState = _ElementLifecycle.defunct;
      return true;
    }());
  }
複製程式碼

可以看到,unmount() 中,如果當前 Element 註冊了 Globalkey 就會被清空掉,同時生命週期會被設定為 defunct,在 StatefulElement 中會重寫這個方法

StatefulElement -> unmount()

  @override
  void unmount() {
    super.unmount();
    _state.dispose();
    ...
    _state._element = null;
    _state = null;
  }
複製程式碼

在這裡呼叫了 Statedispose() 方法,並且在之後清理了 State 中持有的 Element 引用,最後將 State 置空

到了這裡,StatefulElement 的銷燬流程也結束了,本篇文章也接近尾聲

當然,還有 RenderObjectElement 都還沒有做過分析,因為所有和 RenderObject 有關的內容都將放到第三篇來講,這裡就先跳過吧

總結

  • Element 是真正的資料持有者,而 State 也是在它的建構函式裡被建立,它的生命週期比 State 略長。
  • 每次重新整理時,Widget 都會被重新建立,而在 Element 的建立流程結束後, Element 只有在 canUpdate(...) 返回 false 時才會重新建立,不然一般都是呼叫它的 update(...) 進行更新。StatelessElement 也是這樣的。
  • GlobalKey 除了可以跨 Widget 傳遞資料外,還可以對 Element 進行復用

剩下的總結,就看圖片吧

注:以上原始碼分析基於flutter stable 1.13.6
新的版本可能存在程式碼不一致的地方,比如updateChild(...),但是邏輯都是一樣的,可以放心食用

從原始碼看flutter(二):Element篇

相關文章