Flutter 必知必會系列 —— runApp 做了啥

語言: CN / TW / HK

theme: smartblue

一起養成寫作習慣!這是我參與「掘金日新計劃 · 4 月更文挑戰」的第1天,點選檢視活動詳情

前面介紹了 Flutter 的三棵樹機制、Flutter 的自定義繪製、Flutter 路由機制,這些機制都是建立在 Flutter 已經執行起來的基礎上的,那麼 Flutter 是怎麼執行的呢?

接下來的幾篇文章,就介紹 Flutter 執行之前的準備工作。

程式開始的 Main 方法

每個程式都有入口方法,比較熟悉的 Java 專案就是 public static voidmain 方法,同樣 Flutter 應用也是一個單純的 Dart 程式,所以它的入口也是 main 方法。

Fluttermain 方法的預設簽名如下:

```dart void main() {

runApp(MyApp());

} ``` 它是不接受引數,也沒有返回值的同步方法。

這裡注意一點,這個方法僅僅是 Flutter 程式的 main 簽名。

Dartmain 方法支援非同步、傳參。下面我們分別來看。

讓 main 方法非同步

Dart 程式可以用同步的方式寫非同步程式碼,就是一組 async\/await 關鍵字,這組關鍵字的使用可以看這裡:

改造之後的程式碼如下:

```dart

void main() async{ await Future.delayed(Duration(seconds: 3)); print('await 3 seconds'); } ```

和普通的 main 方法相比,上面的 main 方法在宣告處增加了 async 關鍵字,在方法體內部增加了 await 關鍵字。

列印語句並不會立馬執行,會在等待 3 秒之後執行。

Flutter 支援這種語法嗎? 支援的!但是讓 main 變成 async 的用處並不多。

讓 main 方法支援引數

Dartmian 方法也支援新增入參,規則如下:

  • 第一個位置的普通引數必須是 List 陣列型別,後面可以跟著其他型別的引數
  • 不允許存在 required 標記的可選命名引數,引數是可 null 型別

下面我們分別來看:

dart void main(List<String> args) { print(args); }

上面是宣告的地方,那麼怎麼用呢?為程式執行增加額外引數

企業微信截圖_e95e7067-5c09-4d06-a122-e2bfcf47acaf.png

我們以 IntelliJ IDEA 為例,就是在 Edit Configuration 的地方,配置 Program arguments 引數。

上面的程式就會收到一個數組,陣列的內容是 namesun

所以控制檯會打印出 namesun

但是 Flutter 並不支援這種語法。我們來看為啥不支援。我們以 Android 為例。

Android 中承載 Flutter 執行的是 FlutterActivity,執行 Flutter 程式碼的是 FlutterEngine

FlutterActivityActivity 一樣,也有 onCreate 等方法,在其 onCreate 方法中也進行了初始化的操作。

```dart

@Override protected void onCreate(@Nullable Bundle savedInstanceState) { /// 程式碼省略 delegate = new FlutterActivityAndFragmentDelegate(this); delegate.onAttach(this);//第一處 /// 程式碼省略 }

``FlutterActivityAndFragmentDelegate是代理器,處理FlutterActivityFlutterFragment` 的通用邏輯。

第一處的繫結就是初始化了 FlutterEngine 並進行了繫結。

初始化如下:

```dart

void setupFlutterEngine() {

// First, check if the host wants to use a cached FlutterEngine. String cachedEngineId = host.getCachedEngineId(); ///-------- // Second, defer to subclasses for a custom FlutterEngine. flutterEngine = host.provideFlutterEngine(host.getContext()); ///-------- flutterEngine = new FlutterEngine( host.getContext(), host.getFlutterShellArgs().toArray(), /automaticallyRegisterPlugins=/ false, /willProvideRestorationData=/ host.shouldRestoreAndSaveState()); isFlutterEngineFromHost = false; }

`` 初始化Engine的流程就是:首先從快取中取Engine,要是沒有的話,就嘗試生成使用者自定義的Engine`,這兩個要是都沒有的話,就真正執行構造方法來生成一個引擎

生成引擎的入參如下:

  • 第一個引數上下文:如果是 FlutterActivity 的話,這個上下文就是 Activity 本身,如果是 FlutterFragment 的話,這個上下文就是 Fragment 的宿主 Activity

  • 第二個引數虛擬機器引數:dartVmArgs 就是 getFlutterShellArgs 返回值,這個返回值的型別是字串型別的陣列。用於設定 Dart 虛擬機器的執行引數,比如:ARG_ENABLE_DART_PROFILING 就是虛擬機器的埠號之類的。

更明確一點:

企業微信截圖_138a2180-a15e-47ae-b7a8-2fec63d992a4.png

所以這個並不是 main 的引數,而是虛擬機器的執行配置。

  • 第三個引數是否自動註冊外掛,就是我們在 pubspec.yaml 檔案下注冊的外掛是否自動註冊到native 的工程中,一般是自動的。

註冊就是呼叫 GeneratedPluginRegister 的註冊方法。

  • 第四個引數就是表示引擎是否延遲初始化來響應某些資料,如果設定為 true,表示引擎會延遲初始化,直到資料可用才初始化。

所以 FlutterActivityonCreate 構造了一些初始化的東西:FlutterEngineFlutterViewDartExecutor等等。

Activity 中用於顯示的是 onStartFlutterActivityonStart 呼叫了代理的邏輯,我們看其中的一段流程。

```java private void doInitialFlutterViewRun() { /// 省略程式碼 String initialRoute = host.getInitialRoute(); if (initialRoute == null) { initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent()); if (initialRoute == null) { initialRoute = DEFAULT_INITIAL_ROUTE; } }

// 省略程式碼 DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint( appBundlePathOverride, host.getDartEntrypointFunctionName()); flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); //第一處 } ``` 在執行 Flutter 程式碼之前做了兩件事:

  • 確定初始化路由

  • 執行 Dart 的入口程式碼

