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 中的入口方法逻辑没有进一步深入分析,这些内容后续再继续分析撰文分享。
本文作者:闲鱼技术 - 然道阅读原文
本文为云栖社区原创内容,未经允许不得转载。