本文不是一篇介紹Flutter入門知識,主要包括Flutter的視圖管理和渲染機(jī)制。如果在閱讀中有任何問題,麻煩Q442953298。歡迎喜歡Flutter的同學(xué)交流
Flutter中的樹
Widget樹
對Flutter有一定了解的同學(xué),可能知道Flutter存在Everything’s a Widget的說法。在我們構(gòu)建一個FlutterApp的時候,從main函數(shù)開始就調(diào)起了runApp函數(shù)。而runApp傳入的參數(shù)就是一個Widget。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page')),
);
}
}
MyApp -> MaterialApp -> MyHomePage -> ...
組成了一個Widget樹。
一個Widget的簡單結(jié)構(gòu)如下
// DiagnosticableTree 是一個調(diào)試類
// Wiget可以說是表現(xiàn)層的基類
abstract class Widget extends DiagnosticableTree {
final Key key;
@protected
Element createElement();
}
而在StatelessWidget 和 State類中都實現(xiàn)了
Widget build(BuildContext context) {}
通過build方法父Widget可以訪問到子節(jié)點(diǎn)的Widget
幾個問題
- Stateless的Build方法由它本身實現(xiàn),StatefulWidget的Build方法由State類實現(xiàn)。這種設(shè)計是出于對性能和數(shù)據(jù)-狀態(tài)的響應(yīng)等多方面的考慮。
- 并不是所有的部件都需要關(guān)心數(shù)據(jù)/狀態(tài)的變化,比如布局類、文本、圖片等。他們對于狀態(tài)的變化應(yīng)該是無感知的。所以他們直接調(diào)用本身的Build方法來就可以輕松構(gòu)造表現(xiàn)層。
- StatefulWidget是有狀態(tài)的Widget,這種說法其實是存在問題的。StatefulWidget繼承自Widget,而Widget被@immutable標(biāo)記,所以StatefulWidget本身也是不可變的。他之所以能夠響應(yīng)狀態(tài)的變化,完全依賴于State類的實現(xiàn)。
- 在Wiget樹更新的時候,必然伴隨著部件的創(chuàng)建和銷毀.如果將狀態(tài)交付給其他類管理,則可以在響應(yīng)狀態(tài)時輕松的改變Widget樹而不影響數(shù)據(jù)層的維護(hù)。這樣設(shè)計是數(shù)據(jù)層和表現(xiàn)層分離的良好體現(xiàn)。
- Widget類由@immutable標(biāo)記,狀態(tài)不可變。
剛才我們說Widget的可變狀態(tài)都是由State類進(jìn)行管理,因此Widget自身的狀態(tài)其實都是靜態(tài)的,Widget從構(gòu)造到被釋放,自身的狀態(tài)都應(yīng)該保持不變。
Element樹
Element - An instantiation of a [Widget] at a particular location in the tree.
一個在樹中特定位置的Widget實例。
我們討論Widget的時候提到Widget實現(xiàn)了一個方法:
@protected
Element createElement();
在StatelessWidget和StatefulWidget重寫了這個方法
abstract class StatelessWidget extends Widget {
StatelessElement createElement() => StatelessElement(this);
}
abstract class StatefulWidget extends Widget {
StatefulElement createElement() => StatefulElement(this);
}
StatelessElement構(gòu)造方法只是簡單的將Widget傳給父類構(gòu)造方法
StatelessElement(StatelessWidget widget) : super(widget);
StatefulElement的構(gòu)造方法則不太相同
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
_state._element = this;
_state._widget = widget;
}
// 1. 調(diào)用傳入的widget createState 創(chuàng)建了State對象
// 2. 賦值給自己的_state變量(可用get方法訪問)
// 3. state對象分別持有了自身 和 傳入的widget對象。
StatelessElement 和StatefulElement都繼承自ComponentElement。ComponentElement聲明了一個抽象方法
Widget build();
StatelessElement和StatefulElement都重寫了build方法
// stateless實現(xiàn)
@override
Widget build() => widget.build(this);
// stateful實現(xiàn)
@override
Widget build() => state.build(this);
根據(jù)上面的實現(xiàn)我們可以簡單的得出一個結(jié)論:
Widget的build都是由Element類的build方法觸發(fā)。
Widget樹的構(gòu)建就和Element樹就有了密不可分的關(guān)系,他們到底是怎么實現(xiàn)的。
runApp()以后發(fā)生了什么
-
runApp(Widget app)
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..attachRootWidget(app) ..scheduleWarmUpFrame(); // WidgetsFlutterBinding.ensureInitialized() 創(chuàng)建了一個WidgetsFlutterBinding對象,他的主要任務(wù)就是協(xié)調(diào)Framework層和Application層的交互、進(jìn)程、渲染等底層任務(wù) } -
attachRootWidget(app)
// WidgetsFlutterBinding.dart void attachRootWidget(Widget rootWidget) { _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget ).attachToRenderTree(buildOwner, renderViewElement); } // 首先RenderObjectToWidgetAdapter 創(chuàng)建了 RenderObjectToWidgetAdapter對象 // 接著attachToRenderTree 則生成了renderViewElement // renderViewElement 通過RenderObjectToWidgetAdapter的createElement創(chuàng)建,它繼承自RenderObjectToWidgetElement RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this); // RenderObjectToWidgetElement的widget字段持有了RenderObjectToWidgetAdapter對象 WidgetsFlutterBinding 持有了Element樹的根節(jié)點(diǎn)RenderObjectToWidgetElement;
RenderObjectToWidgetElement持有了Widget樹的根節(jié)點(diǎn) RenderObjectToWidgetAdapter
-
關(guān)于RenderObjectToWidgetAdapter
RenderObjectToWidgetAdapter({ this.child, this.container, this.debugShortDescription }) : super(key: GlobalObjectKey(container)); // 在第一第二塊的attachRootWidget方法里我們將app當(dāng)做child參數(shù)傳給了RenderObjectToWidgetAdapter RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget ) final Widget child; // 其中還有個參數(shù)需要被注意: container的參數(shù)傳入的變量是renderView。 renderView 在WidgetsFlutterBinding的構(gòu)造方法里被創(chuàng)建,他S是后續(xù)我們會講到的RenderObject樹的根節(jié)點(diǎn) 我們可以通過 WidgetsFlutterBinding.renderView或者WidgetsFlutterBinding.pipelineOwner.rootNode訪問到它 -
關(guān)于RenderObjectToWidgetElement
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) { if (element == null) { owner.lockState(() { element = createElement(); element.assignOwner(owner); }); owner.buildScope(element, () { element.mount(null, null); }); } else { element._newWidget = this; element.markNeedsBuild(); } return element; } // 在attachToRenderTree方法里會判斷element參數(shù)是否為null如果為null則調(diào)用CreateElement,否則更新已存在的element的widget -
element.mount(null, null);
上面我們已經(jīng)提到WidgetsFlutterBinding -> element根節(jié)點(diǎn) RenderObjectToWidgetElement -> widget根節(jié)點(diǎn)RenderObjectToWidgetAdapter
element.mount 方法則開啟了Element樹和Widget樹構(gòu)建的大門
// RenderObjectToWidgetElement mount 掛載 void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _rebuild(); } // 我們先不關(guān)心 super.mount在RenderObjectToWidgetElement的父類做了什么 // _rebuild方法 void _rebuild() { try { _child = updateChild(_child, widget.child, _rootChildSlot); // _child 作為參數(shù)傳入第一次為null // widget.child // widget是RenderObjectToWidgetAdapter // RenderObjectToWidgetAdapter.child 則是我們最初runApp傳入的appWidget } catch (exception, stack) { // 拋出我們在頁面上看見的紅色背景黃字警告頁面 } }我們終于看到我們最初傳入的AppWidget有了用武之地。
-
updateChild方法 是構(gòu)建和更新Widget樹和Element樹的靈魂
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) { // 此處為更新slot代碼 return child; } if (Widget.canUpdate(child.widget, newWidget)) { // 此處為更新slot代碼 child.update(newWidget); return child; } deactivateChild(child); } return inflateWidget(newWidget, newSlot); }讓我們用通俗的語言來描述這段代碼的邏輯
1.如果傳入的newWidget為空而child不為空,則表明這是一次更新樹的操作。newWidget為空表示我們在移除了一個Widget節(jié)點(diǎn)和它子Widget樹,因此需要我們移除Element對應(yīng)的child節(jié)點(diǎn)2.如果child不為空(表明是更新操作):
2.1如果widget和新傳入的newWidget是同一個對象,則說明這個節(jié)點(diǎn)沒有任何變化返回child即可。(此處表明該節(jié)點(diǎn)可能是InheritWidget或者GlobalKey做了標(biāo)記,此處后面的文章分析)
2.2.如果widget與新傳入的newWidget不是同一塊內(nèi)存,但是Widget.canUpdate(new,old) 的 runtimeType以及key都相同,則表明這個widget的深度和類型都沒有發(fā)生改變,此時child這個Element對象可以繼續(xù)拿來復(fù)用,只需要將舊的widget更新為新的即可。(此處在更新操作里最常見,比如setState后,build方法沒有改變child節(jié)點(diǎn)的地方,則該節(jié)點(diǎn)下的所有widget都會走此處)
2.3如果上面的情況都不滿足,比如widget的節(jié)點(diǎn)雖然沒有變化但是key發(fā)生了變化,再比如widget節(jié)點(diǎn)的類型由Text轉(zhuǎn)換為Image類型,則這個節(jié)點(diǎn)的Element不能再復(fù)用,此時需要我們先移除Element節(jié)點(diǎn)
3.當(dāng)child為空,或者widget的key或類型發(fā)生了變化,就會走到這里,此時會調(diào)用inflateWidget方法,將新widget膨脹為一個Element。
此處用膨脹其實是想說明Element的作用更加底層和重要
-
inflateWidget魔法-一種遞歸創(chuàng)建Widget樹和Element的方式
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) { return updatedChild; } } final Element newChild = newWidget.createElement(); newChild.mount(this, newSlot); return newChild; }Globalkey此處的邏輯先不表,后續(xù)系列再主要介紹,當(dāng)我們重新create了一個newChild的Element后,Element調(diào)用了自身的mount方法。
此處有了一個大的設(shè)想,StatelessElement和StatefulElement的mout方法將會把他們build方法獲得的widget繼續(xù)updateChild-inflateWidget方式傳遞下去因此去做了驗證 -
ComponentElement
StatelessElement 和 StatefulElement都繼承自ComponentElement。在ComponentElement的mount方法里存在void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _firstBuild(); } // _firstBuild() void _firstBuild() { rebuild(); } // rebuild在element中實現(xiàn),最后會調(diào)用performRebuild //performRebuild 在ComponentElement中實現(xiàn) void performRebuild() { Widget built; try { built = build(); } catch (e, stack) { //error 處理 } finally { _dirty = false; } try { _child = updateChild(_child, built, slot); assert(_child != null); } catch (e, stack) { //error 處理 } }最終在performRebuild方法里知道了我們需要的東西。element會調(diào)用自身的build實現(xiàn),獲取當(dāng)前widget節(jié)點(diǎn)下的build子節(jié)點(diǎn)。這個build方法創(chuàng)建的子節(jié)點(diǎn)會在調(diào)用updateChild方法的時候傳入,因此形成了遞歸,一層一層的去構(gòu)建或者說更新我們的element樹,和Widget樹。也就完整的完成了Element樹和Widget樹的調(diào)用。
SetState以后發(fā)生了什么
我們說剛才的流程是一個樹創(chuàng)建的流程,那么樹的更新是怎么開始的了?
-
State. setState
_element.markNeedsBuild();
-
Element. didChangeDependencies
markNeedsBuild();
這兩者都會引發(fā)markNeedsBuild()
void markNeedsBuild() {
_dirty = true;
owner.scheduleBuildFor(this);
}
// markNeedsBuild則會標(biāo)記element被污染
scheduleBuildFor方法會將element add -> owner._dirtyElements里
并且標(biāo)記element的_inDirtyList 為 true
然后發(fā)起一個等待Vsync信號渲染事件,當(dāng)下一個Vsync信號來臨時,會調(diào)用DrawFrame方法
此時buildScope方法重新被調(diào)用
你可能沒有記住這個方法,你可以去上面查找一下attachWidgetTree 沒錯在第五節(jié)。
在attachToRenderTree方法里,如果element是新建的也會調(diào)用這個方法,這個方法的目的就是清空owner的_dirtyElements的element。
清空的方式就是調(diào)用他們的rebuild方法
_dirtyElements[index].rebuild();
因此回到了上面我們說的流程,更新和構(gòu)建的區(qū)別,只在于構(gòu)建是從頂點(diǎn)開始,并且是element樹與Widget樹向下交叉構(gòu)建,而更新的流程會在Widget節(jié)點(diǎn)沒有類型和key變化的前提下優(yōu)先保留element,新建Widget節(jié)點(diǎn)的子樹。更新的方式也是向下交叉的遍歷。
RenderObject樹
經(jīng)過上面的介紹,可能存在很多復(fù)雜的邏輯繞來繞去,如果沒有耐心查看源代碼,大可以簡單的認(rèn)為,element樹和Widget樹的掛載是向下交叉完成的。
但我們了解Element樹的意義似乎并沒有表現(xiàn)出來,因為Element沒有渲染合成相關(guān)的代碼。所以可以認(rèn)為Element并不是Layer合成的幫助者,這時候需要引入一個之前介紹樹構(gòu)建邏輯時忽略的對象RenderObject。
RenderObject的主要實現(xiàn)我們后續(xù)系列再表,它的主要作用就是將Widget的布局屬性進(jìn)行l(wèi)ayout paint CompositingBits 等方式的計算,最后將渲染的計算結(jié)果提交圖形引擎。
在Element的實現(xiàn)中
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;
}
如果此對象是RenderObjectElement,則渲染對象是樹中此位置處的對象。否則,這個getter將沿著樹走下去,直到找到一個RenderObjectElement。
根節(jié)點(diǎn)的element 其實繼承自RenderObjectElement
RnderObjectElement持有了一個_renderObeject對象,這個對象是由element.widget createRenderObject創(chuàng)建出來的。
在根節(jié)點(diǎn)RenderObjectToWidgetAdpater中返回的是RenderObjectWithChildMixin的類,這個類是在RenderObject的Mixin類,他實際返回的是WidgetFlutterBinding中的renderView 也即是pipelineOwner的rootNode。
不是每個Widget都存在CreateRenderObject方法。CreateRenderObject被聲明在Widget的子類RenderObjectWidget中。