? Flutter APP跟其他應用程式一樣,入口也是在main方法,其內部就是一行代碼,呼叫runAppp方法,將我們定義的根widget添加到界面上,作為Flutter的入口函式,我們有必要了解其背后的作業原理,runApp都做了些啥,能讓我們的widget顯示在界面上,同事支持各種事件操作,界面重繪等,
首先我們來看看runApp原始碼:
// App是一個widget,是Flutter應用啟動以后要展示的第一個組件
void runApp(Widget app) {
// 1. 確保WidgetsFlutterBinding被初始化,
WidgetsFlutterBinding.ensureInitialized()
// 2. 將傳遞過來的根widget app attach到某個地方
..scheduleAttachRootWidget(app)
// 3. 調度一個‘熱身’幀
..scheduleWarmUpFrame();
}
接下來我們就繼續對著runApp內三行代碼進行逐一突破:
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類繼承自BindingBase并且混入[mixin]了很多其他Binding類,看名稱都是系結各種不同的功能;
BindingBase,上面的各個mixin Binding類都是繼承自它,各個mixin類都重寫了initInstances()方法,并且呼叫了super.initInstances(),所以他們所有的initInstans()方法都會被串行順序執行,如果對mixin機制不是很理解可以先看看”小白都能看懂的關于Mixins機制的理解“,最終FlutterWidgetBinding()初始化的邏輯為:

WidgetsFlutterBinding經過mixin依賴,實作了所有的Binding類的功能,下面逐一大概介紹一下每個Binding的作用:
GestureBinding:提供了window.onPointerDataPacket的回呼,系結Fragmework子系統,是Framework事件模型與底層事件的系結入口,ServicesBinding:提供了window.onPlatformMessage回呼,用于系結平臺訊息通道(messagechannel),主要處理原生和Flutter之間的通信,SchedulerBinding:提供了window.onBeginFrame和window.onDrawFrame回呼,監聽重繪事件,系結Framework繪制調度子系統PaintingBinding:系結繪制庫,主要用于處理圖片快取,SematicsBinding:語意化層與Flutter engine的橋梁,主要是輔助功能的底層支持,RenderBinding:提供了window.onMetricsChange、window.onTextScaleFactorChanged等回呼,它是渲染樹與Flutter engine的橋梁,WidgetsBinding:提供了window.onLocaleChanged,onBuildScheduled等回呼,它是Flutter widget層與engine的橋梁,
很明顯,可以看到Window類提供了各種平臺的回呼方法,正是我們Flutter Framework連接宿主作業系統的介面,我們來大致看下原始碼:
class Window {
// 當前設備的DPI,即一個邏輯像素顯示多少物理像素,數字越大,顯示效果就越精細保真,
// DPI是設備螢屏的韌體屬性,如Nexus 6的螢屏DPI為3.5
double get devicePixelRatio => _devicePixelRatio;
// Flutter UI繪制區域的大小
Size get physicalSize => _physicalSize;
// 當前系統默認的語言Locale
Locale get locale;
// 當前系統字體縮放比例,
double get textScaleFactor => _textScaleFactor;
// 當繪制區域大小改變回呼
VoidCallback get onMetricsChanged => _onMetricsChanged;
// Locale發生變化回呼
VoidCallback get onLocaleChanged => _onLocaleChanged;
// 系統字體縮放變化回呼
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
// 繪制前回呼,一般會受顯示幕的垂直同步信號VSync驅動,當螢屏重繪時就會被呼叫
FrameCallback get onBeginFrame => _onBeginFrame;
// 繪制回呼
VoidCallback get onDrawFrame => _onDrawFrame;
// 點擊或指標事件回呼
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
// 調度Frame,該方法執行后,onBeginFrame和onDrawFrame將緊接著會在合適時機被呼叫,
// 此方法會直接呼叫Flutter engine的Window_scheduleFrame方法
void scheduleFrame() native 'Window_scheduleFrame';
// 更新應用在GPU上的渲染,此方法會直接呼叫Flutter engine的Window_render方法
void render(Scene scene) native 'Window_render';
// 發送平臺訊息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) ;
// 平臺通道訊息處理回呼
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
... //其它屬性及回呼
}
Window類包含了當前設備和系統的一些資訊以及Flutter Engine的一些回呼,通過這些Binding 監聽Window物件的一些事件,然后將這些事件按照Framework的模型包裝,抽象再分發,
2、scheduleAttachRootWidget
WidgetsFlutterBinding初始化之后,接著會呼叫WidgetsBinding.attachRootWidget方法,該方法負責將根Widget添加到RenderView上,
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
注意:
代碼中的renderView是一個RenderObject,它渲染樹的根
renderViewElement是renderView對應的Element物件,可見該方法主要完成根widget到根RenderObject再到跟Element的整個關聯程序,
再來看看attachToRenderTree原始碼實作:
/// 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!;
}
該方法負責創建根element,即:RenderObjectToWidgetElement,并且將element于widget進行關聯,即創建出widget數對對應的element樹,如果element已經創建過了,則將根element中關聯的widget設為新的,由此可以看出element只會創建一次,后面會進行復用,那么BuildOwner是什么呢?其實它就是widget fragment的管理類,它跟蹤哪些widget需要重新構建,
3、熱身幀繪制
? 組件數在構建(build)完成以后,回到runApp實作中,當attachRootWidget后,最后一行呼叫WidgetsFlutterBinding實體的scheduleWarmUpFrame()方法,該方法在實體SchedulerBinding中,它被呼叫后會立即進行一次繪制,在此次繪制結束之前,該方法會鎖定事件分發,也就是說在本次繪制結束完成之前Flutter將不會回應各個事件,這可以保證在繪制程序中不會被再出發新的繪制,
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();
});
}
這個函式其實就呼叫了兩個函式,onBeginFrame和onDrawFrame,最后渲染出來的首幀場景送入engine顯示到螢屏,這里使用 Timer.run()來異步運行兩個回呼,就是為了在他們被呼叫之前有機會處理完微任務佇列(microtaskqueue),
我們之前說渲染流水線是由Vsync信號驅動的,但是上述程序都是在runApp()里完成的,并沒有看到什么地方告訴engine去調度一幀,這是因為我們是在做Flutter的初始化,為了節省等待Vsync信號的時間,所以就直接把渲染流程跑完做出來第一幀影像來了,
總結
Flutter 入口runApp分析完,我們了解到其實主要Flutter 框架的初始化程序核心作用主要是:
- 各種mixin 類 Binding的創建,建立與Flutter Engine的橋梁
- Element ,Render根節點創立以及Element,Render,Widget之間的關聯
- 初始幀的繪制
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/316685.html
標籤:其他
下一篇:江蘇圖采上傳自定義證件照
