FlutterEngine初始化流程剖析

Author Avatar
第五季 2020.5.27
字数:2,268字 时长:10分钟
  • 微信扫一扫分享

Flutter框架介绍

Flutter框架分三层即Framework,Engine, Embedder

FlutterArt

Framework使用dart语言实现,包括UI,文本,图片,按钮等Widgets,渲染,动画,手势等。此部分的核心代码是flutter仓库下的flutter package,以及sky_engine仓库下的 io, async, ui(dart:ui库提供了Flutter框架和引擎之间的接口)等package。

Engine使用C++实现,是Flutter的核心引擎,主要包括Skia图形引擎、Dart运行时环境Dart VM、Text文本渲染引擎等。

Embedder是一个嵌入层,通过该层把Flutter嵌入到各个平台上去,Embedder的主要工作包括渲染Surface设置, 线程设置,以及插件等。平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。

FlutterEngine初始化流程剖析

既然FlutterEngine是整个flutter框架的核心,想要深入理解flutter跨平台UI渲染能力就必须对引擎逻辑有所了解以及掌握重要细节流程。下面我们就以android平台为例开始深入剖析FlutterEngine的启动流程。整个Flutter Engine源码包含了非常多的东西,DartVM、Render、Channel、Event等等,所以我们只能从相关的点去看,那么从FlutterEngine初始化来说,我们主要关注和Java层有关联的一些操作。相关的代码主要在flutter/shell/platform/android/io/flutter目录下。

FlutterEngine初始化

FlutterApplication启动时使用了Java层的FlutterMain来进行初始化。代码位置在flutter/shell/platform/android/io/flutter/app/FlutterApplication.java

1
2
3
4
public void onCreate() {
super.onCreate();
FlutterMain.startInitialization(this);
}

FlutterMain的startInitialization的最终调用方法为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void startInitialization(@NonNull Context applicationContext, @NonNull FlutterMain.Settings settings) {
if (!isRunningInRobolectricTest) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
} else if (sSettings == null) {
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
initConfig(applicationContext);
initResources(applicationContext);
System.loadLibrary("flutter");
VsyncWaiter.getInstance((WindowManager)applicationContext.getSystemService("window")).init();
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
}
}
}

从以上代码我们可以看出flutter框架只会在主线程初始化,这也很好理解因为flutter本质是一个跨平台UI框架。初始化的过程也很简单一是配置一些启动参数,二是加载相关资源文件,还有就是注册GPU垂直同步信号。

flutter配置项与资源加载

initConfig

1
2
3
4
5
6
7
8
9
private static void initConfig(@NonNull Context applicationContext) {
Bundle metadata = getApplicationInfo(applicationContext).metaData;
if (metadata != null) {
sAotSharedLibraryName = metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, "libapp.so");
sFlutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, "flutter_assets");
sVmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, "vm_snapshot_data");
sIsolateSnapshotData = metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, "isolate_snapshot_data");
}
}

从AndroidManfest.xml配置的applicaion节点获取meta-data数据,初始化以下默认值。这些值都是FlutterEngine加载过程中使用到的name,例如,抽取apk中asset资源时,flutter_assets打包目录,打包产物data名称。

1
2
3
4
private static String sAotSharedLibraryName = "libapp.so";
private static String sVmSnapshotData = "vm_snapshot_data";
private static String sIsolateSnapshotData = "isolate_snapshot_data";
private static String sFlutterAssetsDir = "flutter_assets";

initResources

1
2
3
4
5
6
7
8
9
10
private static void initResources(@NonNull Context applicationContext) {
(new ResourceCleaner(applicationContext)).start();
String dataDirPath = PathUtils.getDataDirectory(applicationContext);
String packageName = applicationContext.getPackageName();
PackageManager packageManager = applicationContext.getPackageManager();
AssetManager assetManager = applicationContext.getResources().getAssets();
sResourceExtractor = new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
sResourceExtractor.addResource(fromFlutterAssets(sVmSnapshotData)).addResource(fromFlutterAssets(sIsolateSnapshotData)).addResource(fromFlutterAssets("kernel_blob.bin"));
sResourceExtractor.start();
}

