引言
Flutter開發中三棵樹的重要性不言而喻,了解其原理有助于我們開發出性能更優的App,此文主要從原始碼角度介紹Element樹的管理類BuildOwner,
是什么?
BuildOwner是element的管理類,主要負責dirtyElement、inactiveElement、globalkey關聯的element的管理,
final _InactiveElements _inactiveElements = _InactiveElements();//存盤inactiveElement,
final List<Element> _dirtyElements = <Element>[];//存盤dirtyElement,就是那些需要重建的element,
final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};//存盤所有有globalKey的element,
在哪創建的?
BuildOwner是全域唯一的,當然也可以創建一個buildOwner用來管理離屏的widget,其在widgetsBinding的init方法中創建,并在runApp中的attachRootWidget方法中賦值給root element,子element在其mount方法中可以獲取到parent的BuildOwner,達到全域使用唯一BuildOwner的效果,
//WidgetsBinding類
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
_buildOwner = BuildOwner();//創建buildOwner
buildOwner!.onBuildScheduled = _handleBuildScheduled;//賦值buildScheduled方法
// ...
}
}
//Element類的mount方法
void mount(Element? parent, Object? newSlot) {
//...
_parent = parent;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
//當parent為null時,這個element肯定是root element,
//root element的buildOwner是在runApp中呼叫assignOwner方法賦值的,
_owner = parent.owner;//與parent公用一個buildOwner
}
//...
}
dirtyElements的管理
添加
添加操作主要用的是BuildOwner的scheduleBuildFor方法,當你使用State類時,一個完整的鏈條如下:
//StatfuleWidget的State類中呼叫setState方法
void setState(VoidCallback fn) {
final Object? result = fn() as dynamic;
_element!.markNeedsBuild();
}
?
//Element里的markNeedsBuild方法
void markNeedsBuild() {
//如果不是活躍狀態,直接回傳,
if (_lifecycleState != _ElementLifecycle.active)
return;
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
?
//BuildOwner里的scheduleBuildFor方法
void scheduleBuildFor(Element element) {
if (element._inDirtyList) {
_dirtyElementsNeedsResorting = true;
return;
}
...
_dirtyElements.add(element);//加入到dirtyElement串列里
element._inDirtyList = true;//將element的inDirtyList置為true
}
處理
真正處理的地方是在BuilOwner的buildScope方法里,framework在每次呼叫drawFrame時都會呼叫此方法重新構建dirtyElement,可以參考下WidgetsBinding的drawFrame方法,在runApp一開始啟動時,也會呼叫此方法完成element tree的mount操作,具體可以參考
RenderObjectToWidgetAdapter的attachToRenderTree方法,
void buildScope(Element context, [ VoidCallback? callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
try {
//先執行回呼方法
if (callback != null) {
try {
callback();
} finally {
}
}
//采用深度排序,排序的結果是parent在child的前面
_dirtyElements.sort(Element._sort);
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
final Element element = _dirtyElements[index];
try {
// 依次呼叫element的rebuild方法,呼叫完rebuild方法后,
// element的dirty屬性會被置為false
element.rebuild();
} catch (e, stack) {
}
index += 1;
// 標記 2
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
_dirtyElements.sort(Element._sort);
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
}
} finally {
//最后將dirtyElements清空,并將element的inDirtyList屬性置為false
for (final Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
}
}
這個方法會先執行方法入參的回呼,回呼執行完畢后對dirty element串列根據element的depth屬性進行排序,depth越低越靠前,也就說parent肯定在child前面,然后按照這個順序依次呼叫element的rebuild方法,為什么要這么排序呢?如果是先執行child的rebuild方法,當執行其parent的rebuild方法時,內部會直接呼叫updateChild方法導致child重新build,并不會判斷child是否是dirty,而當parent執行完rebuild方法后,其child的dirty會被置為false,再次呼叫child的rebuild方法時,發現child的dirty為false,那么就直接回傳,所以這么排序的目的是防止child多次執行build操作,下面是rebuild的原始碼,
void rebuild() {
if (_lifecycleState != _ElementLifecycle.active || !_dirty)//如果dirty為false,直接回傳,不再執行build操作,
return;
performRebuild();
}
當串列中的所有element都執行完rebuild方法后,就會將其清空,并將dirtyElement的inDirtyList置為false,對應于原始碼的finally中的代碼,
看原始碼中標記2的地方,dirtyCount不應該等于dirtyElements.length嗎?為什么會小于呢?下面詳細解釋下:
執行element.rebuild方法時,內部還會呼叫updateChild方法用來更新child,在一些場景下updateChild方法會呼叫inflateWidget來創建新的element(會在element里詳細介紹),如果newWidget的key為GlobalKey,這個GlobalKey也有對應的element,并且Widgets.canUpdate()回傳true,那么就呼叫其_activateWithParent方法,
//Element的inflateWidget方法
Element inflateWidget(Widget newWidget, Object? newSlot) {
final Key? key = newWidget.key;
if (key is GlobalKey) {
//重新設定此element的位置,配合下面的代碼完成了key為GlobalKey的element在tree上的移動操作,
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
//呼叫element的activeWithParent方法
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild!;
}
}
//...
}
?
//Element的retakeInactiveElement方法
Element? _retakeInactiveElement(GlobalKey key, Widget newWidget) {
//有對應的element
final Element? element = key._currentElement;
if (element == null)
return null;
//如果Widget.canUpdate的結果是false就直接回傳null,
if (!Widget.canUpdate(element.widget, newWidget))
return null;
final Element? parent = element._parent;
//脫離和原來parent的關系,將其加入到_inactiveElements串列里
if (parent != null) {
parent.forgetChild(element);
parent.deactivateChild(element);
}
//將上一步加入到inactiveElements串列里的element再從中remove掉
owner!._inactiveElements.remove(element);
return element;
}
?
//Element的activateWithParent方法
void _activateWithParent(Element parent, Object? newSlot) {
_parent = parent;
//更新depth,保證其depth一定比parent要深,最小為parent.depth+1
_updateDepth(_parent!.depth);
//呼叫element及其child的active方法
_activateRecursively(this);
attachRenderObject(newSlot);
}
?
//Element的updateDepth方法
void _updateDepth(int parentDepth) {
final int expectedDepth = parentDepth + 1;
if (_depth < expectedDepth) {
_depth = expectedDepth;
visitChildren((Element child) {
child._updateDepth(expectedDepth);
});
}
}
?
//Element的activateRecursively方法
static void _activateRecursively(Element element) {
//呼叫自己的activate方法
element.activate();
//呼叫cihldren的activate方法
element.visitChildren(_activateRecursively);
}
最終呼叫到了element的activate方法:
void activate() {
//...
if (_dirty)
owner!.scheduleBuildFor(this);
//...
}
看到沒,如果重新撈起來的element是dirty的,那么會再次呼叫scheduleBuildFor方法,將此element加入到dirtyElement串列里面,這也就是為什么標記2處dirtyCount會小于dirtyElements.length的原因,此時,因為有新element加入到dirtyElement串列里,所以要重新sort,
總結下,buildScope方法主要是對dirtyElements串列中的每一個element執行了rebuild操作,rebuild會呼叫updateChild方法,當需要重新呼叫inflateWidget創建新element時,如果child使用了GlobalKey并且GlobalKey對應的element是dirty狀態的,那么就會將其加入到dirtyElements串列中,導致dirtyElements數量的變化,
inactiveElements的管理
inactiveElements主要用來管理非活躍狀態的element,特別是可以用來處理key為GlobalKey的element的move操作,其實inactiveElements是一個物件,內部維護了一個Set以及用于debug模式下asset判斷的locked屬性,當然還有其他方法,類定義如下:
class _InactiveElements {
bool _locked = false;
final Set<Element> _elements = HashSet<Element>();
.....
}
添加
在element的deactivateChild方法里完成了inactiveElement的元素添加操作,
//Element類
void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner!._inactiveElements.add(child); // add 操作
}
?
//InactiveElements類的add方法
void add(Element element) {
assert(!_locked);
if (element._lifecycleState == _ElementLifecycle.active)
_deactivateRecursively(element);//遞回呼叫element的child的deactivate方法
_elements.add(element);
}
?
//InactiveElements類的_deactivateRecursively方法,呼叫element的deactive方法
static void _deactivateRecursively(Element element) {
element.deactivate();
element.visitChildren(_deactivateRecursively);
}
deactiveChild呼叫的兩個重要時機:
- updateChild方法里,介紹element時會詳細介紹,
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
....
}
- _retakeInactiveElement方法里(inflateWidget方法里呼叫的),上面介紹過,主要是用于擁有GlobaleKey的element在tree上的移動操作,
清空
其清空操作是在BuildOwner里的finalizeTree方法里面,此方法里會呼叫element的unmount方法,原始碼如下,
//BuildOwner類
void finalizeTree() {
lockState(_inactiveElements._unmountAll);
}
?
//InactiveElement類
void _unmountAll() {
_locked = true;//debug模式下的判斷屬性
final List<Element> elements = _elements.toList()..sort(Element._sort);
_elements.clear();//源list清空
try {
//反轉后呼叫unmount方法,也就是說先呼叫的child的unmount方法,然后呼叫的parent的unmount方法,
elements.reversed.forEach(_unmount);
} finally {
assert(_elements.isEmpty);
_locked = false;
}
}
?
//InactiveElement類
void _unmount(Element element) {
//先unmount children,再unmount自己
element.visitChildren((Element child) {
_unmount(child);
});
element.unmount();
}
需要注意的是:
-
unmount時會將串列按著深度優先排序,也就說先unmount depth大的,再unmount depth小的,
-
真正執行unmount操作時,也是先unmount chidlren 然后unmount自己,
-
每次渲染完一幀后,都會呼叫finalizeTree方法,具體的方法是WidgetsBinding的drawFrame方法中,
key為GloablKey的Element的管理
主要有兩個方法,一個方法用于注冊,一個方法用于解注冊,在element的mount方法里,判斷是否用的GlobalKey,如果是的話呼叫注冊方法,在element的unmount方法里呼叫解注冊方法,
void _registerGlobalKey(GlobalKey key, Element element) {
_globalKeyRegistry[key] = element;
}
?
void _unregisterGlobalKey(GlobalKey key, Element element) {
if (_globalKeyRegistry[key] == element)
_globalKeyRegistry.remove(key);
}
總結
BuildOwner是全域唯一的,在WidgetsBinding的init方法中創建,內部主要用來管理dirtyElements、inactiveElements以及key為GlobalKey的element,
-
在BuildOwner的scheduleBuildFor方法里會向dirtyElements里添加dirty element,在buildScope方法里會呼叫每一個dirty element的rebuild方法,執行rebuild前會對dirty elements進行按深度排序,先執行parent后執行child,目的是為了避免child的build方法被重復執行,在繪制每一幀時(WidgetsBinding的drawFrame方法),會呼叫buildScope方法,
-
inactiveElements并不是一個串列,而是一個類,里面用set集合來保存inactive狀態的element,還實作了一些此集合的操作方法,比如add操作等等,
-
當呼叫element的updateChild方法時,某些場景下會呼叫deactiveChild方法,會將element添加到inaciveElements里面,并呼叫element的deactive方法,使其變為deactive狀態;呼叫updateChild方法時,在某些場景下會呼叫inflateWidget方法用來創建新element,如果此element的key是GlobalKey,并且此key有對應的element、widget.canUpdate回傳true,那么就會將此element與原parent脫離關系(呼叫的是parent的forgetChild方法),并且將其從inactiveElements中remove掉,完成了在tree上的move操作,
-
當繪制完一幀時(WidgetsBinding的drawFrame方法),會呼叫BuildOwner的finalizeTree方法用來清空inactiveElements,并且呼叫每一個inactive element的unmount方法,
-
globalKey的管理比較簡單,用一個map來記錄globalKey和element的對應關系,在element的mount方法里完成注冊操作,unmount方法里完成解注冊方法,
作者:京東物流 沈明亮
來源:京東云開發者社區
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/553909.html
標籤:其他
下一篇:返回列表