Flutter 2 Router 從入門到放棄 - 實現(xiàn)原理與源碼分析(一)
前言
在上一篇文章Flutter 2 Router 從入門到放棄 - 基本使用、區(qū)別&優(yōu)勢中,主要講了多引擎混合開發(fā)的基本用法以及多引擎和單引擎混合開發(fā)的區(qū)別,本文我們主要通過源碼看看多引擎復用是如何實現(xiàn)。
一、Flutter 2 源碼編譯調(diào)試
工欲善其事,必先利其器,這里我們先對源碼編譯和調(diào)試步驟進行說明:
源碼編譯
安裝 depot_tools,配置環(huán)境變量
- git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
 - export PATH=/path/to/depot_tools:$PATH
 
創(chuàng)建空的 engine 目錄并在目錄中創(chuàng)建 .gclient 配置文件,在 .gclient 中配置從 flutter/engine 主工程 fork 出的 github 工程地址,.gclient 配置如下
- solutions = [
 - {
 - "managed": False,
 - "name": "src/flutter",
 - "url": "https://github.com/Alex0605/engine.git",
 - "custom_deps": {},
 - "deps_file": "DEPS",
 - "safesync_url": "",
 - },
 - ]
 
在 engine 目錄中執(zhí)行 gclient sync
切換源碼。編譯前的一個重要操作是將源碼切換到本地 Flutter SDK 的 engine version 對應的提交點
- # 查看本地 Flutter SDK 引擎版本, 這個文件中是包含對應的 commit id
 - vim src/flutter/bin/internal/engine.version
 - # 調(diào)整代碼
 - cd engine/src/flutter
 - git reset --hard <commit id>
 - gclient sync -D --with_branch_heads --with_tags
 - # 準備構(gòu)建文件
 - cd engine/src
 - #Android
 - # 使用以下命令生成 host_debug_unopt 編譯配置
 - ./flutter/tools/gn --unoptimized
 - # android arm (armeabi-v7a) 編譯配置
 - ./flutter/tools/gn --android --unoptimized
 - # android arm64 (armeabi-v8a) 編譯配置
 - ./flutter/tools/gn --android --unoptimized --runtime-mode=debug --android-cpu=arm64
 - # 編譯
 - ninja -C out/host_debug_unopt -j 16
 - ninja -C out/android_debug_unopt -j 16
 - ninja -C out/android_debug_unopt_arm64 -j 16
 - #iOS
 - # unopt-debug
 - ./flutter/tools/gn --unoptimized --ios --runtime-mode debug --ios-cpu arm
 - ./flutter/tools/gn --unoptimized --ios --runtime-mode debug --ios-cpu arm64
 - ./flutter/tools/gn --unoptimized --runtime-mode debug --ios-cpu arm
 - ./flutter/tools/gn --unoptimized --runtime-mode debug --ios-cpu arm64
 - ninja -C out/ios_debug_unopt_arm
 - ninja -C out/ios_debug_unopt
 - ninja -C out/host_debug_unopt_arm
 - ninja -C out/host_debug_unopt
 
編譯完成后的目錄如下:

源碼運行調(diào)試
通過命令創(chuàng)建一個 flutter 工程
flutter create --org com.wedotor.flutter source_code
用 android studio 打開創(chuàng)建的 android 工程
在 gradle.properties 文件中添加 localEngineOut 屬性,配置如下:
- org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
 - android.useAndroidX=true
 - android.enableJitfier=true
 - localEngineOut=/Users/zhoujh/myproj/3-proj/flutter/engine/src/out/android_debug_unopt_arm64
 