在Flutter打包apk的asset目录下,包括fluttter_asset目录/资源项,将资源从apk中抽取,保存在Context.getDir(“flutter”, 0)目录下。

flutter.so加载

在初始化Flutter App资源之后加载了flutter.so,这个就是Flutter Engine源码编译后的产物。 在我们编译Flutter App时,它存在Flutter SDK的flutter.jar中,当生产APK之后,它存在于APK的lib目录下。而当运行时,它被Android虚拟机加载到虚拟内存中。我们知道系统执行完System.loadLibrary后会在C++层执行一个回调函数就是JNI_OnLoad,所以在flutter引擎源码中找到相关函数,代码位于flutter/shell/platform/android/library_loader.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// This is called by the VM when the shared library is first loaded.*
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {

*// Initialize the Java VM.*
fml::jni::InitJavaVM(vm);
JNIEnv* env = fml::jni::AttachCurrentThread();

bool result = false;
*// Register FlutterMain.*
result = flutter::FlutterMain::Register(env);
FML_CHECK(result);

*// Register PlatformView*
result = flutter::PlatformViewAndroid::Register(env);
FML_CHECK(result);

*// Register VSyncWaiter.*
result = flutter::VsyncWaiterAndroid::Register(env);
FML_CHECK(result);

return JNI_VERSION_1_4;

}

一开始进行了Java VM的初始化,其实保存一下当前的Java VM对象到一个全局的变量中, JavaVM的初始化在zygote进程中已经进行了,每个Android进程都是fork出来的。

1
2
3
4
5
6
static JavaVM* g_jvm = nullptr;

void InitJavaVM(JavaVM* vm) {
FML_DCHECK(g_jvm == nullptr);
g_jvm = vm;
}

一个进程中只有一个JavaVM对象,如果要在线程中访问JavaVM,就需要把当前的thread和JavaVM关联起来。所以调用AttachCurrentThread 我们可以得到一个JNIEnv对象, 每个线程都有一个。JNIEnv定义了很多和JNI调用相关的方法。

1
2
3
4
5
6
7
8
JNIEnv* AttachCurrentThread() {
FML_DCHECK(g_jvm != nullptr)
<< "Trying to attach to current thread without calling InitJavaVM first.";
JNIEnv* env = nullptr;
jint ret = g_jvm->AttachCurrentThread(&env, nullptr);
FML_DCHECK(JNI_OK == ret);
return env;
}

后面调用了三个Register方法,作用是注册jni方法,JNI_OnLoad中完成的是jni方法的动态注册,如果没有进行动态注册,JavaVM会默认按照java方法的package name + method name + params 来映射c++方法,这也是一种注册方式-静态注册。由于三个注册方法类似这里重点介绍一下 FlutterMain::Register方法,代码位于flutter/shell/platform/android/flutter_main.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool FlutterMain::Register(JNIEnv* env) {
static const JNINativeMethod methods[] = {
{
.name = "nativeInit",
.signature = "(Landroid/content/Context;[Ljava/lang/String;Ljava/"
"lang/String;Ljava/lang/String;Ljava/lang/String;)V",
.fnPtr = reinterpret_cast<void*>(&Init),
},
{
.name = "nativeRecordStartTimestamp",
.signature = "(J)V",
.fnPtr = reinterpret_cast<void*>(&RecordStartTimestamp),
},
};

jclass clazz = env->FindClass("io/flutter/embedding/engine/FlutterJNI");

if (clazz == nullptr) {
return false;
}

return env->RegisterNatives(clazz, methods, fml::size(methods)) == 0;
}

这个方法就是将C++方法Init和RecordStartTimestamp方法分别与ava层FlutterJNI中的nativeInit和nativeRecordStartTimestamp方法绑定,这样就能通过jni实现java代码调用C++代码。另外两个Register方法的作用是一样的,也是注册对应的native方法,具体代码在:
flutter_engine/shell/platform/android/platform_view_android.h
flutter_engine/shell/platform/android/vsync_waiter_android.h