我們看第一處,第一處只有方法名沒有方法的引數,對吧! 方法的名字是什麼呢?

java public String getDartEntrypointFunctionName() { try { Bundle metaData = getMetaData(); String desiredDartEntrypoint = metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; } catch (PackageManager.NameNotFoundException e) { return DEFAULT_DART_ENTRYPOINT; } } 就是 io.flutter.Entrypoint 對應的 value,預設是就是 main

這就是為什麼可以執行到 Flutter 的 main 方法,這個過程中沒有 main 方法的引數設定,所以 Flutter 的 main 方法不支援設定引數。

小結

Dart 程式的入口方法是 main 方法,支援非同步 、入參等特性。但是由於 Flutter 的特殊性,非同步並不常用,由於引擎的限制,引數並不支援。

Fluttermain 方法 是 FlutterActivity 或者 FlutterFragmentonStart 方法中呼叫的,呼叫的方式是 DartExecutor 來執行。

Flutter main 方法執行了啥

dart void main() { runApp(MyApp()); } 這是預設的 main 方法,這是最簡單的 main,在我們的專案中我們可能會處理很多其他操作,但是最核心的就是一句話:runApp

下面我們來看其中的邏輯。

dart void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() // 第一處 ..scheduleAttachRootWidget(app) //第二處 ..scheduleWarmUpFrame(); //第三處 }

上面的三處程式碼就是三件事:WidgetsFlutterBinding 初始化,三棵樹初始化並繫結,釋出預熱幀。

就是這三行程式碼運行了 Flutter 的工程。

WidgetsFlutterBinding 初始化

```dart class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) WidgetsFlutterBinding(); return WidgetsBinding.instance!; } } ```

ensureInitialized 確保初始化,就是一個確保單例的過程。

第一次執行 ensureInitialized 方法的時候,會走 BindingBase 及其子類的構造方法完成初始化,

確保 Flutter 專案完成初始化並只完成一次。

每一個 Binding 完成的初始化內容如下:

| Binding 名 | 作用 |
| --- | --- | | BindingBase | 初始化基類,規定了初始化的框架。initInstances 中完成初始化,比如單例等initServiceExtensions 完成服務註冊初始化 | | GestureBinding | 初始化手勢識別 和 手勢追蹤框架 | | SchedulerBinding | 初始化 幀呼叫任務 | | ServicesBinding | 初始化 外掛通道、系統的外掛。 | | PaintingBinding | 初始化 圖片快取 | | SemanticsBinding | 初始化 語義框架 | | RendererBinding | 初始化 渲染機制 和 根 RenderObject | | WidgetsBinding | 初始化 Element 機制 和 Debug 顯示機制 |

這一篇文章只介紹巨集觀上的流程,下一節詳細介紹各種 Binding 的初始化內容。

繫結根 Widget

runApp 接受一個 Widget 引數,這個 Widget 就是我們的根 Widget

我們來看這個 Widget 是怎麼繫結到根上的。

dart void scheduleAttachRootWidget(Widget rootWidget) { Timer.run(() { attachRootWidget(rootWidget); }); } 僅僅呼叫了 attach 的方法。其方法內部如下:

```dart

void attachRootWidget(Widget rootWidget) { final bool isBootstrapFrame = renderViewElement == null; _readyToProduceFrames = true; _renderViewElement = RenderObjectToWidgetAdapter( container: renderView, debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement?); if (isBootstrapFrame) { SchedulerBinding.instance!.ensureVisualUpdate(); } }

``` 總結下來就是:先判斷是否已經繫結過了。

根據 renderViewrootWidget 來生成一個 RenderObjectToWidgetAdapterRenderObjectToWidgetAdapterElement。三顆樹的關係可以看這裡。

現在三棵樹的根節點都生成完畢了,然後呼叫 attachToRenderTree 方法把這三棵樹管理起來。👉 Flutter 必知必會系列——三顆樹到底是什麼

管理就是 BuildOwner 來管理 Element,根 Element 發起三棵樹的繫結流程,就是呼叫 Elementmount 方法,方法的細節可以看這裡
👉 Element 生命週期

如果沒有繫結過,那麼就通過 SchedulerBinding 發起幀的排程和繪製流程。

預熱幀

scheduleWarmUpFrame 之前的兩行程式碼,完成了所有的準備工作。

scheduleWarmUpFrame 的作用就是儘可能快的把 Flutter 內容顯示出來。

我們知道螢幕的顯示是根據 Vsync 訊號的,比如下面這張圖:

image.png

每次收到 Vsync 之後,會進行一系列的計算等,然後顯示出來。

scheduleWarmUpFrame 的作用就是不用等待下次的 Vsync,而是直接發起繪製。

發起繪製的是什麼意思呢?就是安排幀

dart void scheduleWarmUpFrame() { /// 省略程式碼 handleBeginFrame(null); /// 省略程式碼 handleDrawFrame(); /// 省略程式碼 }

省去了一些其他的準備和判斷程式碼,handleBeginFramehandleDrawFrame 是核心程式碼。這兩個方法是整個幀排程的核心,我們放在後面詳細講。

小結

Fluttermain 方法完成了前期的準備,三棵樹的根節點的生成和繫結,發起預熱幀三項任務。和前面的流程加起來就是:

image.png

總結

Flutter 程式的入口是 main 方法,呼叫 main 的地方是 Flutter 容器決定的。然後在 main 方法中執行了一系列的 Binding 初始化 和 根結點繫結的任務。做完這些鋪墊,下一篇就看 Binding 是啥