將 engine/src/flutter/shell/platform/android 工程(稱之為* Flutter 引擎工程*)導入到 Android Studio
使用自定義 Flutter 引擎運行 Flutter App(稱之為 Flutter App 工程),具體如 1-3 步所述
Flutter 引擎工程 中給源碼設置斷點并啟動 Debugger 連接到已啟動的 Flutter App 進程
PS:這里 C++ 代碼我用的是 Clion 閱讀,這里配置比較簡單,將上面生成的 compile_commands.json 文件復制到 src/flutter 目錄中,然后使用 Clion 打開項目,indexing 之后便可以跟蹤跳轉(zhuǎn)
二、Flutter 2 源碼閱讀
進行源碼分析之前,先了解一下官方文檔中提供的核心架構(gòu)圖,它也代表著整個 Flutter 架構(gòu)。

Flutter 的架構(gòu)主要分成三層:Framework,Engine 和 Embedder。
1)、Framework:Framework 使用 dart 實現(xiàn),包括 Material Design 風格的 Widget,Cupertino(針對 iOS)風格的 Widgets,文本/圖片/按鈕等基礎(chǔ) Widgets,渲染,動畫,手勢等。此部分的核心代碼是:flutter 倉庫下的 flutter package,以及 sky_engine 倉庫下的 io,async ,ui (dart:ui 庫提供了 Flutter 框架和引擎之間的接口)等 package。其中 dart:ui 庫是對 Engine 中 Skia 庫的 C++ 接口的綁定。向上層提供了 window、text、canvas 等通用的繪制能力,通過 dart:ui 庫就能使用 Dart 代碼操作 Skia 繪制引擎。所以我們實際上可以通過實例化 dart:ui 包中的類(例如 Canvas、Paint 等)來繪制界面。然而,除了繪制,還要考慮到協(xié)調(diào)布局和響應觸摸等情況,這一切實現(xiàn)起來都異常麻煩,這也正是 Framework 幫我們做的事。渲染層 Rendering 是在 ::dart:ui 庫之上的第一個抽象層,它為你做了所有繁重的數(shù)學工作。為了做到這一點,它使用 RenderObject 對象,該對象是真正繪制到屏幕上的渲染對象。由這些 RenderObject 組成的樹處理真正的布局和繪制。
2)、Engine:Engine 使用 C++ 實現(xiàn),主要包括:Skia,Dart 和 Text。Skia 是開源的二維圖形庫,提供了適用于多種軟硬件平臺的通用 API。在安卓上,系統(tǒng)自帶了 Skia,在 iOS 上,則需要 APP 打包 Skia 庫,這會導致 Flutter 開發(fā)的 iOS 應用安裝包體積更大。Dart 運行時則可以以 JIT、JIT Snapshot 或者 AOT 的模式運行 Dart 代碼。
3)、Embedder:Embedder 是一個嵌入層,即把 Flutter 嵌入到各個平臺上去,這里做的主要工作包括渲染 Surface 設置,線程設置,以及插件等。從這里可以看出,F(xiàn)lutter 的平臺相關(guān)層很低,平臺(如 iOS)只是提供一個畫布,剩余的所有渲染相關(guān)的邏輯都在 Flutter 內(nèi)部,這就使得它具有了很好的跨端一致性。
2、啟動 app 時會在 Application onCreate 方法中創(chuàng)建 FlutterEngineGroup 對象
- public void onCreate() {
 - super.onCreate();
 - // 創(chuàng)建 FlutterEngineGroup 對象
 - engineGroup = new FlutterEngineGroup(this);
 - }
 
3、在創(chuàng)建 FlutterEngineGroup 時,使通過該引擎組創(chuàng)建的子引擎共享資源,比單獨通 FlutterEngine 構(gòu)造函數(shù)創(chuàng)建,創(chuàng)建速度的更快、占用內(nèi)存更少,在創(chuàng)建或重新創(chuàng)建第一個引擎時,行為與通過 FlutterEngine 構(gòu)造函數(shù)創(chuàng)建相同。當創(chuàng)建后續(xù)的引擎時,會重新使用現(xiàn)有的引擎中的資源。共享資源會一直保留到最后一個引擎被銷毀。刪除 FlutterEngineGroup 不會使其現(xiàn)有的已創(chuàng)建引擎失效,但它無法再創(chuàng)建更多的 FlutterEngine。
- //src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
 - public FlutterEngineGroup(@NonNull Context context, @Nullable String[] dartVmArgs) {
 - FlutterLoader loader = FlutterInjector.instance().flutterLoader();
 - if (!loader.initialized()) {
 - loader.startInitialization(context.getApplicationContext());
 - loader.ensureInitializationComplete(context, dartVmArgs);
 - }
 - }
 
