關於Flutter初始化,我必須告訴你的是...(乾貨)

閒魚技術發表於2018-11-28

引言

最近在做效能最佳化的時候發現,在混合棧開發中,第一次啟動Flutter頁面的耗時總會是第二次啟動Flutter頁面耗時的兩倍左右,這樣給人感覺很不好。分析發現第一次啟動Flutter頁面會做一些初始化工作,藉此,我梳理了下Flutter的初始化流程。

Flutter初始化時序

Flutter初始化主要分四部分,FlutterMain初始化、FlutterNativeView的初始化、FlutterView初始化和Flutter Bundle初始化。 我們先看下Flutter初始化的時序圖,來整體把握下Flutter初始化的一般流程:

關於Flutter初始化,我必須告訴你的是...(乾貨)

具體分析

FlutterMain初始化

這部分初始化工作是由Application.onCreate方法中呼叫開始的,在Application建立的時候就會初始化完成,不會影響Flutter頁面的第一次啟動,所以這裡只是做一個簡單分析。
從FlutterMain.startInitialization方法程式碼中可以輕易看出來,初始化主要分四部分。
前面三部分比較類似,分別是初始化配置資訊、初始化AOT編譯和初始化資源,最後一部分則是載入Flutter的Native環境。
這部分感興趣的同學可以看下FlutterMain.java原始碼,邏輯還是比較清晰的。

public static void startInitialization(Context applicationContext, Settings settings) {
    // other codes ...
    initConfig(applicationContext);
    initAot(applicationContext);
    initResources(applicationContext);
    System.loadLibrary("flutter");
    // other codes ...
}

FlutterNativeView初始化

先用一個圖來展現FlutterNativeView建構函式的呼叫棧:

關於Flutter初始化,我必須告訴你的是...(乾貨)

從上圖的呼叫棧中我們知道FlutterNativeView的初始化主要做了些什麼,我們再從原始碼角度較為深入的瞭解下:
FlutterNativeView的建構函式最終主要呼叫了一個nativeAttach方法。到這裡就需要分析引擎層程式碼了,我們可以在JNI檔案中找到對應的jni方法呼叫。(具體檔案為platform_view_android_jni.cc)

static const JNINativeMethod native_view_methods[] = {
  {
      .name = "nativeAttach",
      .signature = "(Lio/flutter/view/FlutterNativeView;)J",
      .fnPtr = reinterpret_cast<void*>(&shell::Attach),
  },
  // other codes ...
};

從程式碼中很容易看出FlutterNativeView.attach方法最終呼叫了shell::Attach方法,而shell::Attach方法主要做了兩件事:
1. 建立PlatformViewAndroid。
2. 呼叫PlatformViewAndroid::Attach。

static jlong Attach(JNIEnv* env, jclass clazz, jobject flutterView) {
  auto view = new PlatformViewAndroid();
  // other codes ...
  view->Attach();
  // other codes ...
}

