一,前言
寫過Flutter程序的同學都知道,Flutter app的入口就是函數runApp()。
void main() { runApp(MyApp()); }
那么我們就從函數runApp()入手,看看這個函數被調用以后發生了什么。
二,初始化
runApp()的函數體位於widgets/binding.dart。長這樣:
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..attachRootWidget(app) ..scheduleWarmUpFrame(); }
從調用的函數名稱就可以看出來,它做了3件事,
- 確保
WidgetsFlutterBinding被初始化。 - 把你的Widget貼到什么地方去。
- 然后調度一個“熱身”幀。
OK,那我們就來挨個看一下這3件事具體都做了什么。
-
-
ensureInitialized()
-
首先我們先看一下WidgetsFlutterBinding是什么,從這個類的名稱來看,是把Widget和Flutter綁定在一起的意思。
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding { static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) WidgetsFlutterBinding(); return WidgetsBinding.instance; } }
這個類繼承自BindingBase並且混入(Mixin)了很多其他類,看名稱都是不同功能的綁定。而靜態函數ensureInitialized()所做的就是返回一個WidgetsBinding.instance單例。
混入的那些各種綁定類也都是繼承自抽象類BindingBase的。
abstract class BindingBase { BindingBase() { ... initInstances(); ... } ... ui.Window get window => ui.window; }
關於抽象類BindingBase,你需要了解兩個地方,一個是在其構造的時候會調用函數initInstances()。這個函數會由其子類,也就是上面說那些各種混入(Mixin)的綁定類各自實現,具體的初始化都是在其內部實現的。另一個就是BindingBase有一個getter,返回的是window。還記得在《Flutter框架分析之總覽和Window》中提到過的窗口嗎?沒錯,這里的window就是它。那我們是不是可以推論,這些個綁定其實就是對window的封裝?來,讓我們挨個看一下這幾個綁定類在調用initInstances()的時候做了什么的吧。
第一個是GestureBinding。手勢綁定。
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget { @override void initInstances() { super.initInstances(); _instance = this; window.onPointerDataPacket = _handlePointerDataPacket; }
在調用initInstances()的時候,主要做的事情就是給window設置了一個手勢處理的回調函數。所以這個綁定主要是負責管理手勢事件的。
第二個是ServicesBinding。服務綁定
mixin ServicesBinding on BindingBase { @override void initInstances() { super.initInstances(); _instance = this; window ..onPlatformMessage = BinaryMessages.handlePlatformMessage; initLicenses(); }
這個綁定主要是給window設置了處理Platform Message的回調。
第三個是SchedulerBinding。調度綁定。
mixin SchedulerBinding on BindingBase, ServicesBinding { @override void initInstances() { super.initInstances(); _instance = this; window.onBeginFrame = _handleBeginFrame; window.onDrawFrame = _handleDrawFrame; SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage); }
這個綁定主要是給window設置了onBeginFrame和onDrawFrame的回調,回憶一下上一篇文章講渲染流水線的時候,當Vsync信號到來的時候engine會回調Flutter的來啟動渲染流程,這兩個回調就是在SchedulerBinding管理的。
第四個是PaintingBinding。繪制綁定。
mixin PaintingBinding on BindingBase, ServicesBinding { @override void initInstances() { super.initInstances(); _instance = this; _imageCache = createImageCache(); }
這個綁定只是創建了個圖片緩存,就不細說了。
第五個是SemanticsBinding。輔助功能綁定。
mixin SemanticsBinding on BindingBase { @override void initInstances() { super.initInstances(); _instance = this; _accessibilityFeatures = window.accessibilityFeatures; }
這個綁定管理輔助功能,就不細說了。
第六個是RendererBinding。渲染綁定。這是比較重要的一個類。
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; initRenderView(); _handleSemanticsEnabledChanged(); assert(renderView != null); addPersistentFrameCallback(_handlePersistentFrameCallback); _mouseTracker = _createMouseTracker(); }
這個綁定是負責管理渲染流程的,初始化的時候做的事情也比較多。
首先是實例化了一個PipelineOwner類。這個類負責管理驅動我們之前說的渲染流水線。隨后給window設置了一系列回調函數,處理屏幕尺寸變化,亮度變化等。接着調用initRenderView()。
void initRenderView() { assert(renderView == null); renderView = RenderView(configuration: createViewConfiguration(), window: window); renderView.scheduleInitialFrame(); }
這個函數實例化了一個RenderView類。RenderView繼承自RenderObject。我們都知道Flutter框架中存在這一個渲染樹(render tree)。這個RenderView就是渲染樹(render tree)的根節點,這一點可以通過打開"Flutter Inspector"看到,在"Render Tree"這個Tab下,最根部的紅框里就是這個RenderView。
最后調用addPersistentFrameCallback添加了一個回調函數。請大家記住這個回調,渲染流水線的主要階段都會在這個回調里啟動。
第七個是WidgetsBinding,組件綁定。
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding { @override void initInstances() { super.initInstances(); _instance = this; buildOwner.onBuildScheduled = _handleBuildScheduled; window.onLocaleChanged = handleLocaleChanged; window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged; SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation); SystemChannels.system.setMessageHandler(_handleSystemMessage); }
這個綁定的初始化先給buildOwner設置了個onBuildScheduled回調,還記得渲染綁定里初始化的時候實例化了一個PipelineOwner嗎?這個BuildOwner是在組件綁定里實例化的。它主要負責管理Widget的重建,記住這兩個"owner"。他們將會Flutter框架里的核心類。接着給window設置了兩個回調,因為和渲染關系不大,就不細說了。最后設置SystemChannels.navigation和SystemChannels.system的消息處理函數。這兩個回調一個是專門處理路由的,另一個是處理一些系統事件,比如剪貼板,震動反饋,系統音效等等。
至此,WidgetsFlutterBinding.ensureInitialized()就跑完了,總體上來講是把window提供的API分別封裝到不同的Binding里。我們需要重點關注的是SchedulerBinding,RendererBinding和WidgetsBinding。這3個是渲染流水線的重要存在。
接下來就該看一下runApp()里的第二個調用了。
-
-
attachRootWidget(app)
-
這個函數的代碼如下:
void attachRootWidget(Widget rootWidget) { _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget ).attachToRenderTree(buildOwner, renderViewElement); }
在之前說的RendererBinding的初始化的時候,我們得到了一個RenderView的實例,render tree的根節點。RenderView是繼承自RenderObject的,而RenderObject需要有對應的Widget和Element。上述代碼中的RenderObjectToWidgetAdapter就是這個Widget。而對應的Element就是RenderObjectToWidgetElement了,既然是要關聯到render tree的根節點,那它自然也就是element tree的根節點了。
從上述分析我們可以得出結論:
- 渲染綁定(
RendererBinding)通過pipelineOwner間接持有render tree的根節點RenderView。 - 組件綁定(
WidgetsBinding)持有element tree的根節點RenderObjectToWidgetElement。
那么RenderObjectToWidgetElement是怎么和RenderView關聯起來的呢,那自然是通過一個Widget做到的了,看下RenderObjectToWidgetAdapter的代碼:
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget { /// Creates a bridge from a [RenderObject] to an [Element] tree. /// /// Used by [WidgetsBinding] to attach the root widget to the [RenderView]. RenderObjectToWidgetAdapter({ this.child, this.container, this.debugShortDescription }) : super(key: GlobalObjectKey(container)); @override RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this); @override RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container; ... }
你看,createElement()返回的就是RenderObjectToWidgetElement,而createRenderObject返回的container就是構造這個Widget傳入的RenderView了。而我們自己的MyApp作為一個子widget存在於RenderObjectToWidgetAdapter之中。
最后調用的attachToRenderTree做的事情屬於我們之前說的渲染流水線的構建(Build)階段,這時會根據我們自己的widget生成element tree和render tree。構建(Build)階段完成以后,那自然是要進入布局(Layout)階段和繪制(Paint)階段了。怎么進呢?那就是runApp里的最后一個函數調用了。
-
-
scheduleWarmUpFrame()
-
void scheduleWarmUpFrame() { ... Timer.run(() { ... handleBeginFrame(null); ... }); Timer.run(() { ... handleDrawFrame(); ... }); }
這個函數其實就調了兩個函數,就是之前我們講window的時候說的兩個回調函數onBeginFrame和onDrawFrame嗎?這里其實就是在具體執行這兩個回調。最后渲染出來首幀場景送入engine顯示到屏幕。這里使用Timer.run()來異步運行兩個回調,是為了在它們被調用之前有機會處理完微任務隊列(microtask queue)。關於Dart代碼異步執行可以參考我的文章《Flutter/Dart中的異步》
我們之前說渲染流水線是由Vsync信號驅動的,但是上述過程都是在runApp()里完成的。並沒有看到什么地方告訴engine去調度一幀。這是因為我們是在做Flutter的初始化。為了節省等待Vsync信號的時間,所以就直接把渲染流程跑完做出來第一幀圖像來了。
三,總結
Flutter框架的初始化就介紹完了。順帶還包括了Flutter app首幀渲染的一個大致流程。本文中所說的Flutter框架初始化過程其實主要的點都在幾個綁定(binding)的初始化。理解的時候要記住上篇文章中介紹的渲染流水線和window。Flutter框架其實就是圍繞這兩個東西在做文章。總結起來本文的要點這么幾個:
- 3個重要綁定:
SchedulerBinding,RendererBinding和WidgetsBinding。 - 2個“owner”:
PipelineOwner和BuildOwner。 - 2顆樹的根節點:render tree根節點
RenderView;element tree根節點RenderObjectToWidgetElement。
有了這些基礎以后,后續的文章我們會再去分析Widget,Element和RenderObject之間的關系,以及具體的Flutter渲染流水線各階段是如何工作的。
