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