4、FlutterLoader 的 startInitialization 將加載 Flutter 引擎的本機庫 flutter.so 以啟用后續(xù)的 JNI 調(diào)用。還將查找解壓打包在 apk 中的 dart 資源,而且方法只會被調(diào)用一次。該方法具體調(diào)用步驟:
1)、settings 屬性是否賦值來確定方法是否執(zhí)行過;
2)、方法必須在主線程中執(zhí)行,否則拋異常退出;
3)、獲取 app 上下文;
4)、VsyncWaiter 是同步幀率相關(guān)的操作;
5)、記錄初始化耗時時間;
6)、從 flutter2 開始,初始化配置、初始化資源、加載 flutter.so 動態(tài)庫,都放在后臺子線程中運行,加快了初始化速度。
- //src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
 - public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
 - //初始化方法只能運行一次
 - if (this.settings != null) {
 - return;
 - }
 - //必須在主線程上調(diào)用 startInitialization
 - if (Looper.myLooper() != Looper.getMainLooper()) {
 - throw new IllegalStateException("startInitialization must be called on the main thread");
 - }
 - // 獲取 app 的上下文
 - final Context appContext = applicationContext.getApplicationContext();
 - this.settings = settings;
 - initStartTimestampMillis = SystemClock.uptimeMillis();
 - //獲取 app 相關(guān)信息
 - flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
 - VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
 - .init();
 - //將后臺線程用于需要磁盤訪問的初始化任務
 - Callable<InitResult> initTask =
 - new Callable<InitResult>() {
 - @Override
 - public InitResult call() {
 - //獲取配置資源
 - ResourceExtractor resourceExtractor = initResources(appContext);
 - //加載 fluter 本地 so 庫
 - flutterJNI.loadLibrary();
 - Executors.newSingleThreadExecutor()
 - .execute(
 - new Runnable() {
 - @Override
 - public void run() {
 - //預加載 skia 字體庫
 - flutterJNI.prefetchDefaultFontManager();
 - }
 - });
 - if (resourceExtractor != null) {
 - //等待初始化時的資源初始化完畢后才會向下執(zhí)行,否則會一直阻塞
 - resourceExtractor.waitForCompletion();
 - }
 - return new InitResult(
 - PathUtils.getFilesDir(appContext),
 - PathUtils.getCacheDirectory(appContext),
 - PathUtils.getDataDirectory(appContext));
 - }
 - };
 - initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
 - }
 
5、initResources:將 apk 中的資源文件復制到應用本地文件中,在 DEBUG 或者在 JIT_RELEASE 模式下安裝 Flutter 資源,主要由 ResourceExtractor 來異步執(zhí)行資源文件的解壓縮操作,最終會將 apk 中 assets 中的 Dart 資源 vm_snapshot_data、isolate_snapshot_data、kernel_blob.bin 文件安裝到應用目錄 app_flutter 目錄下。
- private ResourceExtractor initResources(@NonNull Context applicationContext) {
 - ResourceExtractor resourceExtractor = null;
 - if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
 - //獲取 flutter 數(shù)據(jù)存儲路徑
 - final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
 - //獲取包名
 - final String packageName = applicationContext.getPackageName();
 - final PackageManager packageManager = applicationContext.getPackageManager();
 - final AssetManager assetManager = applicationContext.getResources().getAssets();
 - resourceExtractor =
 - new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
 - resourceExtractor
 - .addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData))
 - .addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData))
 - .addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
 - resourceExtractor.start();
 - }
 - return resourceExtractor;
 - }
 
