前面的兩篇小記,介紹了Widget
和RenderObject
,Widget
代表了開發人員的想法,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
複製程式碼
Element
和Widget
的關係
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()
方法中,建立它們。從Element
的slot
和depth
欄位可以得到,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
的實現。
當我們用StatelessWidget
和StatefulWidget
搭積木時,也會發現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
,間接對其訪問。
Element
和RenderObject
的關係
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;
}
}
複製程式碼
Element
對RenderObject
的操作
具體可見RenderObjectElement
,本文就暫不詳細說明了。
總結
經過Element
的串連,整個Flutter的UI機制的思路也變得清晰起來。Element
就像是一位產品經理,將客戶Widget
提出的構想和需求變更,整理之後,安排各崗位的小夥伴RenderObject
,按照開發流程,一步一步開發,bugfix,最終完成客戶的期望。
深夜了,給自己來碗黑芝麻糊恰恰吧!