開篇
上一篇 從原始碼看flutter(一):Widget篇 我們瞭解到了關於 Widget
的相關知識, 知道了 Element
都是通過 Widget
的 createElement()
方法來建立的。
那麼,是誰呼叫了 createElement()
方法?通過查詢, 發現只有兩處呼叫了這個方法。分別是:
Element
的inflateWidget(...)
方法RenderObjectToWidgetAdapter
的attachToRenderTree(...)
方法
第一個方法在 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
我們常用的 StatefulWidget
、StatelessWidget
所對應的 Element
物件,繼承關係如下:
xxxElement -> ComponentElement -> Element
許多其他的 Element
物件也都是直接或者間接繼承於 ComponentElement
,不過 RenderObjectWidget
的 Element
繼承關係如下:
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
, 經常可以在 Widget
或 State
的 build(...)
方法中看到它,我們先來簡單的瞭解一下它
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
物件只在 WidgetsBinding
的 initInstances()
中初始化過一次,也就是說全域性只有唯一的例項。他是 widget framework 的管理類,實際上的的作用有很多,比如在 Element
中,就負責管理它的生命週期
其他的一些方法:
- findRenderObject(): 用於返回當前
Widget
對應的RenderObject
,如果當前Widget
不是RenderObjectWidget
則從children中尋找 - getElementForInheritedWidgetOfExactType(): 在維護的
Map<Type, InheritedElement>
中查詢InheritedElement
,在我們熟知的Provider
中的Provider.of<T>(context)
就是通過這種方法獲取資料類的 - findAncestorWidgetOfExactType(): 通過遍歷
Element
的 parent 來找到指定型別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
用於表示它在 parent
中 child列表 的位置,如果 parent
只有一個 child , slot
應該為 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
的有點類似,不過其中的 active
和 inactive
是可以來回切換的,這裡就涉及到 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
瞭解一下 Element
與 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;
}
複製程式碼
解釋一下就是,如果當前 element
是 RenderObjectElement
的話,直接返回它持有的 renderObject
,否則遍歷 children 去獲取最近的 renderObject
物件
從這裡也可以知道 RenderObject
只與 RenderObjectElement
是一一對應的,與其他 Element
則是一對多的關係,也驗證了我們上一篇中的判定
不過這裡有一點需要吐槽的是,在方法裡面直接定義方法,閱讀體驗不是特別好,而後面這樣的情況還會很多
接下來,我們準備進入 Element
的建立流程入口
Element 建立流程入口
既然要走建立流程,自然是要找個起點的。在上一篇中,我們知道通過 createElement()
建立 Element
的方法只在兩個地方被呼叫:
- 其一是作為根節點
Element
的RenderObjectToWidgetElement
在RenderObjectToWidgetAdapter
的attachToRenderTree(...)
中被建立 - 另一個是其他所有
Element
在inflateWidget(...)
方法中被建立
我們以第二個方法為入口,進入 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;
}
...
}
複製程式碼
可以看到,上面最後呼叫了 Element
的 mount(...)
方法,所以這個方法算是各個 Element
的入口了。
上一篇我們提到過,不同 Widget
對應 Element
的實現都不一樣,其中最廣泛的兩種實現分別是 ComponentElement
和 RenderObjectElement
。
我們可以從第一個開始瞭解
ComponentElement 的建立流程
進入它的 mount(...)
方法
mount(...)
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_firstBuild();
...
}
複製程式碼
呼叫了父類,也就是 Element
的 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 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(...)
中,進行了一些列的初始化操作。
其中如果傳入的 key
是 GlobalKey
,會將當前 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 child
、Widget 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
的部分。如果獲取到 widget
的 key
是 GlobalKey
, 並且之前 Widget
已經在 Element
的 mount(...)
中註冊到了 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) {
...
//這裡一定是true的
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;
}
...
return inflateWidget(newWidget, newSlot);
}
複製程式碼
注意到上面的部分,呼叫 child
的 update(newWidget)
方法,這個方法除了更新當前 Element
持有的 Widget
外,剩下的邏輯都交給子類去實現了
那麼 ComponentElement
的建立流程大致就講到這裡
下面,我們可以看一下 ComponentElement
的兩個子類 StatelessElement
與 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();
}
}
複製程式碼
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
一些主要的方法,可以看到在建構函式中把 Element
和 Widget
物件放入了 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()
的,這裡重寫了這個方法,並且在裡面進行了一些初始化的操作,並且呼叫了 State
的 initState()
方法
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();
}
複製程式碼
在這裡主要是呼叫了 State
的 didUpdateWidget(...)
方法,其他內容和 StatelessElement
差不多
那麼到了這裡,關於 StatefulWidget
中我們常用的 setState()
方法,它具體會走過 StatelessElement
的哪些過程呢,下面我們就來看一下
StatefulElement的重新整理流程
我們知道 State
的 setState()
方法會呼叫 Element
的 markNeedsBuild()
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);
}
複製程式碼
最後,會呼叫到之前在 SchedulerBinding
中 onDrawFrame
所註冊的 _handleDrawFrame
方法, 它會呼叫 handleDrawFrame()
SchedulerBinding -> handleDrawFrame()
void handleDrawFrame() {
...
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
...
}
...
}
複製程式碼
在這裡會遍歷 _persistentCallbacks
來執行對應方法,它是通過 RendererBinding
的 addPersistentFrameCallback
新增,並且之後的每一次 frame 回撥都會遍歷執行一次
這裡將要執行的方法,是在 RendererBinding
的 initInstances()
中新增的 _handlePersistentFrameCallback
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_mouseTracker.schedulePostFrameCheck();
}
複製程式碼
最後,會呼叫到 WidgetBinding
的 drawFrame()
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
的銷燬的
不過這裡不將它作為入口,記得我們之前 Element
的 updateChild(...)
方法裡面,有兩處會對 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
之後呢,就會在WidgetsBinding
的 drawFrame()
被觸發時呼叫 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 Element
的 unmount()
來進行銷燬
在 _InactiveElements
中還有一個 remove
方法,介紹 unmount()
前我們先把這個給看了
_InactiveElements -> remove()
void remove(Element element) {
...
_elements.remove(element);
...
}
複製程式碼
這裡就是從 Set
中移除傳入的 Element
物件,在之前的 _unmountAll()
中會通過 clear()
清楚 Set
內所有的元素,那為什麼這裡會有這樣一種情況呢?之前我們在 Element
的 inflateWidget(...)
中提到過,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
的四個生命週期中可能會出現 active
和 inactive
切換的情況。
那麼在哪種情況下會觸發這種複用呢?其實很簡單:當處於不同深度的同一型別 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;
}
複製程式碼
在這裡呼叫了 State
的 dispose()
方法,並且在之後清理了 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(...),但是邏輯都是一樣的,可以放心食用