6、在初始化 startInitialization 時,也會調(diào)用 ensureInitializationComplete 方法確認初始化是否完成,然后里面會把 so 文件的地址給到 shellArgs 里傳入 FlutterJNI,因此我們可以通過修改 flutter 生成的代碼或者使用 hook 等方式替換 ListshellArgs 的 add 方法,從而改變 so 的路徑,進行熱修復。
- public void ensureInitializationComplete(
 - @NonNull Context applicationContext, @Nullable String[] args) {
 - if (initialized) {
 - return;
 - }
 - if (Looper.myLooper() != Looper.getMainLooper()) {
 - throw new IllegalStateException(
 - "ensureInitializationComplete must be called on the main thread");
 - }
 - if (settings == null) {
 - throw new IllegalStateException(
 - "ensureInitializationComplete must be called after startInitialization");
 - }
 - try {
 - InitResult result = initResultFuture.get();
 - List<String> shellArgs = new ArrayList<>();
 - // 此處省略具體參數(shù)配置代碼...
 - long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
 - // 初始化 JNI
 - flutterJNI.init(
 - applicationContext,
 - shellArgs.toArray(new String[0]),
 - kernelPath,
 - result.appStoragePath,
 - result.engineCachesPath,
 - initTimeMillis);
 - initialized = true;
 - } catch (Exception e) {
 - Log.e(TAG, "Flutter initialization failed.", e);
 - throw new RuntimeException(e);
 - }
 - }
 
FlutterJNI 初始化
- public void init(
 - @NonNull Context context,
 - @NonNull String[] args,
 - @Nullable String bundlePath,
 - @NonNull String appStoragePath,
 - @NonNull String engineCachesPath,
 - long initTimeMillis) {
 - if (FlutterJNI.initCalled) {
 - Log.w(TAG, "FlutterJNI.init called more than once");
 - }
 - //調(diào)用 JNI 中 flutter 初始化方法
 - FlutterJNI.nativeInit(
 - context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis);
 - FlutterJNI.initCalled = true;
 - }
 
7、在初始化資源之后就開始加載 flutter.so,這個就是 Flutter Engine 源碼編譯后的產(chǎn)物。當運行時,它被 Android 虛擬機加載到虛擬內(nèi)存中。(so 是一個標準的 ELF 可執(zhí)行文件,主要分為 .data 和 .text 段,分別包含了數(shù)據(jù)和指令,加載到虛擬內(nèi)存后,指令可以被 CPU 執(zhí)行) 加載了 flutter.so 之后,最先被執(zhí)行的是里面的 JNI_OnLoad 方法 ,會注冊 FlutterMain 、PlatformView、VSyncWaiter 的 jni 方法。
- //src/flutter/shell/platform/android/library_loader.cc
 - JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
 - // 開始進行 Java VM 的初始化,是保存當前的 Java VM 對象到一個全局的變量中
 - fml::jni::InitJavaVM(vm);
 - //把當前的 thread 和 JavaVM 關(guān)聯(lián)起來
 - JNIEnv* env = fml::jni::AttachCurrentThread();
 - bool result = false;
 - // 注冊 FlutterMain,就是把 Java 層的 native 方法和 C++層的方法關(guān)聯(lián)起來
 - result = flutter::FlutterMain::Register(env);
 - FML_CHECK(result);
 - // 注冊 PlatformView
 - result = flutter::PlatformViewAndroid::Register(env);
 - FML_CHECK(result);
 - // 注冊 VSyncWaiter.
 - result = flutter::VsyncWaiterAndroid::Register(env);
 - FML_CHECK(result);
 - return JNI_VERSION_1_4;
 - }
 
