入口函數(shù)担败,其主要作用是注入給定的小控件并將其附加到屏幕上揽浙。
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
1.初始化一個widgetsBinding
的全局單例
2.創(chuàng)建跟widget并添加到renderView上鼠渺,在這個過程中完成Element
樹和RenderObject
樹的生成
3.執(zhí)行渲染
WidgetsBinding 初始化
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
這里通過子類widgetsFlutterBinding
實(shí)例化了一個widgetsBinding
對象遵岩。但是這里widgetsFlutterBinding
沒有顯示聲明構(gòu)造方法你辣,因此我們查看它的父類構(gòu)造方法實(shí)現(xiàn)
abstract class BindingBase {
BindingBase() {
developer.Timeline.startSync('Framework initialization');
initInstances();
initServiceExtensions();
developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
developer.Timeline.finishSync();
}
可以看到,這里主要調(diào)用了initInstances()
做了一些初始化操作,但是基類BindingBase
自己的initInstances()
是一個空的實(shí)現(xiàn)绢记,因此要查看其他父類中的實(shí)現(xiàn)扁达。這里我們需要注意到widgetsFlutterBinding
類的繼承情況
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
}
因此,這里initInstances()
方法會調(diào)用最外側(cè)的widgetsBinding
中的具體實(shí)現(xiàn)蠢熄。檢查代碼可知跪解,混入的每一個類中都實(shí)現(xiàn)了initInstances
方法,并且還調(diào)用了super.initInstances()
,這樣一來签孔,就會從最外側(cè)的widgetsBinding
開始叉讥,依次鏈?zhǔn)秸{(diào)用每一個混入類中的initInstances()
方法,完成各個Binding的初始化饥追。
接下來我們先進(jìn)入widgetsBinding
中的initInstances
實(shí)現(xiàn)图仓,邏輯不多,主要是創(chuàng)建了BuildOwner
對象但绕,并給window設(shè)置了一些回調(diào)函數(shù)
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
assert(() {
_debugAddStackFilters();
return true;
}());
_buildOwner = BuildOwner();
buildOwner!.onBuildScheduled = _handleBuildScheduled;
window.onLocaleChanged = handleLocaleChanged;
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
}
再看看RendererBinding
中的initInstances
實(shí)現(xiàn)救崔,這里創(chuàng)建了PipelineOwner
對象,也給window設(shè)置了另一些回調(diào)函數(shù)
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
// 創(chuàng)建一個根RenderObject
initRenderView();
_handleSemanticsEnabledChanged();
assert(renderView != null);
// 注冊presistentCallbacks回調(diào)
addPersistentFrameCallback(_handlePersistentFrameCallback);
initMouseTracker();
if (kIsWeb) {
addPostFrameCallback(_handleWebFirstFrame);
}
}
這里我們可以查看一些跟RenderObject
的創(chuàng)建
void initRenderView() {
assert(!_debugIsRenderViewInitialized);
assert(() {
_debugIsRenderViewInitialized = true;
return true;
}());
renderView = RenderView(configuration: createViewConfiguration(), window: window);
renderView.prepareInitialFrame();
}
這里其他Binding的初始化先略過捏顺,我們查看一下出現(xiàn)多次的window是什么東西六孵。根據(jù)官方的解釋,window是Flutter框架鏈接宿主操作系統(tǒng)的接口
我們可以發(fā)下幅骄,混入的那些Binding基本上都是監(jiān)聽并處理window對象的一些事件劫窒,然后將這些事件根據(jù)Flutter框架層的模型進(jìn)行包裝、抽象最后分發(fā)拆座。
查看官方文檔可知主巍,widgetsFlutterBinding
是將Framework與Flutter引擎榜單的膠水。BindingBase
相當(dāng)于所以Binding的基類挪凑,定義了一些公共的行為孕索。
*GestureBinding
:榜單Framework手勢子系統(tǒng),是Framework事件模型與底層事件的綁定入口
*ServiceBinding
:用于綁定平臺消息通道(message channel)岖赋,主要處理原生和Flutter通信
*SchedulerBinding
:監(jiān)聽刷新事件檬果,綁定Framework繪制調(diào)度子系統(tǒng)
*PaintingBinding
:綁定繪制庫,主要用于處理圖片緩存
*SemanticsBinding
:語義化層與Flutter引擎的橋梁唐断,主要是輔助功能的底層這次
*RendererBinding
:是渲染樹與Flutter引擎的橋梁
*WidgetsBinding
:它是Flutter Widget層與引擎的橋梁
構(gòu)建Element和RenderObject樹
再看runApp下方法中scheduleAttachRootWidget
方法选脊,它實(shí)際上調(diào)用了如下方法,它完成了Widget脸甘、RenderObject
和Element
三者的關(guān)聯(lián)恳啥。具體代碼在attachToRenderTree
方法中
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
BuildOwner
是widget框架的管理器類,該類跟著哪些widgets需要重建丹诀,并處理其他使用于widgets樹的任務(wù)钝的,如管理樹的非活動元素列表翁垂,并在調(diào)試時的熱重載期間在必要時觸發(fā)“reassemble”命令。
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!;
}
首先執(zhí)行 element 為null硝桩, 所以執(zhí)行creteElement
方法創(chuàng)建Element沿猜,而真正執(zhí)行構(gòu)建樹的操作是owner.buildScope
方法。這個方法首先執(zhí)行傳入的回調(diào)碗脊,即執(zhí)行element.mount(null,null)
方法
@override
void mount(Element? parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
mount 方法會首先調(diào)用父類的mount方法啼肩,即調(diào)用到RenderObjectElement
類的 mount 方法如下,在此處構(gòu)建RenderObject
對象衙伶,同時調(diào)用attachRenderObject
方法生成RenderObject
樹
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
// ...
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
再看上面的_rebuild
方法祈坠,其中主要調(diào)用了updateChild
方法,updateChild方法的主要作用是用給定的新配置(widge)更新給定的子元素矢劲。這里可以關(guān)注catch
中的代碼赦拘,正是此處社鞥從了Flutter中常見的紅屏報錯頁面的ErrorWidget
。
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
assert(_child != null);
} 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);
}
}
updateChilid
方法移除注釋與斷言后如下芬沉,newWidget
和child
的值存在幾種不同的組合情況躺同, 當(dāng)首次進(jìn)入時,會執(zhí)行inflateWidget
丸逸,為根Widget創(chuàng)建一個新的element笋籽。
@protected
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final 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);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
inflateWidget
方法刪除斷言如下,可以看到椭员,這里創(chuàng)建了newChild
后又調(diào)用了mount方法,依次遞歸變了Widget樹的子節(jié)點(diǎn)笛园,不斷為Widget創(chuàng)建對應(yīng)的Element隘击、RenderObject, 以完成“三棵樹”的構(gòu)建與關(guān)聯(lián)研铆。
@protected
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;
}
執(zhí)行渲染
接下runApp 調(diào)用scheduleWarmUpFrame
進(jìn)行了第一次繪制埋同,具體實(shí)現(xiàn)在SchedulerBinding
中。 該方法會鎖定事件調(diào)度直到完成為止棵红,即在這次繪制完成之前都不會接收event(觸摸事件等)凶赁。
該方法中主要調(diào)用了handleBeginFrame()
和handleDrawFrame()
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;
_warmUpFrame = true;
Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame;
// 在這里使用計時器來確保microtasks在兩者之間刷新
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});
// 鎖定事件,使觸摸事件在等到預(yù)定幀結(jié)束前不會自行插入
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
}
其中handleBeginFrame
方法用于渲染前的一些準(zhǔn)備逆甜,主要是處理transientCallbacks
回調(diào)虱肄,也就是觸發(fā)動畫相關(guān)的Ticker回調(diào)。 而真正處理渲染的是handleDrawFrame
方法交煞,它由引擎調(diào)用以生成新幀咏窿。
void handleDrawFrame() {
Timeline.finishSync(); // end the "Animate" phase
try {
// 處理persistentCallbacks回調(diào)
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
// 處理postFrameCallbacks回調(diào)
_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;
}
}
根據(jù)官方文檔的解釋,有一下三個回調(diào)隊列
-
transientCallbacks
:由系統(tǒng)的Window.onBenginFrame
回調(diào)觸發(fā)素征,一般用于處理動畫的回調(diào)集嵌。 -
persistentCallbacks
:由系統(tǒng)的Window.onDrawFrame
回調(diào)觸發(fā)萝挤。用久callback,一經(jīng)注冊無法移除根欧,由widgetsBinding.instance.addPersitentFrameCallback()
注冊怜珍,主要產(chǎn)后護(hù)理布局與繪制工作。 -
postFrameCallbacks
:在persistentCallbacks回調(diào)之后凤粗,Window.onDrawFrame
返回之前執(zhí)行酥泛。它只會調(diào)用一次,調(diào)用后就會被系統(tǒng)移除侈沪〗伊В可由widgetsBinding.Instance.addPostFrameCallback()
注冊,通常用于State的更新亭罪。
可由看的瘦馍,這里的代碼邏輯主要就是處理persistentCallbacks
和postFrameCallbacks
的回調(diào),而框架正好又在 RenderBinding 初始化時注冊了一個persistentCallbacks回調(diào)
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
/// ...
addPersistentFrameCallback(_handlePersistentFrameCallback);
/// ....
}
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_scheduleMouseTrackerUpdate();
}
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;
}
}
以上代碼中需要特別注意应役,回調(diào)中并不是直接調(diào)用RenderBinding
中的drawFrame()
方法情组,而是依據(jù)widgetsFlutterBinding
混入的順序調(diào)用。
根據(jù)Dart mixin 語法箩祥,混入多個類后院崇,調(diào)用同名方法時,是從最外層開始調(diào)用袍祖〉装辏可以看到,這里是調(diào)用的widgetsBinding.drawFrame()
/// [WidgetsBinding]
/// 抽取構(gòu)建和渲染管道以生成一幀蕉陋。
/// 這個方法被handleDrawFrame調(diào)用捐凭,當(dāng)需要布局和繪制一幀時,引擎會自動調(diào)用這個方法
void drawFrame() {
TimingsCallback? firstFrameCallback;
if (_needToReportFirstFrame) {
firstFrameCallback = (List<FrameTiming> timings) {
if (!kReleaseMode) {
developer.Timeline.instantSync('Rasterized first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
}
SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
firstFrameCallback = null;
_firstFrameCompleter.complete();
};
// 只有再調(diào)用 [window.render] 時才會被調(diào)用
// 當(dāng) [sendFramesToEngine] 在幀中被設(shè)置為false時凳鬓,它將不會被調(diào)用茁肠,我們需要刪除回調(diào)
SchedulerBinding.instance!.addTimingsCallback(firstFrameCallback!);
}
try {
if (renderViewElement != null)
buildOwner!.buildScope(renderViewElement!);
super.drawFrame();
buildOwner!.finalizeTree();
} finally {
// assert
}
if (!kReleaseMode) {
if (_needToReportFirstFrame && sendFramesToEngine) {
developer.Timeline.instantSync('Widgets built first useful frame');
}
}
_needToReportFirstFrame = false;
if (firstFrameCallback != null && !sendFramesToEngine) {
// 這個幀是延時的,并不是第一個發(fā)送給引擎的應(yīng)該報告的幀
_needToReportFirstFrame = true;
SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
}
}
實(shí)際上缩举,這里的主要邏輯是try
中的代碼
try {
if (renderViewElement != null)
// 將被標(biāo)記為dirty的Element進(jìn)行rebuild()
buildOwner!.buildScope(renderViewElement!);
// 調(diào)用父類的drawFrame垦梆,這里實(shí)際上調(diào)用的是 RenderBinding中的drawFrame()方法
super.drawFrame();
// 通過卸載任何不再活動的元素來完成元素構(gòu)建過程
buildOwner!.finalizeTree();
}
其中通過調(diào)用super.drawFrame()
又再次回到RenderBinding
中的drawFrame()
方法
//更新需要計算布局的誼染對象。在這個階段仅孩,計算每個渲染對象的大小和位置托猩。
pipelineOwner.flushLayout();
//更新具有dirty compositing bits的所有渲染對象。在此階段辽慕, 每個渲染對象將了解其子對象是否需要合成站刑。
//
在選擇如何實(shí)現(xiàn)視覺效果(例如剪裁)時,在繪畫階段將使用此信息鼻百。
pipeline0wner.flushCompositingBits();
//訪問需要繪制的渲染對象進(jìn)行繪制
pipeline0wner.flushPaint();
//該方法將畫好的layer傳給引擎绞旅, 該方法調(diào)用結(jié)束后摆尝,屏幕就會顯示內(nèi)容
renderView.compositeFrame();
//如果啟用了語義因悲,則該方法將編譯渲染對象的語義,
傳給系統(tǒng)用于輔助功能晃琳,如搜索等。
pipelineOwner.flushSemantics();
小結(jié)
根據(jù)官方drawFrame 文檔的描述卫旱,繪制過程經(jīng)歷一下十個階段:
1、動畫階段:handleBeginFrame
方法是用window.onBeginFrame
注冊的顾翼,按注冊順序調(diào)用所有用scheduleFrameCallback
注冊的transientCallbacks
, 其中包括所有驅(qū)動AnimationController
對象的Ticker實(shí)例,也就是說此時所有的活動Animation對象的tick在此刻回調(diào)适贸。
2、Microtask: 在handleBeginFrame
放回后拜姿,任何被transientCallBacks
安排的微任務(wù)都會被執(zhí)行。這通常包括自Ticker和AnimationController的完成該幀的Future回調(diào)蕊肥。
3试读、構(gòu)建階段:重建widget樹中所有的dirty元素(見 State.build),查閱State.setState了解更多關(guān)于標(biāo)記一個widget dirty的構(gòu)建細(xì)節(jié),有關(guān)此步驟的更新信息辖源,請參見BuildOwner。
4狼速、布局階段:系統(tǒng)中的所有標(biāo)記dirty的 RenderObject
都會被布局。(參見 RenderObject.performLayout)向胡。 請參閱RenderObject.markNeedsLayout, 了解關(guān)于標(biāo)記一個對象以進(jìn)行布局更多的細(xì)節(jié)恼蓬。)
5、合成位階段:更新任何標(biāo)記dirty的RenderObject
對象上的 compositing bits僵芹。(請參見RenderObject.markNeedsCompositingBitsUpdate)处硬。
6、繪制階段:系統(tǒng)中的所有標(biāo)記dirty的 RenderObject
都會被重新繪制拇派,這將生產(chǎn)Layer樹荷辕。 (請參閱RenderObject.markNeedsPaint凿跳,以獲取有關(guān)將對象標(biāo)記為dirty的更新詳細(xì)信息)。
7疮方、合成階段:圖層將被轉(zhuǎn)化為一個Scene并發(fā)送給GPU控嗜。
8、語義階段:系統(tǒng)中所有標(biāo)記dirty的RenderObject的語義都會被更新(見 RenderObject.assembleSemanticsNode
)骡显。這將生產(chǎn)SemanticsNode樹疆栏。請參閱RenderObject.markNeedsSemanticsUpdate
以了解關(guān)于標(biāo)記一個對象為dirty的語義的細(xì)節(jié)。
9惫谤、widget層的完成階段:widget樹已完成壁顶,這將導(dǎo)致在從widget樹中刪除的任何對象上調(diào)用State.dispose
。 請參見``BuildOwner.finalizeTree````
10溜歪、調(diào)度層的最終化階段:在 drawFrame
返回后若专, handleDrawFrame
會調(diào)用postFrameCallbacks
回調(diào)(用 addPostFrameCallback注冊的)