1. 程式人生 > >深入分析 Flutter 初始化流程

深入分析 Flutter 初始化流程

在調研 Flutter 動態化方案的時候,需要了解 Flutter 載入 dart 產物的流程,閱讀了一部分原始碼,順便也讀了初始化相關的程式碼。於是梳理了一遍 FLutter 的初始化流程

flutter的原始碼下載地址在 github 上可以找到,具體地址: github-flutter/engine

FLutterMain的初始化

先從 Android 的入口開始看

FlutterAppliationonCreate 中呼叫了

FlutterMain.startInitialization(this);
複製程式碼

跟進去我們會看到呼叫了 startInitialization

方法,最後會順序呼叫這幾個方法

initConfig(applicationContext);
initAot(applicationContext);
initResources(applicationContext);
複製程式碼

我們檢視 initResources 方法如圖

這裡我們可以看到實際載入了assets裡面的flutter資源。並且會把資源 copy 到本地的路徑。這裡不做深究。FlutterMan 的初始化基本包括了

  • 初始化配置
  • 初始化 AOT 編譯
  • 初始化資源

3 個部分

繼續看 FlutterView 的初始化:

FLutterView的初始化

FlutterActivity 為例,在 onCreate 中會呼叫到 FlutterActivityDelegate 的對應方法,最終呼叫 FlutterViewrunFromBundle 方法

public void runFromBundle(FlutterRunArguments args) {
    this.assertAttached();
    this.preRun();
    this.mNativeView.runFromBundle(args);
    this.postRun();
}
複製程式碼

跟蹤這段程式碼,會呼叫 FlutterNativeView

nativeRunBundleAndSnapshotFromLibrary方法。

這裡會繼續進行 jni 層的呼叫,檢視 platform_view_android_jni.cc

{
    .name = "nativeRunBundleAndSnapshotFromLibrary",
    .signature = "(J[Ljava/lang/String; Ljava/lang/String;"
    "Ljava/lang/String;Landroid/content/res/AssetManager;)V",
    .fnPtr = reinterpret_cast<void*>          (shell::RunBundleAndSnapshotFromLibrary),
},
複製程式碼

檢視 RunBundleAndSnapshotFromLibrary,這裡刪除了一些我們不關心的邏輯

static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
                                            jobject jcaller,
                                            jlong shell_holder,
                                            jobjectArray jbundlepaths,
                                            jstring jEntrypoint,
                                            jstring jLibraryUrl,
                                            jobject jAssetManager) {
    auto asset_manager = std::make_shared<blink::AssetManager>();      
       for (const auto& bundlepath :
       fml::jni::StringArrayToVector(env, jbundlepaths)) {
    const auto file_ext_index = bundlepath.rfind(".");
    if (bundlepath.substr(file_ext_index) == ".zip") {
      asset_manager->PushBack(
          std::make_unique<blink::ZipAssetStore>(bundlepath));
    } else {
      asset_manager->PushBack(
          std::make_unique<blink::DirectoryAssetBundle>(fml::OpenDirectory(
              bundlepath.c_str(), false, fml::FilePermission::kRead)));
      const auto last_slash_index = bundlepath.rfind("/", bundlepath.size());
      if (last_slash_index != std::string::npos) {
        auto apk_asset_dir = bundlepath.substr(
            last_slash_index + 1, bundlepath.size() - last_slash_index);

        asset_manager->PushBack(std::make_unique<blink::APKAssetProvider>(
            env,                       // jni environment
            jAssetManager,             // asset manager
            std::move(apk_asset_dir))  // apk asset dir
        );
      }
    }
  }      
  auto isolate_configuration = CreateIsolateConfiguration(*asset_manager);    
  RunConfiguration config(std::move(isolate_configuration),
                          std::move(asset_manager));  
    ANDROID_SHELL_HOLDER->Launch(std::move(config)); 
複製程式碼

首先會對資源路徑進行處理 會分為 zip包或者資料夾進行分別處理。最終會呼叫常量ANDROID_SHELL_HOLDERLaunch 函式.

最終走到 engineRun 函式。

這裡有 2 個函式比較重要,先是 IsolateConfiguration::PrepareIsolate, 然後是 RunFromLibrary 或者 Run 函式

跟到 PrepareAndLaunchIsolate 函式,檢視原始碼

bool IsolateConfiguration::PrepareIsolate(blink::DartIsolate& isolate) {
  if (isolate.GetPhase() != blink::DartIsolate::Phase::LibrariesSetup) {
    FML_DLOG(ERROR)
        << "Isolate was in incorrect phase to be prepared for running.";
    return false;
  }

  return DoPrepareIsolate(isolate);
}
複製程式碼

而有 DoPrepareIsolate 函式的類 Configuration 類有3個

  • AppSnapshotIsolateConfiguration
  • KernelIsolateConfiguration
  • KernelListIsolateConfiguration

他們分別會呼叫 DartIsolate

  • PrepareForRunningFromPrecompiledCode
  • PrepareForRunningFromKernel

這2個方法的一個,可以看到這裡的prepare操作分成了 預先載入的程式碼從核心獲取 2種

至於 RunFromLibrary 函式和 Run 函式

我們能看到他們最終都會呼叫 dart:isolate_startMainIsolate 的邏輯:

Dart_Handle isolate_lib = Dart_LookupLibrary(tonic::ToDart("dart:isolate"));
if (tonic::LogIfError(Dart_Invoke(
          isolate_lib, tonic::ToDart("_startMainIsolate"),
          sizeof(isolate_args) / sizeof(isolate_args[0]), isolate_args))) {
    return false;
  }
複製程式碼

這裡說明我們正在執行呼叫 Dart 的入口方法。而 RunRunFromLibrary 的區別,則是如果我們傳入了 entrypoint 引數去進行 Flutter 的 bundle 初始化的時候,則會去載入我們制定的 library。

小結

到這裡, Flutter 的初始化流程就就簡單的分析了一遍。大致可以總結成三個部分

  1. 初始化 FlutterMain
  2. 初始化 FlutterView,開始載入 bundle
  3. 初始化Flutter Bundle,這裡獲取了 Flutter 的入口方法、Flutter 的 library, 以及對 Flutter 入口方法的呼叫。

初始化的邏輯比較複雜,對後續一些初始化相關的效能優化應該也會有不小的啟發。FlutterMain 中對資源的處理和寫入本地的邏輯也給 Android 端研究 Flutter 動態化提供了基礎。

很多 bundle 載入和後續初始化的邏輯也還沒有完全弄清楚和深入研究。有興趣的朋友可以一起研究,共同探討。