那我們再分析下PlatformViewAndroid的建構函式和Attach方法都做了些什麼呢?

  1. PlatformViewAndroid::PlatformViewAndroid()
        : PlatformView(std::make_unique<NullRasterizer>()),
          android_surface_(InitializePlatformSurface()) {}
    void PlatformViewAndroid::Attach() {
      CreateEngine();
      // Eagerly setup the IO thread context. We have already setup the surface.
      SetupResourceContextOnIOThread();
      UpdateThreadPriorities();

其中:
1. PlatformViewAndroid的建構函式主要是呼叫了InitializePlatformSurface方法,這個方法主要是初始化了Surface,其中Surface有Vulkan、OpenGL和Software三種型別的區別。
2. PlatformViewAndroid::Attach方法這裡主要呼叫三個方法:CreateEngine、SetupResourceContextOnIOThread和UpdateThreadPriorities。
2.1 CreateEngine比較好理解,建立Engine,這裡會重新建立一個Engine物件。
2.2 SetupResourceContextOnIOThread是在IO執行緒去準備資源的上下文邏輯。
2.3 UpdateThreadPriorities是設定執行緒優先順序,這設定GPU執行緒優先順序為-2,UI執行緒優先順序為-1。

FlutterView初始化

FlutterView的初始化就是純粹的Android層啦,所以相對比較簡單。分析FlutterView.java的建構函式就會發現,整個FlutterView的初始化在確保FlutterNativeView的建立成功和一些必要的view設定之外,主要做了兩件事:
1. 註冊SurfaceHolder監聽,其中surfaceCreated回撥會作為Flutter的第一幀回撥使用。
2. 初始化了Flutter系統需要用到的一系列橋接方法。例如:localization、navigation、keyevent、system、settings、platform、textinput。
FlutterView初始化流程主要如下圖所示:

關於Flutter初始化,我必須告訴你的是...(乾貨)


Flutter Bundle初始化


Flutter Bundle的初始化是由呼叫FlutterActivityDelegate.runFlutterBundle開始的,先用一張圖來說明下runFlutterBundle方法的呼叫棧:

關於Flutter初始化,我必須告訴你的是...(乾貨)

我們再從原始碼角度較為深入瞭解下:
FlutterActivity的onCreate方法在執行完FlutterActivityDelegate的onCreate方法之後會呼叫它的runFlutterBundle方法。runFlutterBundle程式碼如下:

public void runFlutterBundle(){
    // other codes ...
    String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
    if (appBundlePath != null) {
        flutterView.runFromBundle(appBundlePath, null, "main", reuseIsolate);
    }
}

很明顯,這個runFlutterBundle並沒有做太多事情,而且直接呼叫了FlutterView.runFromBundle方法。而後兜兜轉轉最後會呼叫到PlatformViewAndroid::RunBundleAndSnapshot方法。

void PlatformViewAndroid::RunBundleAndSnapshot(JNIEnv* env, std::string bundle_path,
                                               std::string snapshot_override,
                                               std::string entrypoint,
                                               bool reuse_runtime_controller,
                                               jobject assetManager) {
  // other codes ...
  blink::Threads::UI()->PostTask(
      [engine = engine_->GetWeakPtr(),
       asset_provider = std::move(asset_provider),
       bundle_path = std::move(bundle_path), entrypoint = std::move(entrypoint),
       reuse_runtime_controller = reuse_runtime_controller] {
        if (engine)
          engine->RunBundleWithAssets(
              std::move(asset_provider), std::move(bundle_path),
              std::move(entrypoint), reuse_runtime_controller);
      });
}

PlatformViewAndroid::RunBundleAndSnapshot在UI執行緒中呼叫Engine::RunBundleWithAssets,最終呼叫Engine::DoRunBundle。
DoRunBundle方法最後只會呼叫RunFromPrecompiledSnapshot、RunFromKernel和RunFromScriptSnapshot三個方法中的一個。而這三個方法最終都會呼叫SendStartMessage方法。

bool DartController::SendStartMessage(Dart_Handle root_library,
                                      const std::string& entrypoint) {
  // other codes ...
  // Get the closure of main().
  Dart_Handle main_closure = Dart_GetClosure(
      root_library, Dart_NewStringFromCString(entrypoint.c_str()));
  // other codes ...
  // Grab the 'dart:isolate' library.
  Dart_Handle isolate_lib = Dart_LookupLibrary(ToDart("dart:isolate"));
  DART_CHECK_VALID(isolate_lib);
  // Send the start message containing the entry point by calling
  // _startMainIsolate in dart:isolate.
  const intptr_t kNumIsolateArgs = 2;
  Dart_Handle isolate_args[kNumIsolateArgs];
  isolate_args[0] = main_closure;
  isolate_args[1] = Dart_Null();
  Dart_Handle result = Dart_Invoke(isolate_lib, ToDart("_startMainIsolate"),
                                   kNumIsolateArgs, isolate_args);
  return LogIfError(result);
}

而SendStartMessage方法主要做了三件事:
1. 獲取Flutter入口方法(例如main方法)的closure。
2. 獲取FlutterLibrary。
3. 傳送訊息來呼叫Flutter的入口方法。

總結

本次主要分析了下FlutterActivity的onCreate方法中的Flutter初始化部分邏輯,很明顯會發現主要耗時在FlutterNativeView、FlutterView和Flutter Bundle的初始化這三塊,將這三部分的初始化工作前置就可以比較容易的解決引言中提出的問題。經測試發現,這樣改動之後,Flutter頁面第一次啟動時長和後面幾次啟動時長差不多一樣了。

對於FlutterMain.startInitialization的初始化邏輯、SendStartMessage傳送的訊息如何最終呼叫Flutter中的入口方法邏輯沒有進一步深入分析,這些內容後續再繼續分析撰文分享。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900359/viewspace-2222023/,如需轉載,請註明出處,否則將追究法律責任。

相關文章