談?wù)?Flutter 的 RunApp 與三棵樹誕生流程?
背景
從寫 Flutter 第一行程序開始我們就知道在 Dart 的 main 方法中通過調(diào)用 runApp 方法把自己編寫的 Widget 傳遞進(jìn)去,只有這樣編譯運(yùn)行后才能得到預(yù)期效果。你有沒有好奇這背后都經(jīng)歷了什么?runApp 為什么這么神秘?或者說,在你入門 Flutter 后應(yīng)該經(jīng)常聽到或看到過 Flutter 三棵樹核心機(jī)制的東西,你有真正的想過他們都是什么嗎?如果都沒有,那么本文就是一場解密之旅。
Flutter 程序入口
我們編寫的 Flutter App 一般入口都是在 main 方法,其內(nèi)部通過調(diào)用 runApp 方法將我們自己整個(gè)應(yīng)用的 Widget 添加并運(yùn)行,所以我們直接去看下 runApp 方法實(shí)現(xiàn),如下:
- /**
- * 位置:FLUTTER_SDK\packages\flutter\lib\src\widgets\binding.dart
- * 注意:app參數(shù)的Widget布局盒子約束constraints會被強(qiáng)制為填充屏幕,這是框架機(jī)制,自己想要調(diào)整可以用Align等包裹。
- * 多次重復(fù)調(diào)用runApp將會從屏幕上移除已添加的app Widget并添加新的上去,
- * 框架會對新的Widget樹與之前的Widget樹進(jìn)行比較,并將任何差異應(yīng)用于底層渲染樹,有點(diǎn)類似于StatefulWidget
- 調(diào)用State.setState后的重建機(jī)制。
- */
- void runApp(Widget app) {
- WidgetsFlutterBinding.ensureInitialized()
- ..scheduleAttachRootWidget(app)
- ..scheduleWarmUpFrame();
- }
可以看到上面三行代碼代表了 Flutter 啟動的核心三步(級聯(lián)運(yùn)算符調(diào)用):
- WidgetsFlutterBinding 初始化(ensureInitialized())
- 綁定根節(jié)點(diǎn)創(chuàng)建核心三棵樹(scheduleAttachRootWidget(app))
- 繪制熱身幀(scheduleWarmUpFrame())
WidgetsFlutterBinding 實(shí)例及初始化
直接看源碼,如下:
- 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,并且 with 了大量的 mixin 類。WidgetsFlutterBinding 就是將 Widget 架構(gòu)和 Flutter Engine 連接的核心橋梁,也是整個(gè) Flutter 的應(yīng)用層核心。通過 ensureInitialized() 方法我們可以得到一個(gè)全局單例的 WidgetsFlutterBinding 實(shí)例,且 mixin 的一堆 XxxBinding 也被實(shí)例化。
BindingBase 抽象類的構(gòu)造方法中會調(diào)用initInstances()方法,而各種 mixin 的 XxxBinding 實(shí)例化重點(diǎn)也都在各自的initInstances()方法中,每個(gè) XxxBinding 的職責(zé)不同,如下:
- WidgetsFlutterBinding:核心橋梁主體,F(xiàn)lutter app 全局唯一。
- BindingBase:綁定服務(wù)抽象類。
- GestureBinding:Flutter 手勢事件綁定,處理屏幕事件分發(fā)及事件回調(diào)處理,其初始化方法中重點(diǎn)就是把事件處理回調(diào)_handlePointerDataPacket函數(shù)賦值給 window 的屬性,以便 window 收到屏幕事件后調(diào)用,window 實(shí)例是 Framework 層與 Engine 層處理屏幕事件的橋梁。
- SchedulerBinding:Flutter 繪制調(diào)度器相關(guān)綁定類,debug 編譯模式時(shí)統(tǒng)計(jì)繪制流程時(shí)長等操作。
- ServicesBinding:Flutter 系統(tǒng)平臺消息監(jiān)聽綁定類。即 Platform 與 Flutter 層通信相關(guān)服務(wù),同時(shí)注冊監(jiān)聽了應(yīng)用的生命周期回調(diào)。
- PaintingBinding:Flutter 繪制預(yù)熱緩存等綁定類。
- SemanticsBinding:語義樹和 Flutter 引擎之間的粘合劑綁定類。
- RendererBinding:渲染樹和 Flutter 引擎之間的粘合劑綁定類,內(nèi)部重點(diǎn)是持有了渲染樹的根節(jié)點(diǎn)。
- WidgetsBinding:Widget 樹和 Flutter 引擎之間的粘合劑綁定類。
從 Flutter 架構(gòu)宏觀抽象看,這些 XxxBinding 承擔(dān)的角色大致是一個(gè)橋梁關(guān)聯(lián)綁定,如下:
本文由于是啟動主流程相關(guān)機(jī)制分析,所以初始化中我們需要關(guān)注的主要是 RendererBinding 和 WidgetsBinding 類的initInstances()方法,如下:
- mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
- @override
- void initInstances() {
- ......
- /**
- *1、創(chuàng)建一個(gè)管理Widgets的類對象
- *BuildOwner類用來跟蹤哪些Widget需要重建,并處理用于Widget樹的其他任務(wù),例如管理不活躍的Widget等,調(diào)試模式觸發(fā)重建等。
- */
- _buildOwner = BuildOwner();
- //2、回調(diào)方法賦值,當(dāng)?shù)谝粋€(gè)可構(gòu)建元素被標(biāo)記為臟時(shí)調(diào)用。
- buildOwner!.onBuildScheduled = _handleBuildScheduled;
- //3、回調(diào)方法賦值,當(dāng)本地配置變化或者AccessibilityFeatures變化時(shí)調(diào)用。
- window.onLocaleChanged = handleLocaleChanged;
- window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
- ......
- }
- }
- mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
- @override
- void initInstances() {
- ......
- /**
- * 4、創(chuàng)建管理rendering渲染管道的類
- * 提供接口調(diào)用用來觸發(fā)渲染。
- */
- _pipelineOwner = PipelineOwner(
- onNeedVisualUpdate: ensureVisualUpdate,
- onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
- onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
- );
- //5、一堆window變化相關(guān)的回調(diào)監(jiān)聽
- window
- ..onMetricsChanged = handleMetricsChanged
- ..onTextScaleFactorChanged = handleTextScaleFactorChanged
- ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
- ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
- ..onSemanticsAction = _handleSemanticsAction;
- //6、創(chuàng)建RenderView對象,也就是RenderObject渲染樹的根節(jié)點(diǎn)
- initRenderView();
- ......
- }
- void initRenderView() {
- ......
- //RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
- //7、渲染樹的根節(jié)點(diǎn)對象
- renderView = RenderView(configuration: createViewConfiguration(), window: window);
- renderView.prepareInitialFrame();
- }
- //定義renderView的get方法,獲取自_pipelineOwner.rootNode
- RenderView get renderView => _pipelineOwner.rootNode! as RenderView;
- //定義renderView的set方法,上面initRenderView()中實(shí)例化賦值就等于給_pipelineOwner.rootNode也進(jìn)行了賦值操作。
- set renderView(RenderView value) {
- assert(value != null);
- _pipelineOwner.rootNode = value;
- }
- }
到此基于初始化過程我們已經(jīng)得到了一些重要信息,請記住 RendererBinding 中的 RenderView 就是 RenderObject 渲染樹的根節(jié)點(diǎn)。上面這部分代碼的時(shí)序圖大致如下:
通過 scheduleAttachRootWidget 創(chuàng)建關(guān)聯(lián)三棵核心樹
- mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
- @protected
- void scheduleAttachRootWidget(Widget rootWidget) {
- //簡單的異步快速執(zhí)行,將attachRootWidget異步化
- Timer.run(() {
- attachRootWidget(rootWidget);
- });
- }
- void attachRootWidget(Widget rootWidget) {
- //1、是不是啟動幀,即看renderViewElement是否有賦值,賦值時(shí)機(jī)為步驟2
- final bool isBootstrapFrame = renderViewElement == null;
- _readyToProduceFrames = true;
- //2、橋梁創(chuàng)建RenderObject、Element、Widget關(guān)系樹,_renderViewElement值為attachToRenderTree方法返回值
- _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
- //3、RenderObjectWithChildMixin類型,繼承自RenderObject,RenderObject繼承自AbstractNode。
- //來自RendererBinding的_pipelineOwner.rootNode,_pipelineOwner來自其初始化initInstances方法實(shí)例化的PipelineOwner對象。
- //一個(gè)Flutter App全局只有一個(gè)PipelineOwner實(shí)例。
- container: renderView,
- debugShortDescription: '[root]',
- //4、我們平時(shí)寫的dart Widget app
- child: rootWidget,
- //5、attach過程,buildOwner來自WidgetsBinding初始化時(shí)實(shí)例化的BuildOwner實(shí)例,renderViewElement值就是_renderViewElement自己,此時(shí)由于調(diào)用完appach才賦值,所以首次進(jìn)來也是null。
- ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
- if (isBootstrapFrame) {
- //6、首幀主動更新一下,匹配條件的情況下內(nèi)部本質(zhì)是調(diào)用SchedulerBinding的scheduleFrame()方法。
- //進(jìn)而本質(zhì)調(diào)用了window.scheduleFrame()方法。
- SchedulerBinding.instance!.ensureVisualUpdate();
- }
- }
- }
上面代碼片段的步驟 2 和步驟 5 需要配合 RenderObjectToWidgetAdapter 類片段查看,如下:
- //1、RenderObjectToWidgetAdapter繼承自RenderObjectWidget,RenderObjectWidget繼承自Widget
- class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
- ......
- //3、我們編寫dart的runApp函數(shù)參數(shù)中傳遞的Flutter應(yīng)用Widget樹根
- final Widget? child;
- //4、繼承自RenderObject,來自PipelineOwner對象的rootNode屬性,一個(gè)Flutter App全局只有一個(gè)PipelineOwner實(shí)例。
- final RenderObjectWithChildMixin<T> container;
- ......
- //5、重寫Widget的createElement實(shí)現(xiàn),構(gòu)建了一個(gè)RenderObjectToWidgetElement實(shí)例,它繼承于Element。
- //Element樹的根結(jié)點(diǎn)是RenderObjectToWidgetElement。
- @override
- RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
- //6、重寫Widget的createRenderObject實(shí)現(xiàn),container本質(zhì)是一個(gè)RenderView。
- //RenderObject樹的根結(jié)點(diǎn)是RenderView。
- @override
- RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
- @override
- void updateRenderObject(BuildContext context, RenderObject renderObject) { }
- /**
- *7、上面代碼片段中RenderObjectToWidgetAdapter實(shí)例創(chuàng)建后調(diào)用
- *owner來自WidgetsBinding初始化時(shí)實(shí)例化的BuildOwner實(shí)例,element 值就是自己。
- *該方法創(chuàng)建根Element(RenderObjectToWidgetElement),并將Element與Widget進(jìn)行關(guān)聯(lián),即創(chuàng)建WidgetTree對應(yīng)的ElementTree。
- *如果Element已經(jīng)創(chuàng)建過則將根Element中關(guān)聯(lián)的Widget設(shè)為新的(即_newWidget)。
- *可以看見Element只會創(chuàng)建一次,后面都是直接復(fù)用的。
- */
- RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
- //8、由于首次實(shí)例化RenderObjectToWidgetAdapter調(diào)用attachToRenderTree后才不為null,所以當(dāng)前流程為null
- if (element == null) {
- //9、在lockState里面代碼執(zhí)行過程中禁止調(diào)用setState方法
- owner.lockState(() {
- //10、創(chuàng)建一個(gè)Element實(shí)例,即調(diào)用本段代碼片段中步驟5的方法。
- //調(diào)用RenderObjectToWidgetAdapter的createElement方法構(gòu)建了一個(gè)RenderObjectToWidgetElement實(shí)例,繼承RootRenderObjectElement,又繼續(xù)繼承RenderObjectElement,接著繼承Element。
- element = createElement();
- assert(element != null);
- //11、給根Element的owner屬性賦值為WidgetsBinding初始化時(shí)實(shí)例化的BuildOwner實(shí)例。
- element!.assignOwner(owner);
- });
- //12、重點(diǎn)!mount里面RenderObject
- owner.buildScope(element!, () {
- element!.mount(null, null);
- });
- } else {
- //13、更新widget樹時(shí)_newWidget賦值為新的,然后element數(shù)根標(biāo)記為markNeedsBuild
- element._newWidget = this;
- element.markNeedsBuild();
- }
- return element!;
- }
- ......
- }
對于上面步驟 12 我們先進(jìn)去簡單看下 Element (RenderObjectToWidgetElement extends RootRenderObjectElement extends RenderObjectElement extends Element)的 mount 方法,重點(diǎn)關(guān)注的是父類 RenderObjectElement 中的 mount 方法,如下:
- abstract class RenderObjectElement extends Element {
- //1、Element樹通過構(gòu)造方法RenderObjectToWidgetElement持有了Widget樹實(shí)例。(RenderObjectToWidgetAdapter)。
- @override
- RenderObjectWidget get widget => super.widget as RenderObjectWidget;
- //2、Element樹通過mount后持有了RenderObject渲染樹實(shí)例。
- @override
- RenderObject get renderObject => _renderObject!;
- RenderObject? _renderObject;
- @override
- void mount(Element? parent, Object? newSlot) {
- ......
- //3、通過widget樹(即RenderObjectToWidgetAdapter)調(diào)用createRenderObject方法傳入Element實(shí)例自己獲取RenderObject渲染樹。
- //RenderObjectToWidgetAdapter.createRenderObject(this)返回的是RenderObjectToWidgetAdapter的container成員,也就是上面分析的RenderView渲染樹根節(jié)點(diǎn)。
- _renderObject = widget.createRenderObject(this);
- ......
- }
- }
到這里對于 Flutter 的靈魂“三棵樹”來說也能得出如下結(jié)論:
- Widget 樹的根結(jié)點(diǎn)是 RenderObjectToWidgetAdapter(繼承自 RenderObjectWidget extends Widget),我們 runApp 中傳遞的 Widget 樹就被追加到了這個(gè)樹根的 child 屬性上。
- Element 樹的根結(jié)點(diǎn)是 RenderObjectToWidgetElement(繼承自 RootRenderObjectElement extends RenderObjectElement extends Element),通過調(diào)用 RenderObjectToWidgetAdapter 的 createElement 方法創(chuàng)建,創(chuàng)建 RenderObjectToWidgetElement 的時(shí)候把 RenderObjectToWidgetAdapter 通過構(gòu)造參數(shù)傳遞進(jìn)去,所以 Element 的 _widget 屬性值為 RenderObjectToWidgetAdapter 實(shí)例,也就是說 Element 樹中 _widget 屬性持有了 Widget 樹實(shí)例。RenderObjectToWidgetAdapter 。
- RenderObject 樹的根結(jié)點(diǎn)是 RenderView(RenderView extends RenderObject with RenderObjectWithChildMixin
),在 Element 進(jìn)行 mount 時(shí)通過調(diào)用 Widget 樹(RenderObjectToWidgetAdapter)的createRenderObject方法獲取 RenderObjectToWidgetAdapter 構(gòu)造實(shí)例化時(shí)傳入的 RenderView 渲染樹根節(jié)點(diǎn)。
上面代碼流程對應(yīng)的時(shí)序圖大致如下:
結(jié)合上一小結(jié)可以很容易看出來三棵樹的創(chuàng)建時(shí)機(jī)(時(shí)序圖中紫紅色節(jié)點(diǎn)),也可以很容易看出來 Element 是 Widget 和 RenderObject 之前的一個(gè)“橋梁”,其內(nèi)部持有了兩者樹根,抽象表示如下:
由于篇幅和本文主題原因,我們重心關(guān)注三棵樹的誕生流程,對于三棵樹之間如何配合進(jìn)行繪制渲染這里先不展開,后面會專門一篇分析。
熱身幀繪制
到此讓我們先將目光再回到一開始runApp方法的實(shí)現(xiàn)中,我們還差整個(gè)方法實(shí)現(xiàn)中的最后一個(gè)scheduleWarmUpFrame()調(diào)用,如下:
- mixin SchedulerBinding on BindingBase {
- void scheduleWarmUpFrame() {
- ......
- Timer.run(() {
- assert(_warmUpFrame);
- handleBeginFrame(null);
- });
- Timer.run(() {
- assert(_warmUpFrame);
- handleDrawFrame();
- //重置時(shí)間戳,避免熱重載情況從熱身幀到熱重載幀的時(shí)間差,導(dǎo)致隱式動畫的跳幀情況。
- resetEpoch();
- ......
- if (hadScheduledFrame)
- scheduleFrame();
- });
- //在此次繪制結(jié)束前該方法會鎖定事件分發(fā),可保證繪制過程中不會再觸發(fā)新重繪。
- //也就是說在本次繪制結(jié)束前不會響應(yīng)各種事件。
- lockEvents(() async {
- await endOfFrame;
- Timeline.finishSync();
- });
- }
- }
這段代碼的本質(zhì)這里先不詳細(xì)展開,因?yàn)楸举|(zhì)就是渲染幀的提交與觸發(fā)相關(guān),我們后邊文章會詳細(xì)分析 framework 層繪制渲染相關(guān)邏輯,那時(shí)再展開。在這里只用知道它被調(diào)用后會立即執(zhí)行一次繪制(不用等待 VSYNC 信號到來)。
這時(shí)候細(xì)心的話,你可能會有疑問,前面分析 attachRootWidget 方法調(diào)用時(shí),它的最后一行發(fā)現(xiàn)是啟動幀則會調(diào)用window.scheduleFrame()然后等系統(tǒng) VSYNC 信號到來觸發(fā)繪制,既然 VSYNC 信號到來時(shí)會觸發(fā)繪制,這個(gè)主動熱身幀豈不是可以不要?
是的,不要也是沒問題的,只是體驗(yàn)不是很好,會導(dǎo)致初始化卡幀的效果。因?yàn)榍懊鎤indow.scheduleFrame()發(fā)起的繪制請求是在收到系統(tǒng) VSYNC 信號后才真正執(zhí)行,而 Flutter app 初始化時(shí)為了盡快呈現(xiàn) UI 而沒有等待系統(tǒng) VSYNC 信號到來就主動發(fā)起一針繪制(也被形象的叫做熱身幀),這樣最長可以減少一個(gè) VSYNC 等待時(shí)間。
總結(jié)
上面就是 Flutter Dart 端三棵樹的誕生流程,關(guān)于三棵樹是如何互相工作的,我們會在后面專門篇章做分析,這里就先不展開了。
本文轉(zhuǎn)載自微信公眾號「碼農(nóng)每日一題」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系碼農(nóng)每日一題公眾號。