Flutter Framework
視圖樹的創建與管理機制、布局、渲染核心框架
視圖樹
- Widget => 為Element提供配置信息
- Element => Flutter創建Element的可見樹, 同時持有Widget和RenderObject
- RenderObject => 渲染樹中的一個對象
渲染機制
調用runApp(rootWidget),將rootWidget傳給rootElement,做為rootElement的子節點,生成Element樹,由Element樹生成Render樹
runApp(首次執行)
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
runApp(rootWidget) => attachRootWidget(rootWidget) => attachToRenderTree() => element.mount() => _rebuild() => updateChild()
1. WidgetsFlutterBinding
WidgetsFlutterBinding混入了不少的其他的Binding
- BindingBase 那些單一服務的混入類的基類
- GestureBinding framework手勢子系統的綁定,處理用戶輸入事件
- ServicesBinding 接受平臺的消息將他們轉換成二進制消息,用于平臺與flutter的通信
- SchedulerBinding 調度系統,用于調用Transient callbacks(Window.onBeginFrame的回調)、Persistent callbacks(Window.onDrawFrame的回調)、Post-frame callbacks(在Frame結束時只會被調用一次,調用后會被系統移除,在Persistent callbacks后Window.onDrawFrame回調返回之前執行)
- PaintingBinding 繪制庫的綁定,主要處理圖片緩存
- SemanticsBinding 語義化層與Flutter engine的橋梁,主要是輔助功能的底層支持
- RendererBinding 渲染樹與Flutter engine的橋梁
- WidgetsBinding Widget層與Flutter engine的橋梁
持有BuildOwner、PipelineOwner
-
BuildOwner
BuildOwner是Widget framework的管理類, 該類跟蹤哪些小部件需要重新構建,并處理應用于整個小部件樹的其他任務,比如管理樹的非活動元素列表
-
PipelineOwner
管理真正需要繪制的View, 對RenderObjectTree中發生變化節點的進行flush操作, 最后交給底層引擎渲染
2. attachRootWidget
-
1.attachRootWidget(app) 方法創建了Root[Widget](也就是 RenderObjectToWidgetAdapter)
void attachRootWidget(Widget rootWidget) { _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget ).attachToRenderTree(buildOwner, renderViewElement); }
-
2.緊接著調用attachToRenderTree方法創建了 Root[Element]
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) { if (element == null) { owner.lockState(() { element = createElement(); //創建rootElement element.assignOwner(owner); //綁定BuildOwner }); owner.buildScope(element, () { //子widget的初始化從這里開始 element.mount(null, null); // 初始化子Widget前,先執行rootElement的mount方法 }); } else { ... } return element; }
-
3.Root[Element]嘗試調用mount方法將自己掛載到父Element上,因為自己就是root了,所以沒有父Element,掛空了
owner.buildScope(element, () { //子widget的初始化從這里開始 element.mount(null, null); // 初始化子Widget前,先執行rootElement的mount方法 });
-
4.mount的過程中會調用Widget的createRenderObject,創建了 Root[RenderObject]
void mount(Element parent, dynamic newSlot) { _parent = parent; //持有父Element的引用 _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; //每個Element的buildOwner,都來自父類的BuildOwner, 這樣可以保證一個ElementTree,只由一個BuildOwner來維護 ... } @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _renderObject = widget.createRenderObject(this); attachRenderObject(newSlot); _dirty = false; }
5.我們將app作為參數傳給了Root[Widget](也就是 RenderObjectToWidgetAdapter),app[Widget]也就成了為root[Widget]的child[Widget]
6.調用owner.buildScope,開始執行子Tree的創建以及掛載(與更新流程一致, 見更新)
-
7.調用createElement方法創建出Child[Element]
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; }
8.調用Element的mount方法,將自己掛載到Root[Element]上,形成一棵樹
9.掛載的同時,調用widget.createRenderObject,創建Child[RenderObject]
-
10.創建完成后,調用attachRenderObject,完成和Root[RenderObject]的鏈接
@override void attachRenderObject(dynamic newSlot) { _slot = newSlot; _ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot); final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement(); if (parentDataElement != null) _updateParentData(parentDataElement.widget); }
RenderObject與父RenderObject的掛載稍微復雜了點。每一個Widget都有一個對應的Element,但Element不一定會有對應的RenderObject。(因為有一些Element是不用來做頁面顯示的, 像StatelessWidget=>StatelessElement沒有對應的RenderObject)所以你的父Element并不一有RenderObject,這個時候就需要向上查找。
RenderObjectElement _findAncestorRenderObjectElement() { Element ancestor = _parent; while (ancestor != null && ancestor is! RenderObjectElement) ancestor = ancestor._parent; return ancestor; }
find方法在向上遍歷Element,直到找到RenderObjectElement,RenderObjectElement肯定是有對應的RenderObject了,這個時候在進行RenderObject子父間的掛載。
3. scheduleWarmUpFrame
安排一個幀盡快運行, 這在應用程序啟動期間使用,以便第一個幀(可能非常昂貴)可以多運行幾毫秒。(個人理解是為了實現第一次頁面渲染可以調用到 => drawFrame)
setState(更新)
@protected
void setState(VoidCallback fn) {
...
_element.markNeedsBuild();
}
1.Element標記自身為dirty,并通知buildOwner處理
void markNeedsBuild() {
...
_dirty = true; // 標記自身為dirty
owner.scheduleBuildFor(this); // 通知buildOwner處理
}
2.buildOwner將element添加到集合_dirtyElements中,并通知ui.window安排新的一幀
buildOwner會將所有dirty的Element添加到_dirtyElements當中,等待下一幀繪制時集中處理。
還會調用ui.window.scheduleFrame();通知底層渲染引擎安排新的一幀處理。
void scheduleBuildFor(Element element) {
...
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
3.底層引擎最終回調到Dart層, 完成計算渲染回收
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
}
...
}
3.1 執行buildOwner的buildScope方法
void buildScope(Element context, [VoidCallback callback]) {
...
try {
...
//1.排序
_dirtyElements.sort(Element._sort);
...
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
//2.遍歷rebuild
_dirtyElements[index].rebuild();
} catch (e, stack) {
}
index += 1;
}
} finally {
for (Element element in _dirtyElements) {
element._inDirtyList = false;
}
//3.清空
_dirtyElements.clear();
...
}
}
3.1.1 按照Element的深度從小到大,對_dirtyElements進行排序
3.1.2 遍歷執行_dirtyElements當中element的rebuild方法
遍歷執行的過程中,也有可能會有新的element被加入到_dirtyElements集合中,此時會根據dirtyElements集合的長度判斷是否有新的元素進來了,如果有,就重新排序。
void rebuild() {
if (!_active || !_dirty)
return;
Element debugPreviousBuildTarget;
performRebuild();
}
element的rebuild方法最終會調用performRebuild(),而performRebuild()不同的Element有不同的實現
執行performRebuild()
performRebuild()不同的Element有不同的實現,我們暫時只看最常用的兩個Element:
-
ComponentElement,是StatefulWidget和StatelessElement的父類
void performRebuild() { Widget built; try { built = build(); } ... try { _child = updateChild(_child, built, slot); } ... }
build()執行我們復寫的StatefulWidget的state的build方法, 拿到子Widget, 交給updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) { ... //1. 如果newWidget是null, 說明刪除控件, Element被刪除 if (newWidget == null) { if (child != null) deactivateChild(child); return null; } if (child != null) { //2. 如果新舊控件相同, 說明Widget復用了, 判斷位置是否相同, 不相同更新 if (child.widget == newWidget) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); return child; } //3. 判斷key值和運行時類型(runtimeType)是否相等, 都相同才可以更新, 更新并返回Element(這個時候應該是Widget變了, 但是還是同類型的Widget) if (Widget.canUpdate(child.widget, newWidget)) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); child.update(newWidget);// return child; } deactivateChild(child); } //4. 如果上面的條件都不滿足, 創建新的Element return inflateWidget(newWidget, newSlot); }
1.如果newWidget是null, 說明刪除控件, Element被刪除
2.如果新舊控件相同, 說明Widget復用了, 判斷位置是否相同, 不相同更新
-
3.判斷key值和運行時類型(runtimeType)是否相等, 都相同才可以更新, 更新并返回Element(這個時候應該是Widget變了, 但是還是同類型的Widget)
static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; }
child.update(newWidget);方法, 會根據newWidget的類型執行不同的update方法, 例如:
-
Column是MultiChildRenderObjectWidget類型的, 就會執行下面的方法:
@override void update(MultiChildRenderObjectWidget newWidget) { super.update(newWidget); _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren); _forgottenChildren.clear(); }
由于Column里面的孩子是children類型(MultiChildRenderObjectWidget), 有多個, 所以對比算法采用updateChildren, 返回新的Element
-
Container是StatelessWidget類型的, 所以他執行StatelessWidget的update方法:
@override void update(StatelessWidget newWidget) { super.update(newWidget); _dirty = true; rebuild(); }
-
Scaffold是StatefulWidget類型的, 所以執行:
@override void update(StatefulWidget newWidget) { super.update(newWidget); final StatefulWidget oldWidget = _state._widget; _dirty = true; _state._widget = widget; try { _debugSetAllowIgnoredCallsToMarkNeedsBuild(true); final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic; } finally { _debugSetAllowIgnoredCallsToMarkNeedsBuild(false); } rebuild(); }
如果是StatelessWidget/StatefulWidget類型, 則繼續執行下一級的對比, 以此類推.(child.update => rebuild => performRebuild(), rebuild就是上面那個方法)
如果是MultiChildRenderObjectWidget類型, 則updateChildren里面會進行List對比算法, 每一個item也會調用updateChild()方法, 進行計算, 詳細過程見最后
* 4.如果上面的條件都不滿足, 創建新的Element首先會嘗試通過GlobalKey去查找可復用的Element,復用失敗就調用Widget的方法創建新的Element,然后調用mount方法,將自己掛載到父Element上去,會在這個方法里創建新的RenderObject。
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; }
-
-
RenderObjectElement,是有渲染功能的Element的父類
與ComponentElement的不同之處在于,沒有去build,而是調用了updateRenderObject方法更新RenderObject。
@override void performRebuild() { widget.updateRenderObject(this, renderObject); _dirty = false; }
他代表的具有自己渲染功能的一類Widget(Text,沒有child的)
3.1.3 遍歷結束之后,清空dirtyElements集合
3.2 執行WidgetsBinding 的drawFrame (), PipelineOwner對RenderObject管理, 更新頁面
@protected
void drawFrame() {
pipelineOwner.flushLayout(); //布局需要被布局的RenderObject
pipelineOwner.flushCompositingBits(); // 判斷layer是否變化
pipelineOwner.flushPaint(); //繪制需要被繪制的RenderObject
renderView.compositeFrame(); // this sends the bits to the GPU 將畫好的layer傳給engine繪制
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS. 一些語義場景需要
}
3.3 執行了buildOwner.finalizeTree()清理
void finalizeTree() {
Timeline.startSync('Finalize tree', arguments: timelineWhitelistArguments);
try {
lockState(() {
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
});
...
} catch (e, stack) {
_debugReportException('while finalizing the widget tree', e, stack);
} finally {
Timeline.finishSync();
}
}
所有沒用的element都調用了deactivateChild方法進行回收
void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
也就在這里將被廢棄的element添加到了_inactiveElements當中。
另外在廢棄element之后,調用inflateWidget創建新的element時,還調用了_retakeInactiveElement嘗試通過GlobalKey復用element,此時的復用池也是在_inactiveElements當中。
如果你沒有在一幀里通過GlobeKey完成Element的復用,_inactiveElements在最后將被清空,就沒辦法在復用了。
updateChildren詳細過程
@protected
List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element> forgottenChildren }) {
Element replaceWithNullIfForgotten(Element child) {
return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
}
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
final List<Element> newChildren = oldChildren.length == newWidgets.length ?
oldChildren : List<Element>(newWidgets.length);
Element previousChild;
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
final Widget newWidget = newWidgets[newChildrenTop];
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// Scan the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
final Widget newWidget = newWidgets[newChildrenBottom];
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
}
// Scan the old children in the middle of the list.
final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
Map<Key, Element> oldKeyedChildren;
if (haveOldChildren) {
oldKeyedChildren = <Key, Element>{};
while (oldChildrenTop <= oldChildrenBottom) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
if (oldChild != null) {
if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key] = oldChild;
else
deactivateChild(oldChild);
}
oldChildrenTop += 1;
}
}
// Update the middle of the list.
while (newChildrenTop <= newChildrenBottom) {
Element oldChild;
final Widget newWidget = newWidgets[newChildrenTop];
if (haveOldChildren) {
final Key key = newWidget.key;
if (key != null) {
oldChild = oldKeyedChildren[key];
if (oldChild != null) {
if (Widget.canUpdate(oldChild.widget, newWidget)) {
// we found a match!
// remove it from oldKeyedChildren so we don't unsync it later
oldKeyedChildren.remove(key);
} else {
// Not a match, let's pretend we didn't see it for now.
oldChild = null;
}
}
}
}
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
}
// We've scanned the whole list.
newChildrenBottom = newWidgets.length - 1;
oldChildrenBottom = oldChildren.length - 1;
// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = oldChildren[oldChildrenTop];
final Widget newWidget = newWidgets[newChildrenTop];
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// Clean up any of the remaining middle nodes from the old list.
if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
for (Element oldChild in oldKeyedChildren.values) {
if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
deactivateChild(oldChild);
}
}
return newChildren;
}
- 從前往后, 依次判斷oldChild是否存在(如果是GlobalKey, 則當做不存在處理), 并且新舊節點是否相同, 皆是則執行updateChild對比里面的節點, 返回Element, 賦值給newChildren, 記錄下一個沒有對比的新舊節點序號; 如果不同, 則跳出循環.
- 從后往前, 依次判斷oldChild是否存在, 并且新舊節點是否相同, 皆是則記錄下一個沒有對比的新舊節點序號; 如果不同, 則跳出循環.
- 判斷是否還有未比較的oldChildren, 如果有, 則獲取到所有含有key的節點, 存入oldKeyedChildren, 不包括GlobalKey.
- 循環未比較的newChildren, 是否存在key, 存在則對比key是否在oldKeyedChildren中存在, 存在則移除oldKeyedChildren中對于的key, 最后調用updateChild返回Element, 賦值給newChildren.
- 再次循環, 把后面具有相同key的數據賦值給newChildren.
- 刪除多余的oldChildren.