本文是『 深刻淺出 Flutter Framework 』系列文章的第三篇,主要圍繞 Element 相關內容進行分析介紹,包括 Element 分類、Element 與其餘幾個核心元素的關係、Element 生命週期以及核心方法解讀等。git
本文同時發表於個人我的博客github
本系列文章將深刻 Flutter Framework 內部逐步去分析其核心概念和流程,主要包括:json
經過『 深刻淺出 Flutter Framework 之 Widget 』的介紹,咱們知道 Widget 本質上是 UI 的配置數據 (靜態、不可變),Element 則是經過 Widget 生成的『實例』,二者間的關係就像是 json 與 object。設計模式
同一份配置 (Widget) 能夠生成多個實例 (Element),這些實例可能會被安插在樹上不一樣的位置。less
UI 的層級結構在 Element 間造成一棵真實存在的樹「Element Tree」,Element 有 2 個主要職責:ide
build
方法去建立。同時,該類型 Element 都只有一個子節點 (single child);原生型 Element,只有 MultiChildRenderObjectElement 是多子節點的,其餘都是單子節點。post
同時,能夠看到,Element
實現了BuildContext
接口 —— 咱們在 Widget 中遇到的context
,其實就是該 Widget 對應的 Element。動畫
在繼續以前有必要先了解一下 Element 與其餘幾個核心元素間的關係,以便在全局上有個認識。 ui
上述這些關係並非全部類型的 Element 都有,如:「Render Object」只有「RenderObject Element」纔有,State 只有「Stateful Element」纔有。this
Element 做爲『實例』,隨着 UI 的變化,有較複雜的生命週期:
parent 經過Element.inflateWidget
->Widget.createElement
建立 child element,觸發場景有:UI 的初次建立、UI 刷新時新老 Widget 不匹配(old element 被移除,new element 被插入);
parent 經過Element.mount
將新建立的 child 插入「Element Tree」中指定的插槽處 (slot);
dynamic Element.slot
——其含意對子節點透明,父節點用於肯定其下子節點的排列順序 (兄弟節點間的排序)。所以,對於單子節點的節點 (single child),child.slot 一般爲 null。 另外,slot 的類型是動態的,不一樣類型的 Element 可能會使用不一樣類型的 slot,如:Sliver 系列使用的是 int 型的 index,MultiChildRenderObjectElement 用兄弟節點做爲後一個節點的 slot。 對於「component element」,mount
方法還要負責全部子節點的 build (這是一個遞歸的過程),對於「render element」,mount
方法須要負責將「render object」添加到「render tree」上。其過程在介紹到相應類型的 Element 時會詳情分析。
此時,(child) element 處於 active 狀態,其內容隨時可能顯示在屏幕上;
此後,因爲狀態更新、UI 結構變化等,element 所在位置對應的 Widget 可能發生了變化,此時 parent 會調用Element.update
去更新子節點,update 操做會在以當前節點爲根節點的子樹上遞歸進行,直到葉子節點;(執行該步驟的前提是新老 Widget.[key && runtimeType] 相等,不然建立新 element,而不是更新現有 element);
狀態更新時,element 也可能會被移除 (如:新老 Widget.[key || runtimeType] 不相等),此時,parent 將調用deactivateChild
方法,該方法主要作了 3 件事:
owner._inactiveElements
中,在添加過程當中會對『以該 element 爲根節點的子樹上全部節點』調用deactivate
方法 (移除的是整棵子樹)。void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
複製代碼
此時,element 處於 "inactive" 狀態,並從屏幕上消失,該狀態一直持續到當前幀動畫結束;
從 element 進入 "inactive" 狀態到當前幀動畫結束期間,其還有被『搶救』的機會,前提是『帶有「global key」&& 被從新插入樹中』,此時:
owner._inactiveElements
中移除;activate
方法 (它們又復活了!);上述過程經歷這幾個方法:
Parent Element.inflateWidget
-->Parent Element._retakeInactiveElement
-->BuildOwner._inactiveElements.remove
-->Child Element._activateWithParent
...
對於全部在當前幀動畫結束時未能成功『搶救』回來的「Inactive Elements」都將被 unmount;
至此,element 生命週期圓滿結束。
下面對 Element 中的幾個核心方法進行簡單介紹:
updateChild
是 flutter framework 中的核心方法之一:
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);
return child;
}
deactivateChild(child);
assert(child._parent == null);
}
return inflateWidget(newWidget, newSlot);
}
複製代碼
在「Element Tree」上,父節點經過該方法來修改子節點對應的 Widget。
newWidget
== null
—— 說明子節點對應的 Widget 已被移除,直接 remove child element (若有);child
== null
—— 說明 newWidget 是新插入的,建立子節點 (inflateWidget);child
!= null
—— 此時,分爲 3 種狀況:
updateSlotForChild
修改 child.slot 便可;Widget.canUpdate
判斷是否能夠用 newWidget 修改 child element,若能夠,則調用update
方法;子類通常不須要重寫該方法,該方法有點相似設計模式中的『模板方法』。
在更新流程中,若新老 Widget.[runtimeType && key] 相等,則會走到該方法。 子類須要重寫該方法以處理具體的更新邏輯:
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
複製代碼
基類中的update
很簡單,只是對_widget
賦值。
子類重寫該方法時必須調用 super.
父類
ComponentElement
沒有重寫該方法
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
複製代碼
經過rebuild
方法觸發重建 child widget (第 4 行),並以此來 update child element,期間會調用到StatelessWidget.build
方法 (也就是咱們寫的 Flutter 代碼)。
組合型 Element 都會在
update
方法中觸發rebuild
操做,以便從新 build child widget。
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget;
try {
_state.didUpdateWidget(oldWidget) as dynamic;
}
finally {
}
rebuild();
}
複製代碼
相比StatelessElement
,StatefulElement.update
稍微複雜一些,須要處理State
,如:
_widget
屬性;State.didUpdateWidget
(熟悉麼)。最後,一樣會觸發rebuild
操做,期間會調用到State.build
方法。
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
super.update(newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
Widget build() => widget.child;
複製代碼
ProxyElement.update
方法須要關注的是對updated
的調用,其主要用於通知關聯對象 Widget 有更新。 具體通知邏輯在子類中處理,如:InheritedElement
會觸發全部依賴者 rebuild (對於 StatefulElement 類型的依賴者,會調用State.didChangeDependencies
)。
ProxyElement 的build
操做很簡單:直接返回widget.child
。
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
複製代碼
RenderObjectElement.update
方法調用了widget.updateRenderObject
來更新「Render Object」(熟悉麼)。
SingleChildRenderObjectElement
、MultiChildRenderObjectElement
是RenderObjectElement
的子類。
void update(SingleChildRenderObjectWidget newWidget) {
super.update(newWidget);
_child = updateChild(_child, widget.child, null);
}
複製代碼
第 3 行,經過newWidget.child
調用updateChild
方法遞歸修改子節點。
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
}
複製代碼
上述實現看似簡單,實則很是複雜,在updateChildren
方法中處理了子節點的插入、移動、更新、刪除等全部狀況。
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;
}
複製代碼
inflateWidget
屬於模板方法,故通常狀況下子類不用重寫。
該方法的主要職責:經過 Widget 建立對應的 Element,並將其掛載 (mount) 到「Element Tree」上。
若是 Widget 帶有 GlobalKey,首先在 Inactive Elements 列表中查找是否有處於 inactive 狀態的節點 (即剛從樹上移除),如找到就直接復活該節點。
主要調用路徑來自上面介紹的updateChild
方法。
當 Element 第一次被插入「Element Tree」上時,調用該方法。因爲此時 parent 已肯定,故在該方法中能夠作依賴 parent 的初始化操做。通過該方法後,element 的狀態從 "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();
}
複製代碼
還記得BuildOwner
嗎,正是在該方法中父節點的 owner 傳給了子節點。 若是,對應的 Widget 帶有 GlobalKey,進行相關的註冊。 最後,繼承來自父節點的「Inherited Widgets」。
子類重寫該方法時,必須調用 super。 關於「Inherited Widgets」,後文會詳細分析
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
void _firstBuild() {
rebuild();
}
複製代碼
組合型 Element 在掛載時會執行_firstBuild->rebuild
操做。
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
複製代碼
在RenderObjectElement.mount
中作的最重要的事就是經過 Widget 建立了「Render Object」(第 3 行),並將其插入到「RenderObject Tree」上 (第 4 行)。
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
複製代碼
SingleChildRenderObjectElement
在 super (RenderObjectElement
) 的基礎上,調用updateChild
方法處理子節點,其實此時_child
爲nil
,前面介紹過當 child 爲nil
時,updateChild
會調用inflateWidget
方法建立 Element 實例。
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_children = List<Element>(widget.children.length);
Element previousChild;
for (int i = 0; i < _children.length; i += 1) {
final Element newChild = inflateWidget(widget.children[i], previousChild);
_children[i] = newChild;
previousChild = newChild;
}
}
複製代碼
MultiChildRenderObjectElement
在 super (RenderObjectElement
) 的基礎上,對每一個子節點直接調用inflateWidget
方法。
void markNeedsBuild() {
if (!_active)
return;
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
複製代碼
markNeedsBuild
方法其實在介紹BuildOwer時已經分析過,其做用就是將當前 Element 加入_dirtyElements
中,以便在下一幀能夠rebuild。 那麼,哪些場景會調用markNeedsBuild
呢?
State.setState
—— 這個在介紹 Widget 時已分析過了;Element.reassemble
—— debug hot reload;Element.didChangeDependencies
—— 前面介紹過當依賴的「Inherited Widget」有變化時會致使依賴者 rebuild,就是從這裏觸發的;StatefulElement.activate
—— 還記得activate
嗎?前文介紹過當 Element 從 "inactive" 到 "active" 時,會調用該方法。爲何StatefulElement
要重寫activate
?由於StatefulElement
有附帶的 State,須要給它一個activate
的機會。子類通常沒必要重寫該方法。
void rebuild() {
if (!_active || !_dirty)
return;
performRebuild();
}
複製代碼
該方法邏輯很是簡單,對於活躍的、髒節點調用performRebuild
,在 3 種場景下被調用:
BuildOwner.buildScope
;Element.mount
調用;update
方法內被調用。上述第 二、3 點僅「Component Element」須要
Element 基類中該方法是no-op
。
void performRebuild() {
Widget built;
built = build();
_child = updateChild(_child, built, slot);
}
複製代碼
對於組合型 Element,rebuild 過程其實就是調用build
方法生成「child widget」,再由其更新「child element」。
StatelessElement.build:
Widget build() => widget.build(this);
StatefulElement.build:Widget build() => state.build(this);
ProxyElement.build:Widget build() => widget.child;
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
複製代碼
在渲染型 Element 基類中只是用 Widget 更新了對應的「Render Object」。 在相關子類中能夠執行更具體的邏輯。
至此,Element 的核心方法基本已介紹完,是否是有點暈乎乎的感受?inflateWidget
、updateChild
、update
、mount
、rebuild
以及performRebuild
等你中有我、我中有你,再加上不一樣類型的子類對這些方法的重寫。
下面,咱們以 Element 生命週期爲切入點將這些方法串起來。 對於一個 Element 節點來講在其生命週期內可能會歷經幾回『重大事件』:
inflateWidget
,隨之被掛載到「Element Tree」上, 此後遞歸建立子節點;
parent.updateChild
->child.update
;
rebuild
方法(調用場景上面已分析);
在 Element 基類中有這樣兩個成員:
Map<Type, InheritedElement> _inheritedWidgets;
Set<InheritedElement> _dependencies;
複製代碼
它們是幹嗎用的呢?
_inheritedWidgets
—— 用於收集從「Element Tree」根節點到當前節點路徑上全部的「Inherited Elements」; 前文提到過在mount
方法結束處會調用_updateInheritance
: 如下是 Element 基類的實現,能夠看到子節點直接得到父節點的_inheritedWidgets
:void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
複製代碼
如下是InheritedElement
類的實現,其在父節點的基礎上將本身加入到_inheritedWidgets
中,以便其子孫節點的_inheritedWidgets
包含它 (第 8 行):
void _updateInheritance() {
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
複製代碼
_dependencies
—— 用於記錄當前節點依賴了哪些「Inherited Elements」,一般咱們調用context.dependOnInheritedWidgetOfExactType<T>
時就會在當前節點與目標 Inherited 節點間造成依賴關係。在 Element 上提供的便利方法
of
,通常殾會調用dependOnInheritedWidgetOfExactType
。
同時,在InheritedElement
中還有用於記錄全部依賴於它的節點:final Map<Element, Object> _dependents
。 最終,在「Inherited Element」發生變化,須要通知依賴者時,會利用依賴者的_dependencies
信息作一下 (debug) check (第 4 行):
void notifyClients(InheritedWidget oldWidget) {
for (Element dependent in _dependents.keys) {
// check that it really depends on us
assert(dependent._dependencies.contains(this));
notifyDependent(oldWidget, dependent);
}
}
複製代碼
至此,Element 相關的內容基本已介紹完。總結提煉一下:
最後,強烈推薦Keys! What are they good for?這篇文章,對於理解本文相關的內容有很大的幫助。