Flutter框架分析(二)-- 初始化

ad6623發表於2019-03-07

Flutter框架分析分析系列文章:

《Flutter框架分析(一)-- 總覽和Window》

《Flutter框架分析(二)-- 初始化》

《Flutter框架分析(三)-- Widget,Element和RenderObject》

《Flutter框架分析(四)-- Flutter框架的執行》

《Flutter框架分析(五)-- 動畫》

《Flutter框架分析(六)-- 佈局》

《Flutter框架分析(七)-- 繪製》

前言

上篇文章《Flutter框架分析(一)-- 總覽和Window》介紹了Flutter框架最核心的渲染流水線和最基礎的Window。這篇文章裡,我們從Flutter框架的初始化來進入,來一步步揭開Flutter的面紗。寫過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設定了onBeginFrameonDrawFrame的回撥,回憶一下上一篇文章講渲染流水線的時候,當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

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.navigationSystemChannels.system的訊息處理函式。這兩個回撥一個是專門處理路由的,另一個是處理一些系統事件,比如剪貼簿,震動反饋,系統音效等等。

至此,WidgetsFlutterBinding.ensureInitialized()就跑完了,總體上來講是把window提供的API分別封裝到不同的Binding裡。我們需要重點關注的是SchedulerBindingRendererBindingWidgetsBinding。這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需要有對應的WidgetElement。上述程式碼中的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的時候說的兩個回撥函式onBeginFrameonDrawFrame嗎?這裡其實就是在具體執行這兩個回撥。最後渲染出來首幀場景送入engine顯示到螢幕。這裡使用Timer.run()來非同步執行兩個回撥,是為了在它們被呼叫之前有機會處理完微任務佇列(microtask queue)。關於Dart程式碼非同步執行可以參考我的文章《Flutter/Dart中的非同步》

我們之前說渲染流水線是由Vsync訊號驅動的,但是上述過程都是在runApp()裡完成的。並沒有看到什麼地方告訴engine去排程一幀。這是因為我們是在做Flutter的初始化。為了節省等待Vsync訊號的時間,所以就直接把渲染流程跑完做出來第一幀影像來了。

總結

Flutter框架的初始化就介紹完了。順帶還包括了Flutter app首幀渲染的一個大致流程。本文中所說的Flutter框架初始化過程其實主要的點都在幾個繫結(binding)的初始化。理解的時候要記住上篇文章中介紹的渲染流水線和window。Flutter框架其實就是圍繞這兩個東西在做文章。總結起來本文的要點這麼幾個:

  • 3個重要繫結:SchedulerBindingRendererBindingWidgetsBinding
  • 2個“owner”:PipelineOwnerBuildOwner
  • 2顆樹的根節點:render tree根節點RenderView;element tree根節點RenderObjectToWidgetElement

有了這些基礎以後,後續的文章我們會再去分析WidgetElementRenderObject之間的關係,以及具體的Flutter渲染流水線各階段是如何工作的。

相關文章