一: 概述
學(xué)Flutter印象最深的一句話是:萬物皆Widget。 但Widget是不可變的,頁面發(fā)生變化時,Widget一定會被重新構(gòu)建。 在我們理解中對象的創(chuàng)建帶來的性能損耗是非常巨大的,這與Flutter宣傳的高性能不符合。這其中的緣由是,Widget并不是“邏輯處理”與“視圖渲染”對象,它是一個非常輕量級的描述類。 因此Widget的批量創(chuàng)建銷毀對Flutter的性能損耗是非常小的。
Flutter會根據(jù)Widget創(chuàng)建出唯一的Element,然后會創(chuàng)建出一個繼承自Element的唯一RenderObjet。Widget負責(zé)頁面描述,Element負責(zé)信息存儲和中樞調(diào)度,RenderObjet是真正的繪制實例。 由于他們結(jié)構(gòu)類似于HTML中的DOM樹,因此形象的將其稱為Flutter的三棵樹。
二: 啟動流程圖
本文將從源碼解析Flutter的啟動流程,順便標記三棵樹的創(chuàng)建時機。源碼均復(fù)制自 Stable 1.17.5, 非核心代碼有刪減。下圖是我根據(jù)源碼調(diào)用順序繪制的簡單流程圖。
三: 啟動詳細流程
3.1 main
void main() => runApp(MyApp());
3.2 runApp
[flutter/lib/src/widgets/binding.dart]
/// 把傳入 widget 充滿全屏
///
/// 傳入的 widget 會被強制充滿全屏,若想調(diào)整 widget 對齊方式,可考慮[Align]、[Center]等
///
/// 再次調(diào)用[runApp]將會把屏幕之前的root widget替換為傳入的widget。
/// 新的 widget 樹會與之前的 widget樹進行比較,任何差異都會被應(yīng)用到底層的渲染樹上
/// 類似于調(diào)用 [State.setState] 后 [StatefulWidget] 重新構(gòu)建
///
/// 如果需要,使用[WidgetsFlutterBinding]初始化綁定。
///
/// 參見:
///
/// * [WidgetsBinding.attachRootWidget], widget 結(jié)構(gòu)創(chuàng)建根 widget
/// * [RenderObjectToWidgetAdapter.attachToRenderTree], 元素結(jié)構(gòu)創(chuàng)建根元素。
/// * [WidgetsBinding.handleBeginFrame], _transientCallbacks 的函數(shù)回調(diào)
/// 以確保小部件、元素和渲染樹都構(gòu)建好了。
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
從這里可大致推測出,runApp做了三件事:
- 初始化 WidgetsFlutterBinding
- 將界面App加載到 flutter 樹中
- 計算面積,開始繪制
3.3 ensureInitialized
[flutter/lib/src/widgets/binding.dart]
/// 給程序做一個功能集的綁定
///
/// 粘合framework 和 引擎層的膠水
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
這是一個功能集(flutter使用 mixin語法,實現(xiàn)多繼承的效果)包含:
- GestureBinding:提供了window.onPointerDataPacket 回調(diào),綁定Framework手勢子系統(tǒng),是Framework事件模型與底層事件的綁定入口。
- ServicesBinding:提供了window.onPlatformMessage 回調(diào), 用于綁定平臺消息通道(message channel),主要處理原生和Flutter通信。
- SchedulerBinding:提供了window.onBeginFrame和window.onDrawFrame回調(diào),監(jiān)聽刷新事件,綁定Framework繪制調(diào)度子系統(tǒng)。
- PaintingBinding:綁定繪制庫,主要用于處理圖片緩存。
- SemanticsBinding:語義化層與Flutter engine的橋梁,主要是輔助功能的底層支持。
- RendererBinding: 提供了window.onMetricsChanged 、window.onTextScaleFactorChanged 等回調(diào)。它是渲染樹與Flutter engine的橋梁。
- WidgetsBinding:提供了window.onLocaleChanged、onBuildScheduled 等回調(diào)。它是Flutter widget層與engine的橋梁。
3.4 scheduleAttachRootWidget
[flutter/lib/src/widgets/binding.dart]
/// 生成一個計時器,同時附加到根 widget 上.
///
/// [runApp]時配置,若是想同步構(gòu)建 widget 樹,需使用[attachRootWidget]
void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget);
});
}
3.4.1 attachRootWidget
[flutter/lib/src/widgets/binding.dart]
/// 將 widget 附加到 [renderViewElement]上(renderViewElement 是 Element 樹的根元素)
///
/// [runApp] 時配置生成 widget 樹
///
/// 參見: [RenderObjectToWidgetAdapter.attachToRenderTree].
void attachRootWidget(Widget rootWidget) {
//構(gòu)造了一個RenderObjectToWidgetAdapter實例,它繼承于Widget,所以它本質(zhì)就是Widget。然后我們寫的Widget(也就是rootWidget變量)作為child參數(shù)傳進去,而Render樹的根結(jié)點RenderView也作為container參數(shù)傳進去。
//它就是我們Widget樹的根結(jié)點,我們寫的Widget就是掛在它下面,它對應(yīng)的RenderObject就是RenderView。
// _renderViewElement是element的根樹,首次調(diào)用[runApp]時初始化
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement);
// buildOwner負責(zé)管理 Widget 樹的構(gòu)建
// 初始化[_buildOwner]必須在[initInstances]方法中完成,
// 因為它需要 [ServicesBinding] 設(shè)置 [defaultBinaryMessenger] 實例。
}
3.4.2 attachToRenderTree
[flutter/lib/src/widgets/binding.dart]
/// 將當(dāng)前 widget 充滿屏幕,返回 element 會作為 [Element] 樹的子樹
///
/// If `element` is null, 該方法會創(chuàng)建一個 element . else 傳入 element 將有會替換該 widget。
///
/// 用于[runApp]引導(dǎo)應(yīng)用程序
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
if (element == null) {
owner.lockState(() { //lockState,在下面代碼執(zhí)行過程中,禁止調(diào)用setState方法
element = createElement(); //
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;
}
3.4.2.1 createElement
@override
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
/// 創(chuàng)建 [RenderObject]托管的 節(jié)點
///
/// 創(chuàng)建的[RenderObject]不會添加到 render 樹中,需要調(diào)用
///[RenderObjectToWidgetAdapter.attachToRenderTree] 添加
RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> widget) : super(widget);
3.4.2.2 buildScope
[flutter/lib/src/widgets/framework.dart]
/// 為更新的 widget 樹建立一個作用域,并調(diào)用給定的' callback '(如果有的話)
/// 然后,使用 [scheduleBuildFor] 按深度順序構(gòu)建所有標記為dirty的元素
///
/// 這種機制可以防止構(gòu)建方法過渡性地要求其他構(gòu)建方法運行,從而可能導(dǎo)致無限循環(huán)
///
/// 臟列表在`callback'返回之后被處理,并使用[scheduleBuildFor]按深度順序構(gòu)建所有標記為臟的元素
/// 如果在此方法運行時將元素標記為臟元素,則此元素必須比“ context”節(jié)點深,并且比任何先前構(gòu)建的節(jié)點深
///
/// 要在不執(zhí)行任何其他工作的情況下刷新當(dāng)前臟列表,可以調(diào)用此函數(shù)而無需回調(diào)
/// framework會在每幀繪制中調(diào)用 [WidgetsBinding.drawFrame]
///
/// 一次只能激活一個[buildScope]。
///
/// 每個[buildScope]都有各自的[lockState]作用域。
///
/// 若想在每次調(diào)用此方法時打印控制臺消息,將[debugPrintBuildScope]設(shè)置為true。
/// 該方法在調(diào)試 widgets 未被標記為dirty或過于頻繁地被標記為dirty的問題時非常有用。
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
Timeline.startSync('Build', arguments: timelineWhitelistArguments);
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
Element debugPreviousBuildTarget;
_dirtyElementsNeedsResorting = false;
try {
callback(); //回調(diào)mount()
}
...
}
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild(); //針對臟元素執(zhí)行rebuild操作
}
...
}
} finally {
...
Timeline.finishSync();
}
}
3.4.2.3 mount
[flutter/lib/src/widgets/binding.dart]
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_rebuild();
}
3.4.2.3.1 mount
[flutter/lib/src/widgets/framework.dart]
/// 將這個元素添加到給定父節(jié)點的給定位置中。
///
/// 首次將新創(chuàng)建的元素添加到樹時,framework 調(diào)用此函數(shù)。使用此方法初始化依賴于擁有父類的狀態(tài)。
/// 獨立于父類的 State 更容易在構(gòu)造函數(shù)中初始化。
///
/// 此方法將元素從“initial(初始)” 生命周期狀態(tài)轉(zhuǎn)換為 “active (活動)” 生命周期狀態(tài)。
///
/// 重寫此方法的子類可能也要重寫[update], [visitChildren],
/// [RenderObjectElement.insertRenderObjectChild],
/// [RenderObjectElement.moveRenderObjectChild],
/// [RenderObjectElement.removeRenderObjectChild].
@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();
}
3.4.2.3.2 mount
[flutter/lib/src/widgets/framework.dart]
/// 實現(xiàn)類重寫mount
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);//通過Elemet生成 RenderObject樹
attachRenderObject(newSlot);
_dirty = false;
}
3.4.2.4 _rebuild
[flutter/lib/src/widgets/binding.dart]
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
} catch (exception, stack) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: ErrorDescription('attaching to the render tree'),
);
FlutterError.reportError(details);
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}
3.4.2.5 updateChild
[flutter/lib/src/widgets/framework.dart]
/// 用給定的新配置更新給定的子節(jié)點。
///
/// 此方法是 widgets 系統(tǒng)的核心。每次根據(jù)更新后的配置 “添加”、“更新”、“刪除子節(jié)點”時都會調(diào)用它。
///
/// `newSlot`參數(shù)指定此元素的 [slot](節(jié)點槽點) 的新值。
///
/// 若`child`為null,而`newWidget`不為null,則需要為其創(chuàng)建一個新元素,
/// 并使用newWidget配置一個[Element]。
///
/// 若`newWidget`為null,而`child`不為null,則需將其刪除,因為它沒有配置信息了。
///
/// 若兩個值都不為 null,則需要將 'child' 的配置更新為 'newWidget' 提供的新配置。
/// 如果可以將“ newWidget”提供給現(xiàn)有的子節(jié)點(由[Widget.canUpdate]確定),則將其給定。
/// 否則,需要丟棄舊的子節(jié)點,并為新配置創(chuàng)建一個新的子節(jié)點。
///
/// 如果兩者都為null,則說明沒有也不會有子節(jié)點了,因此啥也不干
///
/// [updateChild]方法如果必須創(chuàng)建一個子節(jié)點,則返回一個新的子節(jié)點;
/// 如果必須更新子節(jié)點,則返回傳入的子節(jié)點;如果必須刪除該子節(jié)點而沒替換它,則返回null。
///
/// 下面的表格總結(jié)了上面的內(nèi)容:
///
/// | | **newWidget == null** | **newWidget != null** |
/// | :-----------------: | :--------------------- | :---------------------- |
/// | **child == null** | Returns null. | Returns new [Element]. |
/// | **child != null** | 刪除Old child, returns null. | 如果可能的話更新Old child,返回child或新的 [Element]. |
@protected
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;
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;
}
3.5.scheduleWarmUpFrame
[flutter/lib/src/widgets/binding.dart]
/// 盡可能快的幀繪制,并非等待Vsync信號的響應(yīng)
///
/// 在程序啟動時使用,以便第一幀繪制能額外獲得幾毫秒
///
/// 鎖定事件調(diào)度,直至此幀繪制完成
///
/// 若提前執(zhí)行了 [scheduleFrame] 或 [scheduleForcedFrame],它會延時執(zhí)行
///
/// 若別的幀繪制調(diào)度已開始,或別的地方調(diào)用了[scheduleWarmUpFrame] 則此方法不會執(zhí)行
///
/// 更推薦 [scheduleFrame] 來更新試圖
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;
_warmUpFrame = true;
Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame;
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();//
resetEpoch();// 重置時間
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});
// 事件鎖定
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
}
3.5.1. handleBeginFrame
[flutter/lib/src/widgets/binding.dart]
/// 由引擎調(diào)用以準備 framework 生成新的幀。
///
/// 此函數(shù)調(diào)用[scheduleFrameCallback]注冊的所有瞬時幀回調(diào)(TRANSIENT FRAME CALLBACKS)
/// 然后返回,運行任何預(yù)定的微任務(wù)(例如,通過瞬態(tài)幀回調(diào)解決的任何[Future]的處理程序),
/// 并調(diào)用[handleDrawFrame]以繼續(xù)幀。
///
/// 若傳入時間戳為空,則重用最后一幀的時間戳。
///
/// 若想在 debug 模式下的每幀開始處顯示橫,將[debugPrintBeginFrameBanner]設(shè)置為true。
/// 橫幅將使用[debugPrint]打印到控制臺,并包含幀號(每幀加1)和幀的時間戳。
/// 若傳入時間戳為空,則顯示字符串“warm-up frame”而非時間戳。
/// 它允許響應(yīng)操作系統(tǒng)的“Vsync”信號,從而將 frames 主動推入的幀與引擎請求的幀區(qū)分開來。
///
/// 也可以將[debugPrintEndFrameBanner]設(shè)置為true,從而在每幀末尾顯示橫幅。
/// 這使的開發(fā)者可以區(qū)分打印的日志是在'幀期間'還是在'幀之間'(例如,響應(yīng)事件或計時器)。
void handleBeginFrame(Duration? rawTimeStamp) {
Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent);
_firstRawTimeStampInEpoch ??= rawTimeStamp;
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
if (rawTimeStamp != null)
_lastRawTimeStamp = rawTimeStamp;
_hasScheduledFrame = false;
try {
// TRANSIENT FRAME CALLBACKS
Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
_schedulerPhase = SchedulerPhase.transientCallbacks;
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = <int, _FrameCallbackEntry>{};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
});
_removedIds.clear();
} finally {
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}
}
3.5.2. handleDrawFrame
[flutter/lib/src/widgets/binding.dart]
/// 由引擎調(diào)用以繪制出新的幀。
///
/// 此方法在 [handleBeginFrame] 之后立即被調(diào)用。
/// 它調(diào)用由[addPersistentFrameCallback]注冊的所有回調(diào),這些回調(diào)通常驅(qū)動渲染管道,
/// 然后調(diào)用由[addPostFrameCallback]注冊的回調(diào)。
///
/// 可以參閱 [handleBeginFrame] 的掛載調(diào)試(debugging hooks),它在處理幀回調(diào)時可能很有用。
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
Timeline.finishSync(); // end the "Animate" phase
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 {
_schedulerPhase = SchedulerPhase.idle;
Timeline.finishSync(); // end the Frame
_currentFrameTimeStamp = null;
}
}
3.5.3. scheduleFrame
[flutter/lib/src/widgets/binding.dart]
/// 如有必要,可通過調(diào)用[Window.scheduleFrame]來調(diào)度一個新幀。
///
/// 調(diào)用此方法后,引擎將(最終)調(diào)用 [handleBeginFrame]。
/// (該調(diào)用可能會延遲,例:如果屏幕處于關(guān)閉狀態(tài),則通常會延遲直到屏幕打開且應(yīng)用程序可見為止。)
/// 在一個幀中調(diào)用此方法則會強制調(diào)度另一個幀,即使當(dāng)前幀尚未完成。
///
/// 調(diào)度幀由操作系統(tǒng)提供的 “Vsync” 信號觸發(fā)。
/// 歷史上屏幕通過 “Vsync”信號 刷新顯示內(nèi)容
/// 現(xiàn)在硬件的操作更復(fù)雜些,但通過“Vsync” 信號重新渲染從而刷新APP的方案繼續(xù)被延用。
///
/// 若想打印調(diào)度幀的堆棧信息,需將[debugPrintScheduleFrameStacks]設(shè)置為true。
///
/// 參見:
///
/// * [scheduleForcedFrame], 在調(diào)度幀時會忽略[lifecycleState]。
/// * [scheduleWarmUpFrame], 完全忽略“Vsync”信號并立即觸發(fā)幀繪制。
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}