Flutter APP跟其他應(yīng)用程序一樣,入口也是在main方法涣狗,其內(nèi)部就是一行代碼,調(diào)用runAppp方法穗熬,將我們定義的根widget添加到界面上唤蔗。作為Flutter的入口函數(shù)窟赏,我們有必要了解其背后的工作原理涯穷,runApp都做了些啥,能讓我們的widget顯示在界面上作煌,同事支持各種事件操作粟誓,界面刷新等起意。
首先我們來(lái)看看runApp源碼:
// App是一個(gè)widget揽咕,是Flutter應(yīng)用啟動(dòng)以后要展示的第一個(gè)組件
void runApp(Widget app) {
// 1. 確保WidgetsFlutterBinding被初始化心褐。
WidgetsFlutterBinding.ensureInitialized()
// 2. 將傳遞過(guò)來(lái)的根widget app attach到某個(gè)地方
..scheduleAttachRootWidget(app)
// 3. 調(diào)度一個(gè)‘熱身’幀
..scheduleWarmUpFrame();
}
接下來(lái)我們就繼續(xù)對(duì)著runApp內(nèi)三行代碼進(jìn)行逐一突破:
1笼踩、WidgetsFlutterBinding初始化
直接看ensureInitialized()源碼:
// WidgetsFlutterBinding可以理解為是widget框架和Flutter引擎的橋梁
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
}
WidgetsFlutterBinding類(lèi)繼承自BindingBase并且混入[mixin]了很多其他Binding類(lèi)嚎于,看名稱(chēng)都是綁定各種不同的功能于购;
BindingBase肋僧,上面的各個(gè)mixin Binding類(lèi)都是繼承自它控淡,各個(gè)mixin類(lèi)都重寫(xiě)了initInstances()方法掺炭,并且調(diào)用了super.initInstances()凭戴,所以他們所有的initInstans()方法都會(huì)被串行順序執(zhí)行么夫。如果對(duì)mixin機(jī)制不是很理解可以先看看”小白都能看懂的關(guān)于Mixins機(jī)制的理解“。最終FlutterWidgetBinding()初始化的邏輯為:
WidgetsFlutterBinding經(jīng)過(guò)mixin依賴(lài),實(shí)現(xiàn)了所有的Binding類(lèi)的功能拜银,下面逐一大概介紹一下每個(gè)Binding的作用:
GestureBinding
:提供了window.onPointerDataPacket
的回調(diào)遭垛,綁定Fragmework子系統(tǒng)锯仪,是Framework事件模型與底層事件的綁定入口庶喜。ServicesBinding
:提供了window.onPlatformMessage
回調(diào),用于綁定平臺(tái)消息通道(messagechannel
)秩冈,主要處理原生和Flutter之間的通信斥扛。SchedulerBinding
:提供了window.onBeginFrame和window.onDrawFrame回調(diào)入问,監(jiān)聽(tīng)刷新事件,綁定Framework繪制調(diào)度子系統(tǒng)PaintingBinding
:綁定繪制庫(kù)稀颁,主要用于處理圖片緩存芬失。SematicsBinding
:語(yǔ)義化層與Flutter engine的橋梁,主要是輔助功能的底層支持匾灶。RenderBinding
:提供了window.onMetricsChange棱烂、window.onTextScaleFactorChanged等回調(diào)。它是渲染樹(shù)與Flutter engine的橋梁阶女。WidgetsBinding
:提供了window.onLocaleChanged颊糜,onBuildScheduled等回調(diào)。它是Flutter widget層與engine的橋梁衬鱼。
很明顯锚扎,可以看到Window
類(lèi)提供了各種平臺(tái)的回調(diào)方法,正是我們Flutter Framework連接宿主操作系統(tǒng)的接口馁启。我們來(lái)大致看下源碼:
class Window {
// 當(dāng)前設(shè)備的DPI驾孔,即一個(gè)邏輯像素顯示多少物理像素,數(shù)字越大惯疙,顯示效果就越精細(xì)保真翠勉。
// DPI是設(shè)備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5
double get devicePixelRatio => _devicePixelRatio;
// Flutter UI繪制區(qū)域的大小
Size get physicalSize => _physicalSize;
// 當(dāng)前系統(tǒng)默認(rèn)的語(yǔ)言Locale
Locale get locale;
// 當(dāng)前系統(tǒng)字體縮放比例霉颠。
double get textScaleFactor => _textScaleFactor;
// 當(dāng)繪制區(qū)域大小改變回調(diào)
VoidCallback get onMetricsChanged => _onMetricsChanged;
// Locale發(fā)生變化回調(diào)
VoidCallback get onLocaleChanged => _onLocaleChanged;
// 系統(tǒng)字體縮放變化回調(diào)
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
// 繪制前回調(diào)对碌,一般會(huì)受顯示器的垂直同步信號(hào)VSync驅(qū)動(dòng),當(dāng)屏幕刷新時(shí)就會(huì)被調(diào)用
FrameCallback get onBeginFrame => _onBeginFrame;
// 繪制回調(diào)
VoidCallback get onDrawFrame => _onDrawFrame;
// 點(diǎn)擊或指針事件回調(diào)
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
// 調(diào)度Frame蒿偎,該方法執(zhí)行后朽们,onBeginFrame和onDrawFrame將緊接著會(huì)在合適時(shí)機(jī)被調(diào)用,
// 此方法會(huì)直接調(diào)用Flutter engine的Window_scheduleFrame方法
void scheduleFrame() native 'Window_scheduleFrame';
// 更新應(yīng)用在GPU上的渲染,此方法會(huì)直接調(diào)用Flutter engine的Window_render方法
void render(Scene scene) native 'Window_render';
// 發(fā)送平臺(tái)消息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) ;
// 平臺(tái)通道消息處理回調(diào)
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
... //其它屬性及回調(diào)
}
Window
類(lèi)包含了當(dāng)前設(shè)備和系統(tǒng)的一些信息以及Flutter Engine的一些回調(diào)诉位。通過(guò)這些Binding 監(jiān)聽(tīng)Window對(duì)象的一些事件骑脱,然后將這些事件按照Framework的模型包裝,抽象再分發(fā)苍糠。
2叁丧、scheduleAttachRootWidget
WidgetsFlutterBinding初始化之后,接著會(huì)調(diào)用WidgetsBinding.attachRootWidget
方法岳瞭,該方法負(fù)責(zé)將根Widget添加到RenderView
上拥娄,
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
注意:
代碼中的renderView
是一個(gè)RenderObject
,它渲染樹(shù)的根
renderViewElement
是renderView對(duì)應(yīng)的Element對(duì)象瞳筏,可見(jiàn)該方法主要完成根widget到根RenderObject再到跟Element的整個(gè)關(guān)聯(lián)過(guò)程稚瘾。
再來(lái)看看attachToRenderTree源碼實(shí)現(xiàn):
/// Inflate this widget and actually set the resulting [RenderObject] as the
/// child of [container].
///
/// If `element` is null, this function will create a new element. Otherwise,
/// the given element will have an update scheduled to switch to this widget.
///
/// Used by [runApp] to bootstrap applications.
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!;
}
該方法負(fù)責(zé)創(chuàng)建根element,即:RenderObjectToWidgetElement
姚炕,并且將element于widget進(jìn)行關(guān)聯(lián)摊欠,即創(chuàng)建出widget數(shù)對(duì)對(duì)應(yīng)的element樹(shù)。如果element已經(jīng)創(chuàng)建過(guò)了钻心,則將根element中關(guān)聯(lián)的widget設(shè)為新的凄硼,由此可以看出element只會(huì)創(chuàng)建一次铅协,后面會(huì)進(jìn)行復(fù)用捷沸。那么BuildOwner
是什么呢?其實(shí)它就是widget fragment的管理類(lèi)狐史,它跟蹤哪些widget需要重新構(gòu)建痒给。
3说墨、熱身幀繪制
組件數(shù)在構(gòu)建(build)完成以后,回到``runApp``實(shí)現(xiàn)中苍柏,當(dāng)``attachRootWidget``后尼斧,最后一行調(diào)用WidgetsFlutterBinding實(shí)例的scheduleWarmUpFrame()方法,該方法在實(shí)例``SchedulerBinding``中试吁,它被調(diào)用后會(huì)立即進(jìn)行一次繪制棺棵,在此次繪制結(jié)束之前,該方法會(huì)鎖定事件分發(fā)熄捍,也就是說(shuō)在本次繪制結(jié)束完成之前Flutter將不會(huì)響應(yīng)各個(gè)事件烛恤,這可以保證在繪制過(guò)程中不會(huì)被再出發(fā)新的繪制。
scheduleWarmUpFrame()源碼
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;
_warmUpFrame = true;
Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame;
// We use timers here to ensure that microtasks flush in between.
Timer.run(() {
assert(_warmUpFrame);
handleBeginFrame(null);
});
Timer.run(() {
assert(_warmUpFrame);
handleDrawFrame();
// We call resetEpoch after this frame so that, in the hot reload case,
// the very next frame pretends to have occurred immediately after this
// warm-up frame. The warm-up frame's timestamp will typically be far in
// the past (the time of the last real frame), so if we didn't reset the
// epoch we would see a sudden jump from the old time in the warm-up frame
// to the new time in the "real" frame. The biggest problem with this is
// that implicit animations end up being triggered at the old time and
// then skipping every frame and finishing in the new time.
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});
// Lock events so touch events etc don't insert themselves until the
// scheduled frame has finished.
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
}
這個(gè)函數(shù)其實(shí)就調(diào)用了兩個(gè)函數(shù)余耽,onBeginFrame
和onDrawFrame
,最后渲染出來(lái)的首幀場(chǎng)景送入engine顯示到屏幕缚柏。這里使用 Timer.run()來(lái)異步運(yùn)行兩個(gè)回調(diào),就是為了在他們被調(diào)用之前有機(jī)會(huì)處理完微任務(wù)隊(duì)列(microtaskqueue)碟贾。
我們之前說(shuō)渲染流水線是由Vsync信號(hào)驅(qū)動(dòng)的币喧,但是上述過(guò)程都是在runApp()
里完成的。并沒(méi)有看到什么地方告訴engine去調(diào)度一幀袱耽。這是因?yàn)槲覀兪窃谧鯢lutter的初始化杀餐。為了節(jié)省等待Vsync信號(hào)的時(shí)間,所以就直接把渲染流程跑完做出來(lái)第一幀圖像來(lái)了朱巨。
總結(jié)
Flutter 入口runApp分析完怜浅。我們了解到其實(shí)主要Flutter 框架的初始化過(guò)程核心作用主要是:
- 各種mixin 類(lèi) Binding的創(chuàng)建,建立與Flutter Engine的橋梁
- Element 蔬崩,Render根節(jié)點(diǎn)創(chuàng)立以及Element恶座,Render,Widget之間的關(guān)聯(lián)
- 初始幀的繪制