作者:閑魚技術-然道
1. 引言
最近在做性能優化的時候發現,在混合棧開發中,第一次啟動Flutter頁面的耗時總會是第二次啟動Flutter頁面耗時的兩倍左右,這樣給人感覺很不好。分析發現第一次啟動Flutter頁面會做一些初始化工作,借此,我梳理了下Flutter的初始化流程。
2. Flutter初始化時序
Flutter初始化主要分四部分,FlutterMain初始化、FlutterNativeView初始化、FlutterView初始化和Flutter Bundle初始化。
我們先看下Flutter初始化的時序圖,來整體把握下Flutter初始化的一般流程:
Flutter初始化時序
3. 具體分析
3.1 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 ...
}
3.2 FlutterNativeView初始化
先用一個圖來展現FlutterNativeView構造函數的調用棧:
FlutterNativeView構造函數調用棧
從上圖的調用棧中我們知道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方法都做了些什麼呢?
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。
3.3 FlutterView初始化
FlutterView的初始化就是純粹的Android層啦,是以相對比較簡單。分析FlutterView.java的構造函數就會發現,整個FlutterView的初始化在確定FlutterNativeView的建立成功和一些必要的view設定之外,主要做了兩件事:
1. 注冊SurfaceHolder監聽,其中surfaceCreated回調會作為Flutter的第一幀回調使用。
2. 初始化了Flutter系統需要用到的一系列橋接方法。例如:localization、navigation、keyevent、system、settings、platform、textinput。
FlutterView初始化流程主要如下圖所示:
FlutterView初始化
3.4 Flutter Bundle初始化
Flutter Bundle的初始化是由調用FlutterActivityDelegate.runFlutterBundle開始的,先用一張圖來說明下runFlutterBundle方法的調用棧:
Flutter的Bundle初始化
我們再從源碼角度較為深入了解下:
FlutterActivity的onCreate方法在執行完FlutterActivityDelegate的onCreate方法之後會調用它的runFlutterBundle方法。FlutterActivityDelegate.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的入口方法。
4. 總結一下
本次主要分析了下FlutterActivity的onCreate方法中的Flutter初始化部分邏輯,很明顯會發現主要耗時在FlutterNativeView、FlutterView和Flutter Bundle的初始化這三塊,将這三部分的初始化工作前置就可以比較容易的解決引言中提出的問題。經測試發現,這樣改動之後,Flutter頁面第一次啟動時長和後面幾次啟動時長差不多一樣了。
對于FlutterMain.startInitialization的初始化邏輯、SendStartMessage發送的消息如何最終調用Flutter中的入口方法邏輯沒有進一步深入分析,這些内容後續再繼續分析撰文分享。