摘要: 做者:閒魚技術-然道 1. 引言 最近在作性能優化的時候發現,在混合棧開發中,第一次啓動Flutter頁面的耗時總會是第二次啓動Flutter頁面耗時的兩倍左右,這樣給人感受很很差。分析發現第一次啓動Flutter頁面會作一些初始化工做,藉此,我梳理了下Flutter的初始化流程。java
做者:閒魚技術-然道android
最近在作性能優化的時候發現,在混合棧開發中,第一次啓動Flutter頁面的耗時總會是第二次啓動Flutter頁面耗時的兩倍左右,這樣給人感受很很差。分析發現第一次啓動Flutter頁面會作一些初始化工做,藉此,我梳理了下Flutter的初始化流程。shell
Flutter初始化主要分四部分,FlutterMain初始化、FlutterNativeView初始化、FlutterView初始化和Flutter Bundle初始化。
咱們先看下Flutter初始化的時序圖,來總體把握下Flutter初始化的通常流程: 性能優化
Flutter初始化時序app
這部分初始化工做是由Application.onCreate方法中調用開始的,在Application建立的時候就會初始化完成,不會影響Flutter頁面的第一次啓動,因此這裏只是作一個簡單分析。
從FlutterMain.startInitialization方法代碼中能夠輕易看出來,初始化主要分四部分。
前面三部分比較相似,分別是初始化配置信息、初始化AOT編譯和初始化資源,最後一部分則是加載Flutter的Native環境。
這部分感興趣的同窗能夠看下FlutterMain.java源碼,邏輯仍是比較清晰的。ide
public static void startInitialization(Context applicationContext, Settings settings) { // other codes ... initConfig(applicationContext); initAot(applicationContext); initResources(applicationContext); System.loadLibrary("flutter"); // other codes ... }
先用一個圖來展示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。
FlutterView的初始化就是純粹的Android層啦,因此相對比較簡單。分析FlutterView.java的構造函數就會發現,整個FlutterView的初始化在確保FlutterNativeView的建立成功和一些必要的view設置以外,主要作了兩件事:
1. 註冊SurfaceHolder監聽,其中surfaceCreated回調會做爲Flutter的第一幀回調使用。
2. 初始化了Flutter系統須要用到的一系列橋接方法。例如:localization、navigation、keyevent、system、settings、platform、textinput。
FlutterView初始化流程主要以下圖所示:
FlutterView初始化
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的入口方法。
本次主要分析了下FlutterActivity的onCreate方法中的Flutter初始化部分邏輯,很明顯會發現主要耗時在FlutterNativeView、FlutterView和Flutter Bundle的初始化這三塊,將這三部分的初始化工做前置就能夠比較容易的解決引言中提出的問題。經測試發現,這樣改動以後,Flutter頁面第一次啓動時長和後面幾回啓動時長差很少同樣了。
對於FlutterMain.startInitialization的初始化邏輯、SendStartMessage發送的消息如何最終調用Flutter中的入口方法邏輯沒有進一步深刻分析,這些內容後續再繼續分析撰文分享。