哈羅大家好,這個是我們Flutter的原理篇第二篇內(nèi)容宪躯,第一篇的內(nèi)容大家感興趣的話可以點擊這個《Flutter原理篇:聊一聊future,await,事件隊列,微任務(wù)》 連接去查看坝橡,我們今天來說一下《Flutter運行原理篇之Build構(gòu)建的過程》
Build構(gòu)建是Widget最開始執(zhí)行的步驟過程,我們要弄懂原理就要從Widget一開始怎么運行去洞察他棋电,首先我們知道Widget的更新運行要經(jīng)過5個步驟:
void drawFrame() {
buildOwner!.buildScope(renderViewElement!); // 1.重新構(gòu)建widget
super.drawFrame();
//下面幾個是在super.drawFrame()執(zhí)行的
pipelineOwner.flushLayout(); // 2.更新布局
pipelineOwner.flushCompositingBits(); //3.更新“層合成”信息
pipelineOwner.flushPaint(); // 4.重繪
if (sendFramesToEngine) {
renderView.compositeFrame(); // 5. 上屏霎俩,將繪制出的bit數(shù)據(jù)發(fā)送給GPU
}
}
所以我們的Build只是其中的第一個重要的步驟而已,好的讓我們從頭開始吧!
在介紹這個原理之前先給大家普及或者回憶下一些基本的內(nèi)容饥悴,我怕有些初學(xué)的朋友連概率可能都不清楚一看源碼有可能會迷糊棠笑,其中有些我也是吸收了網(wǎng)路上面好的文章再加上我自己總結(jié)的經(jīng)驗在里面了
首先介紹的就是三棵樹的概念:這個概念想必是Flutter的初學(xué)者也會不陌生库车,上圖:
這里需要注意:
- 三棵樹中阵漏,經(jīng)常我們聽過這三棵樹是一一對應(yīng)的,其實這種說法只是宏觀上的,微觀上是不對的栈妆,確切的說應(yīng)該是Widget 和 Element 是一一對應(yīng)的昧旨,但并不和 RenderObject 一一對應(yīng)
例如:Text的Widget 在他的build方法里面返回的確是一個叫做RichText的Widget蒋得,而RichText 又繼承于 MultiChildRenderObjectWidget 在RichText里面才有createRenderObject方法去生產(chǎn)一個RenderObject
Text的Build方法片段如下:
@override
Widget build(BuildContext context) {
// 省略部分片段代碼
Widget result = RichText(
textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
maxLines: maxLines ?? defaultTextStyle.maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
text: TextSpan(
style: effectiveTextStyle,
text: data,
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
),
);
// 省略部分片段代碼
}
class RichText extends MultiChildRenderObjectWidget {
// 省略部分片段代碼
@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
return RenderParagraph(text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior,
locale: locale ?? Localizations.maybeLocaleOf(context),
);
}
// 省略部分片段代碼
}
這足以說明在微觀上并不是一一對應(yīng)的乒疏,我們再來簡單介紹下這三棵樹的對象有什么作用:
1. Widget就是一個描述文件转绷,這些描述文件在我們進(jìn)行狀態(tài)改變時會不斷的調(diào)用build方法;
2. Element是一個元素的起點,從他開始生成Widget,再從widget生成RenderObject鸭栖,他包括了這兩個對象屬性松却,你也可以理解為他們互相都包容
3. RenderObject才是最終可以被layout可以被paint繪制的對象
只不過為了開發(fā)簡單便捷,一般情況下我們只會操作Widget而且溅话,把Element與RenderObject隔離開了玻褪,真的隔離開了嗎?其實并沒有公荧,例如我們再widget的build方法里面的BuildContext對象其實就是他對應(yīng)的Element對象,這個我們后面會說到
好了讓我開始說到Flutter的Widget構(gòu)建過程分兩種調(diào)用時機:
BuildOwner.buildScope() 會有兩種調(diào)用時機:
- 樹構(gòu)建(應(yīng)用啟動時):runApp() 方法調(diào)用的 scheduleAttachRootWidget() 方法同规,它會構(gòu)建Widgets Tree循狰,Element Tree與RenderObject Tree三棵樹。
- 樹更新(更新時):這里不會重新構(gòu)建三棵樹券勺,而是只會更新dirty區(qū)域的Element绪钥,從而開始Build構(gòu)建Widget
我們先來看看第一次構(gòu)建Build是怎么運行的:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
WidgetsBinding.attachRootWidget
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}
RenderObjectToWidgetAdapter繼承自RenderObjectWidget,把他看成是一個普通的RenderObjectWidget即可关炼,RenderObjectWidget也就是從RenderObject到Element樹的橋梁程腹,注意此時的renderViewElement 肯定是null,接著往下看
RenderObjectToWidgetAdapter.attachToRenderTree
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
// This is most likely the first time the framework is ready to produce
// a frame. Ensure that we are asked for one.
SchedulerBinding.instance.ensureVisualUpdate();
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}
createElement創(chuàng)建的element是RenderObjectToWidgetElement儒拂,他是一個RootRenderObjectElement寸潦,也就是根element。
element.mount(null, null);會向下遍歷并構(gòu)建整個widget樹社痛,這里我們看到了熟悉的兩個方法createElement與mount方法见转,只不過這里的createElement并不是創(chuàng)建我們自己寫的Widget對應(yīng)的Element,而是一個根Element蒜哀,這個mount確是整個非根Element創(chuàng)建的開端斩箫,讓我往下看:
RenderObjectToWidgetElement.mount
@override
void mount(Element parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
RenderObjectToWidgetElement._rebuild
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
} catch (exception, stack) {
}
}
此時的widget就是RenderObjectToWidgetAdapter,它的widget.child就是runApp傳遞進(jìn)去的widget,也就是運行的第一個應(yīng)用Widget
Element.updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
乘客。狐血。。
Element newChild;
if (child != null) {
//省略部分代碼
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
由于是第一次易核,Element child是null匈织,執(zhí)行else里的邏輯,inflateWidget使用子widget來創(chuàng)建一個子Element耸成,此時的newWidget是runApp傳遞進(jìn)去的widget报亩。
Element.inflateWidget
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);
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild;
}
}
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
運行有兩步:
- 如果是GlobalKey的話,會先從GlobalKey中獲取引用的Element井氢,如果有 有效的element的話就復(fù)用
- 如果不是GlobalKey 或 沒有從GlobalKey中獲取到element 的話弦追,就用widget調(diào)用其createElement()來創(chuàng)建了一個element,接著就把新建的子element掛載到了element樹上花竞。
一般我們走的是第二步劲件,下面一個是重點,這里調(diào)用了newWidget.createElement 去創(chuàng)建這個Widget對應(yīng)的Element约急,也就是我們第一個運行的Widget對應(yīng)的Element
接下來到element.mount零远,他主要有兩個運行分支流程,
- 一個是ComponentElement的厌蔽,
- 一個是RenderObjectElement的牵辣,
ComponentElement.mount
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_active);
_firstBuild();
assert(_child != null);
}
super.mount會把parent記錄在此element中。
void mount(Element? parent, Object? newSlot) {
assert(_lifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
assert(slot == null);
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
// Only assign ownership if the parent is non-null. If parent is null
// (the root node), the owner should have already been assigned.
// See RootRenderObjectElement.assignOwner().
_owner = parent.owner;
}
assert(owner != null);
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
firstBuild會調(diào)用到performRebuild方法:
void performRebuild() {
Widget built;
try {
built = build();
} catch (e, stack) {
} finally {
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
}
}
此方法會首先調(diào)用build方法奴饮,通過這一步來創(chuàng)建其子widget纬向,也就是build方法里面返回的Widget,ComponentElement主要有兩個子類stateful和stateless戴卜,會有不同的實現(xiàn)逾条。
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();
}
}
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
//省略部分代碼
@override
Widget build() => state.build(this);
//省略部分代碼
}
看到這里大家就應(yīng)該很明白了,這個兩個Element的Build方法其實就是調(diào)用了Widget的Build方法投剥,也是我們最熟悉的build方法了他就是在這里被調(diào)用的
然后就又調(diào)用到了updateChild方法师脂,這就回到了上邊流程一直往下遍歷創(chuàng)建widget樹,不知道大家看明白了沒有江锨,其實就是首先從我們傳入的第一個Widget開始創(chuàng)建他對應(yīng)的Element吃警,然后這個Element調(diào)用了build方法然后間接調(diào)用了這個Widget的build方法,這個方法里面是我們的實現(xiàn)啄育,一般都會返回一個Widget(也就是他的子Widget)汤徽,然后再調(diào)用這個updateChild方法再調(diào)用inflateWidget方法去創(chuàng)建這個子Widget對應(yīng)的Element,然后再調(diào)用他的mount方法去調(diào)用子Widget的build方法去返回他的子子Widget灸撰,一直周而復(fù)始的這樣循環(huán)一直到這個Element是RenderObjectElement
上面說的是ComponentElement的mount方法運行流程谒府,還有一種是RenderObjectElement的mount方法拼坎,這種就是可渲染對象的mount方法與上面的流程會不一樣
RenderObjectElement.mount
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
此方法會創(chuàng)建一個RenderObject,之后就會把此RenderObject添加到RenderObject樹上完疫。
void attachRenderObject(dynamic newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
_findAncestorRenderObjectElement往上找離它最近的父RenderObjectElement泰鸡,然后把當(dāng)前的renderObject給插入到父RenderObjectElement中。
SingleChildRenderObjectElement.insertRenderObjectChild
@override
void insertRenderObjectChild(RenderObject child, Object? slot) {
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
assert(slot == null);
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
assert(renderObject == this.renderObject);
}
我們來看看一個可渲染對象對應(yīng)的Element壳鹤,SingleChildRenderObjectElement的insertRenderObjectChild具體實現(xiàn)是什么樣的呢盛龄,很簡單他這里把上面找到的父類的可渲染對象renderObject的child賦值了當(dāng)前對象,這個無形中通過child字段就形成了一個renderObject的鏈條芳誓,這里是值得關(guān)注的
接著_findAncestorParentDataElement()余舶,它的作用是找到離它最近的父ParentDataElement,并用ParentDataWidget上的數(shù)據(jù)更新此renderObject上的ParentData數(shù)據(jù)
好了锹淌,上面是我們一開始運行程序的時候的構(gòu)建流程匿值,看起來其實也沒有那么復(fù)雜,接下來我們講解一下如果遇到更新的時候Widget的構(gòu)建流程是怎么樣的
狀態(tài)更新時赂摆,會把標(biāo)記變?yōu)閑lement dirty狀態(tài)(這個下面我們會具體分析)挟憔,由于一般狀態(tài)更新是StatefulWidget才可以的,所以我們這里只分析StatefulWidget即可烟号,大家應(yīng)該記得StatefulWidget是通過調(diào)用State.setState來標(biāo)識當(dāng)前需要更新子widget的绊谭,如下:
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
我們可以看到會先執(zhí)行傳遞進(jìn)來的函數(shù)類型參數(shù)(這個也就是我們在setState里面自定義更新的具體內(nèi)容),然后調(diào)用markNeedsBuild來標(biāo)記此element需要更新其子widget
void markNeedsBuild() {
if (!_active)
return;
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
此方法首先會判斷是否已經(jīng)調(diào)用過了汪拥,_dirty會在之后的rebuild后重新設(shè)置為true达传,
owner是BuildOwner,用來管理widget框架迫筑。此類用于追蹤需要rebuilding的widget宪赶,并作為一個整體處理應(yīng)用于widget樹的其他任務(wù),比如管理樹的 inactive element列表铣焊,以及在調(diào)試時hot reload期間在必要時觸發(fā)“reassemble”命令,一般BuildOwner是由WidgetsBinding持有罕伯,并且與 build/layout/paint管道的其余部分一起被操作系統(tǒng)驅(qū)動
BuildOwner.scheduleBuildFor
void scheduleBuildFor(Element element) {
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
}
首先調(diào)用onBuildScheduled()方法曲伊,此方法是一個回調(diào),他是在WidgetsBinding中賦值的追他,然后把此element加入到了_dirtyElements列表中了坟募。
WidgetsBinding.initInstances
void initInstances() {
super.initInstances();
_instance = this;
_buildOwner = BuildOwner();
buildOwner.onBuildScheduled = _handleBuildScheduled;
}
_handleBuildScheduled的具體實現(xiàn)如下:
void _handleBuildScheduled() {
ensureVisualUpdate();
}
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}
先調(diào)用ensureFrameCallbacksRegistered(這個里面會注冊下一幀到來的回調(diào)方法),最后會去調(diào)用底層window.scheduleFrame()來注冊一個下一幀時回調(diào)邑狸,就類似于Android中的ViewRootImpl.scheduleTraversals()懈糯,下一幀來到后,會調(diào)用到的方法是在上邊的ensureFrameCallbacksRegistered()中注冊的回調(diào)单雾,
SchedulerBinding.ensureFrameCallbacksRegistered
void ensureFrameCallbacksRegistered() {
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}
onBeginFrame :主要是用來執(zhí)行動畫赚哗,onDrawFrame :這個主要處理上邊case里面的persistentCallbacks
下面我們簡單介紹下frame 處理流程以及對應(yīng)的幾個狀態(tài):
當(dāng)有新的 frame 到來時她紫,開始調(diào)用 SchedulerBinding.handleDrawFrame 來處理 frame,具體處理過程就是依次執(zhí)行四個任務(wù)隊列:transientCallbacks屿储、midFrameMicrotasks贿讹、persistentCallbacks、postFrameCallbacks够掠,當(dāng)四個任務(wù)隊列執(zhí)行完畢后當(dāng)前 frame 結(jié)束民褂。
綜上,F(xiàn)lutter 將整個生命周期分為五種狀態(tài)疯潭,通過 SchedulerPhase 枚舉類來表示它們:
enum SchedulerPhase {
/// 空閑狀態(tài)赊堪,并沒有 frame 在處理。這種狀態(tài)代表頁面未發(fā)生變化竖哩,并不需要重新渲染哭廉。
/// 如果頁面發(fā)生變化,需要調(diào)用`scheduleFrame()`來請求 frame期丰。
/// 注意群叶,空閑狀態(tài)只是指沒有 frame 在處理,通常微任務(wù)钝荡、定時器回調(diào)或者用戶事件回調(diào)都
/// 可能被執(zhí)行街立,比如監(jiān)聽了tap事件,用戶點擊后我們 onTap 回調(diào)就是在idle階段被執(zhí)行的埠通。
idle,
/// 執(zhí)行”臨時“回調(diào)任務(wù)赎离,”臨時“回調(diào)任務(wù)只能被執(zhí)行一次,執(zhí)行后會被移出”臨時“任務(wù)隊列端辱。
/// 典型的代表就是動畫回調(diào)會在該階段執(zhí)行梁剔。
transientCallbacks,
/// 在執(zhí)行臨時任務(wù)時可能會產(chǎn)生一些新的微任務(wù),比如在執(zhí)行第一個臨時任務(wù)時創(chuàng)建了一個
/// Future舞蔽,且這個 Future 在所有臨時任務(wù)執(zhí)行完畢前就已經(jīng) resolve 了荣病,這中情況
/// Future 的回調(diào)將在[midFrameMicrotasks]階段執(zhí)行
midFrameMicrotasks,
/// 執(zhí)行一些持久的任務(wù)(每一個frame都要執(zhí)行的任務(wù)),比如渲染管線(構(gòu)建渗柿、布局个盆、繪制)
/// 就是在該任務(wù)隊列中執(zhí)行的.
persistentCallbacks,
/// 在當(dāng)前 frame 在結(jié)束之前將會執(zhí)行 postFrameCallbacks,通常進(jìn)行一些清理工作和
/// 請求新的 frame朵栖。
postFrameCallbacks,
}
好了我們繼續(xù)講解上面提到的回調(diào)handleDrawFrame
void handleDrawFrame() {
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
} finally {
}
}
看下persistentCallbacks列表在哪添加的callback颊亮,最終找到是在RendererBinding.initInstances中添加的callback
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
initRenderView();
addPersistentFrameCallback(_handlePersistentFrameCallback);
initMouseTracker();
}
RendererBinding._handlePersistentFrameCallback
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_scheduleMouseTrackerUpdate();
}
WidgetsFlutterBinding中的WidgetsBinding繼承了RendererBinding重載了drawFrame方法,把build流程加入進(jìn)來了陨溅。
WidgetsBinding.drawFrame
@override
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement); //這里就是我們提到的刷新以后的build構(gòu)建
super.drawFrame();
buildOwner.finalizeTree();
} finally {
}
}
由于繼承關(guān)系的順序問題WidgetsBinding里面的super.drawFrame();又會調(diào)用到RendererBinding的drawFrame方法
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
}
我們再貼一下RendererBinding.drawFrame方法终惑,加上父類的drawFrame方法是不是就得出了本文一開始我們提到的整個渲染的幾個步驟呢
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}
好了,本文還是重點關(guān)注build構(gòu)建階段的內(nèi)容门扇,
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
assert(_debugStateLocked);
Element debugPreviousBuildTarget;
_dirtyElementsNeedsResorting = false;
try {
// 可以添加一個回調(diào)在build之前執(zhí)行雹有。
callback();
} finally {
}
}
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild();
} catch (e, stack) {
}
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
}
} finally {
for (final Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
}
}
可以看到會遍歷dirtyElements列表中的element.rebuild()偿渡,而element.rebuild()最終會調(diào)用到performRebuild()。
Element.rebuild
@pragma('vm:prefer-inline')
void rebuild() {
assert(_lifecycleState != _ElementLifecycle.initial);
if (_lifecycleState != _ElementLifecycle.active || !_dirty)
return;
assert(() {
debugOnRebuildDirtyWidget?.call(this, _debugBuiltOnce);
if (debugPrintRebuildDirtyWidgets) {
if (!_debugBuiltOnce) {
debugPrint('Building $this');
_debugBuiltOnce = true;
} else {
debugPrint('Rebuilding $this');
}
}
return true;
}());
assert(_lifecycleState == _ElementLifecycle.active);
assert(owner!._debugStateLocked);
Element? debugPreviousBuildTarget;
assert(() {
debugPreviousBuildTarget = owner!._debugCurrentBuildTarget;
owner!._debugCurrentBuildTarget = this;
return true;
}());
performRebuild();
assert(() {
assert(owner!._debugCurrentBuildTarget == this);
owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
return true;
}());
assert(!_dirty);
}
Element.performRebuild方法會根據(jù)不同類型的子類element去重建子widget或重建子element件舵,分為以下兩種:
ComponentElement.performRebuild
void performRebuild() {
Widget built;
try {
built = build();
} catch (e, stack) {
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
}
}
會先調(diào)用build方法創(chuàng)建一個子widget(這個就是build方法也就是我們自己在stateful控件里面實現(xiàn)的方法)卸察,然后調(diào)用Element.updateChild來更新
RenderObjectElement.performRebuild
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
因為RenderObjectElement是可渲染對象RenderObjec的對應(yīng)的Element,其不具備包裹性(這里說的包裹性指的是他沒有包裹widget)所以他沒有對應(yīng)的build方法可以調(diào)用铅祸,只是更新widget的配置坑质,這個更新我們晚點再說,我們先看看Element.updateChild里面的內(nèi)容
Element.updateChild
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;
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return 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;
}
由于不是第一次調(diào)用临梗,所以Element child不是null涡扼,進(jìn)入if邏輯,會進(jìn)行三個判斷:
- 如果子widget不變盟庞,子element和子widget匹配(就是是否都是stateless吃沪,或都是stateful),那么更新slot
- 如果子element和子widget匹配什猖,但子widget發(fā)生了變化票彪,就調(diào)用子element.update(newWidget)來更新widget配置,這種是我們最常見的情況
- 最后一個判斷是子element和子widget不匹配不狮,那么就把老的child element加入到一個_inactiveElements列表中(變成未激活狀態(tài))降铸,然后進(jìn)行重建element
element.update方法會把newWidget記錄下來,以便于下次更新作為更新判斷使用摇零,并且這里也是Element里面用了新build的newWidget替換掉了老的widget推掸,這里是需要注意的
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
我們再看看Element另一個子類StatelessElement的update方法
StatelessElement.update
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
最后又調(diào)用rebuild,上面已經(jīng)提到了rebuild中會調(diào)用performRebuild()去重建其子widget驻仅,類似一個遞歸的流程
再來看看Element另一個子類StatefulElement的update方法谅畅,也就是我們的例子會調(diào)用到這的方法
StatefulElement.update
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
final StatefulWidget oldWidget = _state._widget;
// Notice that we mark ourselves as dirty before calling didUpdateWidget to
// let authors call setState from within didUpdateWidget without triggering
// asserts.
_dirty = true;
_state._widget = widget as StatefulWidget;
try {
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
} finally {
}
rebuild();
}
回調(diào)state.didUpdateWidget(這里就是我們在自定義Widget寫的生命周期回調(diào)函數(shù)就是在這里觸發(fā)的),最后又調(diào)用rebuild噪服,rebuild中會調(diào)用performRebuild()去重建其子widget毡泻,類似一個遞歸的流程
最后來看看可渲染對象對應(yīng)的Element-RenderObjectElement的update方法是怎么樣的:
RenderObjectElement.update
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
上面我們說過了RenderObjectElement是可渲染對象RenderObjec的對應(yīng)的Element,其不具備包裹性(這里說的包裹性指的是他沒有包裹widget)所以他沒有再構(gòu)建子Widget的build方法的行為粘优,update方法里面只是更新widget的配置仇味,我們下面會提到這個更新的步驟
好了寫到這里我們已經(jīng)很清楚了build構(gòu)建步驟觸發(fā)的時間與流程,另外我們也額外的了解到了一些生命周期函數(shù)的觸發(fā)時機在哪里敬飒,順便在給大家一個小的彩蛋:我們的Element鏈條是在哪里形成的呢邪铲,大家可以觀察下Element.mount函數(shù)芬位,里面通過_parent字段標(biāo)記了一個Element的父親无拗,再通過updateChild方法里面(代碼就不貼了往上看)的child字段來確定他的子類,這樣的話一個Element的鏈條就形成了(這里有些博客忽略的細(xì)節(jié)昧碉,希望能給到你幫助)
Element.mount
void mount(Element? parent, Object? newSlot) {
assert(_lifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
assert(slot == null);
_parent = parent; //重點看這里
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
// Only assign ownership if the parent is non-null. If parent is null
// (the root node), the owner should have already been assigned.
// See RootRenderObjectElement.assignOwner().
_owner = parent.owner;
}
assert(owner != null);
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
PS:我們的RenderObject的鏈條上面也已經(jīng)說過了哦英染,忘記的可以回看一下
最后我們再來提一下另一個菜單也就是我們上面留下的一個問題RenderObjectElement.performRebuild里面的updateRenderObject方法與上面提到的RenderObjectElement.update里面的updateRenderObject方法好像還沒有結(jié)論
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
其實這里會觸發(fā)我們整個渲染流程的第二步揽惹,也就是layout的步驟就是在這里觸發(fā)的,我們下一篇原理篇會重點講解下這個步驟四康,小伙伴們期待一下吧搪搏,好了本文就寫到這里了如果有喜歡的小伙伴歡迎點贊,有疑問的可以給我留言闪金,我都會回復(fù)大家的疯溺,我們下期再見···