【Flutter脫髮錄】盤一盤Element

愛新覺羅狗剩兒發表於2020-04-13

前面的兩篇小記,介紹了WidgetRenderObjectWidget代表了開發人員的想法,RenderObject負責在螢幕上進行繪製。那麼想法如何驅動RenderObject工作呢?那麼就需要引入Element這個中介,今天就來盤它一盤!

官方解釋

An instantiation of a [Widget] at a particular location in the tree.

** Widget在UI樹某個具體位置的例項**

閱讀原始碼

要知道Element是什麼?幹了什麼?必然逃不過看原始碼!

abstract class Element extends DiagnosticableTree implements BuildContext {
  // 構造器傳入Widget 並持有它
  Element(Widget widget)
      : assert(widget != null),
        _widget = widget;

  Element _parent;

  // 當前element在父element的children中的位置
  // 當element只有一個child時 應該置null
  dynamic _slot;

  // 當前element在element tree 上的層級
  // root element = 0
  // child.depth=parent.depth+1
  int _depth;

  // 持有的widget物件
  Widget _widget;

  // element生命週期的管理類
  BuildOwner _owner;

  bool _active = false;

  // 遍歷標記重建 僅debug模式下 hot reload
  void reassemble() {
    markNeedsBuild();
    visitChildren((Element child) {
      child.reassemble();
    });
  }

  // 如果當前為 RenderObjectElement 則返回當前節點對應的render object
  // 否則從當前節點向下尋找,返回第一個render object
  RenderObject get renderObject {
    RenderObject result;
    void visit(Element element) {
      if (element is RenderObjectElement)
        result = element.renderObject;
      else
        element.visitChildren(visit);
    }

    visit(this);
    return result;
  }

