前面三篇文章我们通过Flutter Demo程序,从程序的初始化开始,到MainActivity的初始化(FlutterActivity),然后到FlutterView的初始化, 最后到FlutterView运行一个Flutter Bundle。整个过程中我们都是关注在Java层的代码。 但是通过FlutterJNI类我们知道,很多东西都是在C++层也就是Flutter Engine中实现的, 也就是Flutter.jar中的flutter.so文件。 所以这一篇从FlutterEngine的角度来看看初始化。
一 准备Flutter Engine源码
前面介绍Flutter.jar的时候有简单介绍一下下载Flutter Engine的源码。 源码地址:https://github.com/flutter/engine。建议使用git命令直接下载master分支
git clone https://github.com/flutter/engine.git
然后根据你Flutter SDK的版本来切换到对应的commit
➜ io git:(3757390fa) ✗ flutter --version
Flutter 1.2.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 8661d8aecd (7 weeks ago) • 2019-02-14 19:19:53 -0800
Engine • revision 3757390fa4
Tools • Dart 2.1.2 (build 2.1.2-dev.0.0 0a7dcf17eb)
切换到SDK Engine版本对应的源码,上面就是Flutter SDK 1.2.1对应的Engine的commit ID
flutter_engine git:(master) ✗ git checkout 3757390fa4
HEAD is now at 3757390fa Roll src/third_party/dart ecd7a88606..0a7dcf17eb (4 commits)
2019-8更新:
单纯从git拉去的源码只包含engine本身,但是依赖的一些第三方库都没有。所以如果想看到完整的源码,按照官方的来下载可编译的源码: Setting up the Engine development environment
看源码工具
如果你是在Windows下查看源码,我推荐使用Source Insight ,它可以去解析代码中类之间的调用关系,可以很方便的定位到类的定义和查找引用,以前看Android源码和Framework开发都是使用这个。刚看了下2018年更新到了4.X版本。可惜只能Windows版本。如果你使用Linux或者Mac那就还是使用VS Code 来查看源码吧。
不过用VSCode查看Flutter Engine源码,无法进行代码间的跳转,因为VSCode不知道类之间的关系,这个时候可以使用ctags来生成。Mac上先安装ctags
brew install ctags
其实Mac中预装了ctags,但是是xcode自己版本,命令和标准的有些不一样,所以需要重新安装并设置alias替换系统的
alias ctags="brew –prefix/bin/ctags"
最后可以把alias保存到你的环境变量
alias ctags >> ~/.bashrc
如果你使用了zsh或其他sh,保存到对应的rc文件中,建议是自己新建一个~/.profile文件来放环境变量信息,在不同的sh配置中引用一下就好了。
备注: flutter上推荐使用cquery,但是需要编译flutter engine的环境,直接从个git上拉的代码是无法编译的。
VSCode相关插件
在VSCode中打开终端,使用下面命令生成tags文件,tags文件其实就是源码中class的索引,有了这些索引,有可以建立起跳转的关系。 ctags命令还有一些参数,可以自己研究。当然还有gtags、cscope等多个类是工具,可以自行选择。
安装之后,用CMD+鼠标左键就可以跳转到要查看的类的定义,当然也可以使用右键菜单或者是快捷接盘查找定义和引用。 但是VSCode有个问题,目前不支持鼠标的前进和后退,只能用键盘快捷键,这个实在是很麻烦,所以介绍一个插件: Code Navigation 这个插件会在VSCode底部状态栏上增加2个前进后退按钮,类似IDE里面的。虽然不能直接使用鼠标按键前进后退,用这个也算方便一些了。
除此之外推荐安装以下插件:
- C++ Intellisense
- C/C++
- Visual Studio IntelliCode – Preview
- Dart
- Flutter
- Java Dependency Viewer
- Java Extension Pack
语言问题
Engine使用C++ 11标准语法,所以和以前C++ 98标准差别还是比较大,需要先简单了解一下c++ 11的语法,虽然这些语法其他语言都有了,但是书写方式上可能有一些差异。所以可以先了解下智能指针、拉姆达表达式、 JNI调用。
二 Flutter Engine初始化
整个Flutter Engine源码包含了非常多的东西,DartVM、Render、Channel、Event等等,所以我们只能从相关的点去看,所以可能会有一些遗漏和不清楚的地方。就好比从Engine初始化来说,可以做的事情非常多,但是我们主要关注和Java层有关联的一些操作。相关的代码主要在flutter_engine/shell/platform/android/io/flutter目录下。前面几篇我们从App启动到页面显示,关注的是Java部分的初始化。下面介绍一下这个过程中和Engine相关的操作。
1. 启动过程的Native初始化
A. 加载Flutter Engine
FlutterApplication启动时使用了Java层的FlutterMain来进行初始化
public static void startInitialization(Context applicationContext, Settings settings) {
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
initConfig(applicationContext);
initAot(applicationContext);
initResources(applicationContext);
System.loadLibrary("flutter");
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
}
这里在初始化Flutter App资源之后加载了flutter.so,这个就是Flutter Engine源码编译后的产物。 在我们编译Flutter App时,它存在Flutter SDK的flutter.jar中,当生产APK之后,它存在于APK的lib目录下。而当运行时,它被Android虚拟机加载到虚拟内存中。(so是一个标准的ELF可执行文件,主要分为.data和.text段,分别包含了数据和指令,加载到虚拟内存后,指令可以被CPU执行)
下面是从我Android VM源码笔记中截取的关于System.loadLibrary是实现的描述:
Java程序在调用JNI时会先执行System.loadLibrary来加载动态库,这个方法反映到VM是调用- Runtime_nativeLoad:
1. SetLdLibraryPath 把library的路径设置到全局的path中, 调用的是android linker中得android_update_LD_LIBRARY_PATH
2. 调用JavaVMExt的LoadNativeLibrary
– 根据path从一个ShareLibrary 数组libraries_中获取一个ShareLibrary对象
– 如果获取到说明可能已经加载,检查这个lib是否已经被加载, 如果加载library->GetClassLoader()是存在的
– 如果不存在调用dlopen 打开这个lib,获得lib得地址handle
– 如果不存在 创建SharedLibrary(env, self, path, handle, class_loader))
– 把ShareLibrary加入到libraries_中
– 调用dlsym(handle, “JNI_OnLoad”);查找lib中的JNI_OnLoad方法,如果找到就调用这个方法
– 最后返回结果
从上面我们知道,当我们加载了flutter.so之后,最先被执行的是里面的JNI_OnLoad
方法,所以我们去Engine的源码里找一下,发现在 flutter_engine/shell/platform/android/library_loader.cc
// 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 = shell::FlutterMain::Register(env);
FML_CHECK(result);
// Register PlatformView
result = shell::PlatformViewAndroid::Register(env);
FML_CHECK(result);
// Register VSyncWaiter.
result = shell::VsyncWaiterAndroid::Register(env);
FML_CHECK(result);
return JNI_VERSION_1_4;
}
一开始进行了Java VM的初始化,其实保存一下当前的Java VM对象到一个全局的变量中, JavaVM的初始化在zygote进程中已经进行了,每个Android进程都是fork出来的。
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调用相关的方法。
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;
}
最后调用了Shell下的3个方法,这里我们先主要FlutterMain,这个是C++层的代码,看起来和我们Java层的FlutterMain很有关联。
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/view/FlutterMain");
if (clazz == nullptr) {
return false;
}
return env->RegisterNatives(clazz, methods, arraysize(methods)) == 0;
}
写过JNI的话应该很熟悉上面的代码,就是把Java层的native方法和C++层的方法关联起来。这里是通过env->RegisterNatives
方法把Java层的FlutterMain
的方法和C++层的关联起来。(前面说过JNI_OnLoad不是必须的,如果没有进行native方法的注册,JavaVM会默认按照java方法的package name + method name + params 来映射c++方法,如果找不到就会报错 )
另外两个的作用是一样的,也是注册对应的native方法,具体代码在:
flutter_engine/shell/platform/android/platform_view_android.h
flutter_engine/shell/platform/android/vsync_waiter_android.h
B. 第一个native方法
Java层的FlutterMain里执行的第一个native方式是记录初始化的时间,根据上面的注册关系,它对应C++层的FlutterMain::RecordStartTimestamp方法
static void RecordStartTimestamp(JNIEnv* env,
jclass jcaller,
jlong initTimeMillis) {
int64_t initTimeMicros =
static_cast<int64_t>(initTimeMillis) * static_cast<int64_t>(1000);
blink::engine_main_enter_ts = Dart_TimelineGetMicros() - initTimeMicros;
}
这里把Java层传入的毫秒转换为微妙,然后调用了Dart_TimelineGetMicros()方法,Flutter Engine中并没有这个方法,看起来应该是c++调用了dart的方法,而且引用的头源文件dart_tools_api.h也不在Flutter Engine源码中,而在Dart源码中,所以这里是获取了Dart的时间,然后减去初始化用的时间,得到了Engine的启动时间。我们回过头在看下注释
// We record the initialization time using SystemClock because at the start of the
// initialization we have not yet loaded the native library to call into dart_tools_api.h.
// To get Timeline timestamp of the start of initialization we simply subtract the delta
// from the Timeline timestamp at the current moment (the assumption is that the overhead
// of the JNI call is negligible).
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
因为在启动的时候我们还没有加载flutter.so,所以没办法使用dart来获取时间。
C. NativeInit
当FlutterApplication初始化完成后,MainActivity(FlutterActivity)就会被唤起。也就是上图的第12步,这里调用了NativeInit这个native方法,对应的FlutterMain.cc::Init方法。
void FlutterMain::Init(JNIEnv* env,
jclass clazz,
jobject context,
jobjectArray jargs,
jstring bundlePath,
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);
settings.assets_path = fml::jni::JavaStringToString(env, bundlePath);
// Restore the callback cache.
// TODO(chinmaygarde): Route all cache file access through FML and remove this
// setter.
blink::DartCallbackCache::SetCachePath(
fml::jni::JavaStringToString(env, appStoragePath));
fml::paths::InitializeAndroidCachesPath(
fml::jni::JavaStringToString(env, engineCachesPath));
blink::DartCallbackCache::LoadCacheFromDisk();
if (!blink::DartVM::IsRunningPrecompiledCode()) {
auto application_kernel_path =
fml::paths::JoinPaths({settings.assets_path, "kernel_blob.bin"});
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);
};
g_flutter_main.reset(new FlutterMain(std::move(settings)));
}
这个初始化主要是根据传入的参数生成了一个Settings对象,Demo传入的args如下
从args解析出Settings的过程在 flutter_engine/shell/common/switches.cc, 这里关注几个重要的snapshot路径的构建
if (aot_shared_library_path.size() > 0) {
settings.application_library_path = aot_shared_library_path;
} else if (aot_snapshot_path.size() > 0) {
settings.vm_snapshot_data_path = fml::paths::JoinPaths(
{aot_snapshot_path, aot_vm_snapshot_data_filename});
settings.vm_snapshot_instr_path = fml::paths::JoinPaths(
{aot_snapshot_path, aot_vm_snapshot_instr_filename});
settings.isolate_snapshot_data_path = fml::paths::JoinPaths(
{aot_snapshot_path, aot_isolate_snapshot_data_filename});
settings.isolate_snapshot_instr_path = fml::paths::JoinPaths(
{aot_snapshot_path, aot_isolate_snapshot_instr_filename});
}
构建完成的路径就是进程初始化拷贝到本地的路径。(DEBUG下的kernel_blob.bin好像没有设置?)
然后加载了Dart相关的cache文件(目前不清楚具体作用),最后生成了一个FlutterMain对象保存在了全局静态变量中。
2. FlutterView中的Native操作
lutterView是真正运行Flutter App的地方,前面提到FlutterNativeView通过FlutterJNI负责和engine交互,而FlutterView负责和Java层交互。下面图是FlutterView初始化流程图的前一部分。
从图中我们可以看到FlutterJNI调用的是native方法在platform_view_android_jni.cc文件中, 这个文件实现了FlutterJNI方法中全部的native的方法。 之前我们在FlutterJNI方法中还看到setRenderSurface、setPlatformMessageHandler、addEngineLifecycleListener这三个方法并没有被Java层调用,其实是被native调用了,调用代码也在这个文件中。
nativeAttach
public void attachToNative(boolean isBackgroundView) {
ensureNotAttachedToNative();
nativePlatformViewId = nativeAttach(this, isBackgroundView);
}
FlutterJNI调用这个native方法和native关联,返回一个int值表示platformViewId,目前我们并不清楚什么是PlatformView。 这个方法实际调用了下面方法
static jlong AttachJNI(JNIEnv* env,
jclass clazz,
jobject flutterJNI,
jboolean is_background_view) {
fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
auto shell_holder = std::make_unique<AndroidShellHolder>(
FlutterMain::Get().GetSettings(), java_object, is_background_view);
if (shell_holder->IsValid()) {
return reinterpret_cast<jlong>(shell_holder.release());
} else {
return 0;
}
}
这一段代码短小精悍。
- 把传入的flutterJNI保存到java_object,这里传入的是JAVA层的FlutterJNI对象。(在Java和C++交互中这种操作非常常见,C++保存Java对象的地址或者Java层保存C++对象的地址,这样就饿可以方便的使用)
- 创建了一个AndroidShellHolder 对象
- 把创建的AndroidShellHolder对象的地址返回给了Java层,所以FlutterJNI中的nativePlatformViewId实际上是native层对象的地址。
AndroidShellHolder
这个类从名字看想是一个holder,为什么叫Android Shell Holder? 因为无论对于Android还是IOS,Engine部分的功能是完全一样的(Flutter架构图),但是和上层App交互以及和低层系统交互是不同的,对于低层系统有一层Embedder,对上层也有一层叫Shell,所以整个flutter.jar对应的源码目录也叫做shell,它负责和不同平台进行上层的交互。
private:
const blink::Settings settings_;
const fml::jni::JavaObjectWeakGlobalRef java_object_;
fml::WeakPtr<PlatformViewAndroid> platform_view_;
ThreadHost thread_host_;
std::unique_ptr<Shell> shell_;
bool is_valid_ = false;
pthread_key_t thread_destruct_key_;
既然叫Holder,先看下成员变量,其中有两个很关键的对象,PlatformViewAndroid和Shell对象。整个构造函数内容很多,都是围绕创建上面这些成员变量。只介绍关键的
ThreadHost
if (is_background_view) {
thread_host_ = {thread_label, ThreadHost::Type::UI};
} else {
thread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU |
ThreadHost::Type::IO};
}
is_background_view终于被用到了,表示FlutterView是不是后台的View吗,所谓后台的View就是不用显示的。这里创建了ThreadHost对象。
struct ThreadHost {
enum Type {
Platform = 1 << 0,
UI = 1 << 1,
GPU = 1 << 2,
IO = 1 << 3,
};
std::unique_ptr<fml::Thread> platform_thread;
std::unique_ptr<fml::Thread> ui_thread;
std::unique_ptr<fml::Thread> gpu_thread;
std::unique_ptr<fml::Thread> io_thread;
};
看一下定义,里面包含了4个Thread,从名字就能看出每个线程的作用。Flutter的github上介绍了这4个线程,这里就不在复制一遍了: The Engine architecture
当前初始化的线程是Android 的UI主线程,所以被作为了platform线程,并且确保建立了了消息循环(实际创建了一个MessageLoop对象),所以Task是从MessageLoop中获取的
fml::MessageLoop::EnsureInitializedForCurrentThread();
fml::RefPtr<fml::TaskRunner> platform_runner =
fml::MessageLoop::GetCurrent().GetTaskRunner();
而其他3个线程的Task有所不同:
if (is_background_view) {
auto single_task_runner = thread_host_.ui_thread->GetTaskRunner();
gpu_runner = single_task_runner;
ui_runner = single_task_runner;
io_runner = single_task_runner;
} else {
gpu_runner = thread_host_.gpu_thread->GetTaskRunner();
ui_runner = thread_host_.ui_thread->GetTaskRunner();
io_runner = thread_host_.io_thread->GetTaskRunner();
}
Shell
上面的流程图第13部是创建Shell对象。
shell_ = Shell::Create(task_runners, // task runners
settings_, // settings
on_create_platform_view, // platform view create callback
on_create_rasterizer // rasterizer create callback
);
创建Shell对象时传入了4个参数,其中on_create_platform_view
和on_create_rasterizer 是在创建Shell时回调AndroidShellHolder
的方法来创建PlatformView和Rasterizer。因为创建这2个对象需要先创建Shell,所以使用了回调的方式。
fml::WeakPtr<PlatformViewAndroid> weak_platform_view;
Shell::CreateCallback<PlatformView> on_create_platform_view =
[is_background_view, java_object, &weak_platform_view](Shell& shell) {
std::unique_ptr<PlatformViewAndroid> platform_view_android;
if (is_background_view) {
platform_view_android = std::make_unique<PlatformViewAndroid>(
shell, // delegate
shell.GetTaskRunners(), // task runners
java_object // java object handle for JNI interop
);
} else {
platform_view_android = std::make_unique<PlatformViewAndroid>(
shell, // delegate
shell.GetTaskRunners(), // task runners
java_object, // java object handle for JNI interop
shell.GetSettings()
.enable_software_rendering // use software rendering
);
}
weak_platform_view = platform_view_android->GetWeakPtr();
return platform_view_android;
};
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
};
这里实际上定义的是lamda表达式,主要关注PlatformViewAndroid, 会根据是否后台View来创建不同的PlatformView。
三 总结
这一篇主要分析了Android启动过程中用到的native调用,以及创建FlutterView时engine中的实现。我们大致可以有以下概念:
- Flutter Engine中有4个线程,负责不同的工作。
- 在Engine中有一个PlatformView和FlutterView对应。
- AndroidShellHolder中包含了PlatformView和Shell。
- Shell是负责App和Flutter Framework直接交互的。所以IOS和Android 是分别实现的。
- 看起来一个FlutterView就对应一个AndroidShellHolder,所以如果开多个包含FlutterView的Activity会有多个。
因为Shell::Create的内容很多,所以下一篇文章单独介绍这一块的内容。