C++层的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
void FlutterMain::Init(JNIEnv* env,
jclass clazz,
jobject context,
jobjectArray jargs,
jstring kernelPath,
jstring appStoragePath,
jstring engineCachesPath) {
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);

// Restore the callback cache.
// TODO(chinmaygarde): Route all cache file access through FML and remove this
// setter.
flutter::DartCallbackCache::SetCachePath(
fml::jni::JavaStringToString(env, appStoragePath));

fml::paths::InitializeAndroidCachesPath(
fml::jni::JavaStringToString(env, engineCachesPath));

flutter::DartCallbackCache::LoadCacheFromDisk();

if (!flutter::DartVM::IsRunningPrecompiledCode() && kernelPath) {
// Check to see if the appropriate kernel files are present and configure
// settings accordingly.
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);
};

#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
// There are no ownership concerns here as all mappings are owned by the
// embedder and not the engine.
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 // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG

// Not thread safe. Will be removed when FlutterMain is refactored to no
// longer be a singleton.
g_flutter_main.reset(new FlutterMain(std::move(settings)));

g_flutter_main->SetupObservatoryUriCallback(env);
}

上述代码只是将java层传过来的初始化参数校验拼装成flutter::Settings对象,然后在创建FlutterMain时传入。查看FlutterMain的构造函数可以看到并没有什么特殊操作,只是将setting对象赋值给常量成员_settings

1
2
FlutterMain::FlutterMain(flutter::Settings settings)
: settings_(std::move(settings)), observatory_uri_callback_() {}

注册垂直同步信号

startInitialization方法中初始化了GPU垂直同步信号,用来将GPU发出的渲染信号通知给引擎和Dart层。

1
VsyncWaiter.getInstance((WindowManager)applicationContext.getSystemService("window")).init();

在android平台上Choreographer类管理GPU帧信号与View绘制相关工作,它有个doFrame回调通知业务层某一帧开始绘制,其实VsyncWaiterinit方法就是向Choreographer注册了一个doFrame回调。

1
2
3
4
5
6
7
8
9
10
11
private final AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate = new AsyncWaitForVsyncDelegate() {
public void asyncWaitForVsync(final long cookie) {
Choreographer.getInstance().postFrameCallback(new FrameCallback() {
public void doFrame(long frameTimeNanos) {
float fps = VsyncWaiter.this.windowManager.getDefaultDisplay().getRefreshRate();
long refreshPeriodNanos = (long)(1.0E9D / (double)fps);
FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
}
});
}
};

由以上代码可以看到在GPU发出Vsync信号后,会通过JNI调用C++层渲染逻辑,即上述代码中FlutterJNI.nativeOnVsync完成的逻辑

总结

纵观FlutterApplication中的初始化逻辑,并没有很复杂和耗时的操作,都是一些准备工作,如下图:

FlutterApplication_create

总结其主要工作有以下几点:

  1. 确保该方法必须运行在主线程,否则抛出异常;
  2. 初始化配置参数,vm_snapshot_data、vm_snapshot_instr、isolate_snapshot_data、isolate_snapshot_instr、flutter_assets的值
  3. 获取应用根目录下的所有assets资源路径,提取产物资源,加载到内存;
  4. 加载libflutter.so库,调用JNI_OnLoad();
    • Android进程自身会创建JavaVM,此处将当前进程的JavaVM实例保存在静态变量g_jvm,再将当前线程和JavaVM建立关联
    • 注册FlutterMain的JNI方法,nativeInit()和nativeRecordStartTimestamp()
    • 注册PlatformViewAndroid的一系列方法,完成Java和C++的一些方法互调;
    • 注册VSyncWaiter的nativeOnVsync()用于Java调用C++,asyncWaitForVsync()用于C++调用Java;
  5. 最终将FlutterApplication的启动耗时记录,通过engine_main_enter_ts变量。