系統(tǒng)初始化完成之后,會調(diào)用 NativeInit 這個 native方法,對應的 FlutterMain.cc::Init 方法。這里初始化主要是根據(jù)傳入的參數(shù)生成了一個 Settings 對象。
- // src/flutter/shell/platform/android/flutter_main.cc
 - void FlutterMain::Init(JNIEnv* env,
 - jclass clazz,
 - jobject context,
 - jobjectArray jargs,
 - jstring kernelPath,
 - jstring appStoragePath,
 - jstring engineCachesPath,
 - jlong initTimeMillis) {
 - std::vector<std::string> args;
 - args.push_back("flutter");
 - for (auto& arg : fml::jni::StringArrayToVector(env, jargs)) {
 - args.push_back(std::move(arg));
 - }
 - auto command_line = fml::CommandLineFromIterators(args.begin(), args.end());
 - auto settings = SettingsFromCommandLine(command_line);
 - int64_t init_time_micros = initTimeMillis * 1000;
 - settings.engine_start_timestamp =
 - std::chrono::microseconds(Dart_TimelineGetMicros() - init_time_micros);
 - flutter::DartCallbackCache::SetCachePath(
 - fml::jni::JavaStringToString(env, appStoragePath));
 - fml::paths::InitializeAndroidCachesPath(
 - fml::jni::JavaStringToString(env, engineCachesPath));
 - flutter::DartCallbackCache::LoadCacheFromDisk();
 - if (!flutter::DartVM::IsRunningPrecompiledCode() && kernelPath) {
 - auto application_kernel_path =
 - fml::jni::JavaStringToString(env, kernelPath);
 - if (fml::IsFile(application_kernel_path)) {
 - settings.application_kernel_asset = application_kernel_path;
 - }
 - }
 - settings.task_observer_add = [](intptr_t key, fml::closure callback) {
 - fml::MessageLoop::GetCurrent().AddTaskObserver(key, std::move(callback));
 - };
 - settings.task_observer_remove = [](intptr_t key) {
 - fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
 - };
 - settings.log_message_callback = [](const std::string& tag,
 - const std::string& message) {
 - __android_log_print(ANDROID_LOG_INFO, tag.c_str(), "%.*s",
 - (int)message.size(), message.c_str());
 - };
 - #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
 - auto make_mapping_callback = [](const uint8_t* mapping, size_t size) {
 - return [mapping, size]() {
 - return std::make_unique<fml::NonOwnedMapping>(mapping, size);
 - };
 - };
 - settings.dart_library_sources_kernel =
 - make_mapping_callback(kPlatformStrongDill, kPlatformStrongDillSize);
 - #endif
 - //創(chuàng)建 Flutter 全局變量
 - g_flutter_main.reset(new FlutterMain(std::move(settings)));
 - g_flutter_main->SetupObservatoryUriCallback(env);
 - }
 
從 args 解析出 Settings 的過程在 flutter_engine/shell/common/switches.cc,這里最重要的是 snapshot 路徑的構(gòu)建,構(gòu)建完成的路徑就是進程初始化拷貝到本地的路徑, 最后生成了一個 FlutterMain 對象保存在全局靜態(tài)變量中。
- if (aot_shared_library_name.size() > 0) {
 - for (std::string_view name : aot_shared_library_name) {
 - settings.application_library_path.emplace_back(name);
 - }
 - } else if (snapshot_asset_path.size() > 0) {
 - settings.vm_snapshot_data_path =
 - fml::paths::JoinPaths({snapshot_asset_path, vm_snapshot_data_filename});
 - settings.vm_snapshot_instr_path = fml::paths::JoinPaths(
 - {snapshot_asset_path, vm_snapshot_instr_filename});
 - settings.isolate_snapshot_data_path = fml::paths::JoinPaths(
 - {snapshot_asset_path, isolate_snapshot_data_filename});
 - settings.isolate_snapshot_instr_path = fml::paths::JoinPaths(
 - {snapshot_asset_path, isolate_snapshot_instr_filename});
 - }
 
后記
以上主要是 Flutter 2 FlutterEngineGroup 初始化的過程,下一節(jié)我們開始學習通過 FlutterEngineGroup創(chuàng)建 FlutterEngine 并綁定的 UI 頁面的流程。















 
 
 









 
 
 
 