  // 根據widget配置的變化,更新child
  // 這個方法是整個widget體系的核心方法
  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null) deactivateChild(child);
      return null;
    }
    Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass &&
          Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        child.update(newWidget);
        newChild = child;
      } else {
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }

  // 將element掛載到element tree的指定位置
  // 初始化操作
  // initial -> active
  @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;
    if (widget.key is GlobalKey) {
      final GlobalKey key = widget.key;
      key._register(this);
    }
    _updateInheritance();
  }

  // 更新持有的widget配置
  @mustCallSuper
  void update(covariant Widget newWidget) {
    _widget = newWidget;
  }

  // 遍歷更新節點在render tree的位置
  @protected
  void updateSlotForChild(Element child, dynamic newSlot) {
    void visit(Element element) {
      element._updateSlot(newSlot);
      if (element is! RenderObjectElement) element.visitChildren(visit);
    }

    visit(child);
  }

  // 更新當前節點在render tree上的位置
  void _updateSlot(dynamic newSlot) {
    _slot = newSlot;
  }

  // 遍歷更新節點深度
  void _updateDepth(int parentDepth) {
    final int expectedDepth = parentDepth + 1;
    if (_depth < expectedDepth) {
      _depth = expectedDepth;
      visitChildren((Element child) {
        child._updateDepth(expectedDepth);
      });
    }
  }

  // 從自己和自己的子節點render tree 上移除  slot = null
  void detachRenderObject() {
    visitChildren((Element child) {
      child.detachRenderObject();
    });
    _slot = null;
  }

  // 在指定位置插入render tree
  void attachRenderObject(dynamic newSlot) {
    visitChildren((Element child) {
      child.attachRenderObject(newSlot);
    });
    _slot = newSlot;
  }

  // 從不活躍element列表中,取出並重新啟用,複用element降低開銷
  // Widget.canUpdate(oldWidget, newWidget)判斷兩次配置是否可以在原基礎上升級
  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;
  }

  // 填充widget  根據新的widget配置在指定位置生成一個element 並mount到tree
  // 如果有可複用的element 則在原element上根據newWidget更新
  @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);
        return updatedChild;
      }
    }
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    return newChild;
  }

  // 將一個element從tree移除,並置為不活躍狀態
  @protected
  void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject();
    owner._inactiveElements
        .add(child); // this eventually calls child.deactivate()
  }

  // 啟用
  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);
  }

  // inactive -> active
  // 只有再次啟用時才會呼叫  初次建立不呼叫
  @mustCallSuper
  void activate() {
    final bool hadDependencies =
        (_dependencies != null && _dependencies.isNotEmpty) ||
            _hadUnsatisfiedDependencies;
    _active = true;
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();
    if (_dirty) owner.scheduleBuildFor(this);
    if (hadDependencies) didChangeDependencies();
  }

  // 凍結 active -> inactive
  @mustCallSuper
  void deactivate() {
    if (_dependencies != null && _dependencies.isNotEmpty) {
      for (InheritedElement dependency in _dependencies)
        dependency._dependents.remove(this);
    }
    _inheritedWidgets = null;
    _active = false;
  }

  // 解除安裝  無法再被啟用 inactive -> defunct
  @mustCallSuper
  void unmount() {
    if (widget.key is GlobalKey) {
      final GlobalKey key = widget.key;
      key._unregister(this);
    }
  }

  // 獲取持有的render object物件
  @override
  RenderObject findRenderObject() => renderObject;

  // 獲取對應render object的size
  @override
  Size get size {
    final RenderObject renderObject = findRenderObject();
    if (renderObject is RenderBox) return renderObject.size;
    return null;
  }

  // 當前有依賴關係的widget對應的element map儲存
  Map<Type, InheritedElement> _inheritedWidgets;
  // 與當前element有依賴的祖先 set型別
  Set<InheritedElement> _dependencies;
  bool _hadUnsatisfiedDependencies = false;

  // 獲取某個祖先InheritedElement持有的InheritedWidget  並且將此InheritedElement註冊為依賴的祖先
  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor,
      {Object aspect}) {
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

  // 獲取祖先節點Type為T的Widget物件  InheritedWidget子類能訪問父類的核心原理
  @override
  T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>(
      {Object aspect}) {
    final InheritedElement ancestor =
        _inheritedWidgets == null ? null : _inheritedWidgets[T];
    if (ancestor != null) {
      return dependOnInheritedElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

  @override
  InheritedElement
      getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
    final InheritedElement ancestor =
        _inheritedWidgets == null ? null : _inheritedWidgets[T];
    return ancestor;
  }

  void _updateInheritance() {
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

  // 一直向上尋找Widget型別為T的,並且返回第一個
  @override
  T findAncestorWidgetOfExactType<T extends Widget>() {
    Element ancestor = _parent;
    while (ancestor != null && ancestor.widget.runtimeType != T)
      ancestor = ancestor._parent;
    return ancestor?.widget;
  }

  // 向上查詢第一個型別為T的State
  @override
  T findAncestorStateOfType<T extends State<StatefulWidget>>() {
    Element ancestor = _parent;
    while (ancestor != null) {
      if (ancestor is StatefulElement && ancestor.state is T) break;
      ancestor = ancestor._parent;
    }
    final StatefulElement statefulAncestor = ancestor;
    return statefulAncestor?.state;
  }

  // 向上查詢最後一個型別為T的State
  @override
  T findRootAncestorStateOfType<T extends State<StatefulWidget>>() {
    Element ancestor = _parent;
    StatefulElement statefulAncestor;
    while (ancestor != null) {
      if (ancestor is StatefulElement && ancestor.state is T)
        statefulAncestor = ancestor;
      ancestor = ancestor._parent;
    }
    return statefulAncestor?.state;
  }

  // 向上查詢第一個型別為T的RenderObject
  @override
  T findAncestorRenderObjectOfType<T extends RenderObject>() {
    Element ancestor = _parent;
    while (ancestor != null) {
      if (ancestor is RenderObjectElement && ancestor.renderObject is T) break;
      ancestor = ancestor._parent;
    }
    final RenderObjectElement renderObjectAncestor = ancestor;
    return renderObjectAncestor?.renderObject;
  }

  @override
  void visitAncestorElements(bool visitor(Element element)) {
    Element ancestor = _parent;
    while (ancestor != null && visitor(ancestor)) ancestor = ancestor._parent;
  }

  // 當依賴發生改變時 需要重建
  @mustCallSuper
  void didChangeDependencies() {
    markNeedsBuild();
  }

  bool get dirty => _dirty;
  bool _dirty = true;

  bool _inDirtyList = false;

  void markNeedsBuild() {
    if (!_active) return;
    if (dirty) return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }

  void rebuild() {
    if (!_active || !_dirty) return;
    performRebuild();
  }

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

生命週期

作為一個實體,那麼Element就有它的生命週期:

enum _ElementLifecycle {
  initial, // 初始化
  active, // 活躍
  inactive, // 不活躍
  defunct, // 廢棄
}
複製程式碼

Element各方法與生命週期的關係:

Widget.createElement() => initial
mount() => initial -> active
update() => 只有在active狀態時才會生效
deactivate() => active -> inactive
activate() => inactive -> active
unmount() => inactive -> defunct
複製程式碼

ElementWidget的關係

Element 的建立

從構造器可以看到,傳入了一個Widget物件,並持有這個物件。那麼這個構造器何時被呼叫,即Element何時被建立呢?

Widget篇中提到,Widget有一個createElement()方法標記為@protected,看看它的幾個關鍵的子類是如何實現的:

StatelessWidget => StatelessElement createElement() => StatelessElement(this);
StatefulWidget => StatefulElement createElement() => StatefulElement(this);
SingleChildRenderObjectWidget => SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
MultiChildRenderObjectWidget => MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
InheritedWidget => InheritedElement createElement() => InheritedElement(this);
ParentDataWidget<T extends ParentData> => ParentDataElement<T> createElement() => ParentDataElement<T>(this);
複製程式碼

可以看到,每個Widget都有其專有的Element的,並且在createElement()方法中,建立它們。從Elementslotdepth欄位可以得到,Element就是Widget在整個UI樹中的一個具體例項。

Element 的更新

UI樹已經被構建,當某個節點需要變化時,如何優雅地更新這棵UI樹,是整個Flutter效能表現的重中之重。

updateChild方法中,體現的思想就是,比對配置,能複用就複用。

  // 根據widget配置的變化,更新child
  // 這個方法是整個widget體系的核心方法
  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    // 更新後的widget為null,即widget被移除 則將其對應的element 凍結
    if (newWidget == null) {
      if (child != null) deactivateChild(child);
      return null;
    }
    Element newChild;
    // 更新後的widget不為null
    if (child != null) {
      // child不為null 即該節點存在
      bool hasSameSuperclass = true; // debug欄位
      if (hasSameSuperclass && child.widget == newWidget) {
        // 更新後的widget與原來的widget完全相同  則只更新slot在UI樹中的位置
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass &&
          Widget.canUpdate(child.widget, newWidget)) {
        // 新老不完全相同 則判斷是否可以在原element上更新 
        // oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key
        // 如果需要更新位置 則更新位置
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        // 更換持有的widget物件為newWidget
        child.update(newWidget);
        newChild = child;
      } else {
        // newWidget無法在原來的widget基礎上更新 則凍結原來的element,新建一個element插入tree的slot位置
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      // child為null  即新增的widget 直接建立
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }
複製程式碼

BuildContext

原始碼的第一行可以看到,Element實現了BuildContext這個抽象介面類。並且可以發現,Element中的大部分方法都是對BuildContext的實現。

當我們用StatelessWidgetStatefulWidget搭積木時,也會發現build方法都會傳一個BuildContext物件:

Widget build(BuildContext context) {}
複製程式碼

用這個context,我們可以:

  • 獲取主題Theme.of(context)
  • 獲取螢幕資訊MediaQuery.of(context)
  • 路由Navigator.of(context)
  • 獲取大小context.size

檢視Theme原始碼,Theme.of(context)方法實際是通過dependOnInheritedWidgetOfExactType方法,全域性找到_InheritedTheme類,獲取其theme屬性,然後根據didChangeDependencies機制,觸發子節點的重繪,達到主題的效果。

這些方法是不是很熟悉?是的,都是Element的職責範疇,既然它的職權這麼廣,作用那麼大,必然需要保護起來,遮蔽對其的直接訪問。從而引入了BuildContext,間接對其訪問。

ElementRenderObject的關係

RenderObject 的建立

之前說過,Widget雖然指定了RenderObject,但並沒有真正建立。那麼RenderObject是什麼時候被建立的呢?看下RenderObjectElement的原始碼,找到mount方法:

abstract class RenderObjectElement extends Element {
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    // 此時才真正的建立,並持有renderobject
    _renderObject = widget.createRenderObject(this);
    // 插入到render tree
    attachRenderObject(newSlot);
    _dirty = false;
  }
}
複製程式碼

ElementRenderObject的操作

具體可見RenderObjectElement,本文就暫不詳細說明了。

總結

經過Element的串連,整個Flutter的UI機制的思路也變得清晰起來。Element就像是一位產品經理,將客戶Widget提出的構想和需求變更,整理之後,安排各崗位的小夥伴RenderObject,按照開發流程,一步一步開發,bugfix,最終完成客戶的期望。

深夜了,給自己來碗黑芝麻糊恰恰吧!

相關文章