Flutter 2 Router 从入门到放弃 - 实现原理与源码分析(一)在上一篇文章Flutter 2 Router
周建华: 微医移动端诊疗组, 喜欢看书和运动的 Android 程序猿
前言
一、Flutter 2 源码编译调试
工欲善其事,必先利其器,这里我们先对源码编译和调试步骤进行说明:
源码编译
安装 depot_tools,配置环境变量
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=/path/to/depot_tools:$PATH
创建空的 engine 目录并在目录中创建 .gclient 配置文件,在 .gclient 中配置从 flutter/engine 主工程 fork 出的 github 工程地址,.gclient 配置如下
solutions = [
{
"managed": False,
"name": "src/flutter",
"url": "https://github.com/Alex0605/engine.git",
"custom_deps": {},
"deps_file": "DEPS",
"safesync_url": "",
},
]
在 engine 目录中执行 gclient sync
切换源码。编译前的一个重要操作是将源码切换到本地 Flutter SDK 的 engine version 对应的提交点
# 查看本地 Flutter SDK 引擎版本, 这个文件中是包含对应的 commit id
vim src/flutter/bin/internal/engine.version
# 调整代码
cd engine/src/flutter
git reset --hard <commit id>
gclient sync -D --with_branch_heads --with_tags
# 准备构建文件
cd engine/src
#Android
# 使用以下命令生成 host_debug_unopt 编译配置
./flutter/tools/gn --unoptimized
# android arm (armeabi-v7a) 编译配置
./flutter/tools/gn --android --unoptimized
# android arm64 (armeabi-v8a) 编译配置
./flutter/tools/gn --android --unoptimized --runtime-mode=debug --android-cpu=arm64
# 编译
ninja -C out/host_debug_unopt -j 16
ninja -C out/android_debug_unopt -j 16
ninja -C out/android_debug_unopt_arm64 -j 16
#iOS
# unopt-debug
./flutter/tools/gn --unoptimized --ios --runtime-mode debug --ios-cpu arm
./flutter/tools/gn --unoptimized --ios --runtime-mode debug --ios-cpu arm64
./flutter/tools/gn --unoptimized --runtime-mode debug --ios-cpu arm
./flutter/tools/gn --unoptimized --runtime-mode debug --ios-cpu arm64
ninja -C out/ios_debug_unopt_arm
ninja -C out/ios_debug_unopt
ninja -C out/host_debug_unopt_arm
ninja -C out/host_debug_unopt
编译完成后的目录如下:
源码运行调试
通过命令创建一个 flutter 工程
flutter create --org com.wedotor.flutter source_code
用 android studio 打开创建的 android 工程
在 gradle.properties 文件中添加 localEngineOut 属性,配置如下:
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJitfier=true
localEngineOut=/Users/zhoujh/myproj/3-proj/flutter/engine/src/out/android_debug_unopt_arm64
将 engine/src/flutter/shell/platform/android
工程(称之为* Flutter 引擎工程*)导入到 Android Studio
使用自定义 Flutter 引擎运行 Flutter App(称之为 Flutter App 工程),具体如 1-3 步所述
Flutter 引擎工程 中给源码设置断点并启动 Debugger 连接到已启动的 Flutter App 进程
PS:这里 C++ 代码我用的是 Clion 阅读,这里配置比较简单,将上面生成的 compile_commands.json 文件复制到 src/flutter 目录中,然后使用 Clion 打开项目,indexing 之后便可以跟踪跳转
二、Flutter 2 源码阅读
进行源码分析之前,先了解一下官方文档中提供的核心架构图,它也代表着整个 Flutter 架构。
Flutter 的架构主要分成三层:Framework,Engine 和 Embedder。
1)、Framework:Framework 使用 dart 实现,包括 Material Design 风格的 Widget,Cupertino(针对 iOS)风格的 Widgets,文本/图片/按钮等基础 Widgets,渲染,动画,手势等。此部分的核心代码是:flutter 仓库下的 flutter package,以及 sky_engine 仓库下的 io,async ,ui (dart:ui 库提供了 Flutter 框架和引擎之间的接口)等 package。其中 dart:ui 库是对 Engine 中 Skia 库的 C++ 接口的绑定。向上层提供了 window、text、canvas 等通用的绘制能力,通过 dart:ui 库就能使用 Dart 代码操作 Skia 绘制引擎。所以我们实际上可以通过实例化 dart:ui 包中的类(例如 Canvas、Paint 等)来绘制界面。然而,除了绘制,还要考虑到协调布局和响应触摸等情况,这一切实现起来都异常麻烦,这也正是 Framework 帮我们做的事。渲染层 Rendering 是在 ::dart:ui 库之上的第一个抽象层,它为你做了所有繁重的数学工作。为了做到这一点,它使用 RenderObject 对象,该对象是真正绘制到屏幕上的渲染对象。由这些 RenderObject 组成的树处理真正的布局和绘制。
2)、Engine:Engine 使用 C++ 实现,主要包括:Skia,Dart 和 Text。Skia 是开源的二维图形库,提供了适用于多种软硬件平台的通用 API。在安卓上,系统自带了 Skia,在 iOS 上,则需要 APP 打包 Skia 库,这会导致 Flutter 开发的 iOS 应用安装包体积更大。 Dart 运行时则可以以 JIT、JIT Snapshot 或者 AOT 的模式运行 Dart 代码。
3)、Embedder:Embedder 是一个嵌入层,即把 Flutter 嵌入到各个平台上去,这里做的主要工作包括渲染 Surface 设置,线程设置,以及插件等。从这里可以看出,Flutter 的平台相关层很低,平台(如 iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在 Flutter 内部,这就使得它具有了很好的跨端一致性。
2、启动 app 时会在 Application onCreate 方法中创建 FlutterEngineGroup 对象
public void onCreate() {
super.onCreate();
// 创建 FlutterEngineGroup 对象
engineGroup = new FlutterEngineGroup(this);
}
3、在创建 FlutterEngineGroup 时,使通过该引擎组创建的子引擎共享资源,比单独通 FlutterEngine 构造函数创建,创建速度的更快、占用内存更少,在创建或重新创建第一个引擎时,行为与通过 FlutterEngine 构造函数创建相同。当创建后续的引擎时,会重新使用现有的引擎中的资源。共享资源会一直保留到最后一个引擎被销毁。删除 FlutterEngineGroup 不会使其现有的已创建引擎失效,但它无法再创建更多的 FlutterEngine。
//src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java
public FlutterEngineGroup(@NonNull Context context, @Nullable String[] dartVmArgs) {
FlutterLoader loader = FlutterInjector.instance().flutterLoader();
if (!loader.initialized()) {
loader.startInitialization(context.getApplicationContext());
loader.ensureInitializationComplete(context, dartVmArgs);
}
}
4、FlutterLoader 的 startInitialization 将加载 Flutter 引擎的本机库 flutter.so 以启用后续的 JNI 调用。还将查找解压打包在 apk 中的 dart 资源,而且方法只会被调用一次。该方法具体调用步骤:
1)、settings 属性是否赋值来确定方法是否执行过;
2)、方法必须在主线程中执行,否则抛异常退出;
3)、获取 app 上下文;
4)、VsyncWaiter 是同步帧率相关的操作;
5)、记录初始化耗时时间;
6)、从 flutter2 开始,初始化配置、初始化资源、加载 flutter.so 动态库,都放在后台子线程中运行,加快了初始化速度。
//src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
//初始化方法只能运行一次
if (this.settings != null) {
return;
}
//必须在主线程上调用 startInitialization
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
}
// 获取 app 的上下文
final Context appContext = applicationContext.getApplicationContext();
this.settings = settings;
initStartTimestampMillis = SystemClock.uptimeMillis();
//获取 app 相关信息
flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
.init();
//将后台线程用于需要磁盘访问的初始化任务
Callable<InitResult> initTask =
new Callable<InitResult>() {
@Override
public InitResult call() {
//获取配置资源
ResourceExtractor resourceExtractor = initResources(appContext);
//加载 fluter 本地 so 库
flutterJNI.loadLibrary();
Executors.newSingleThreadExecutor()
.execute(
new Runnable() {
@Override
public void run() {
//预加载 skia 字体库
flutterJNI.prefetchDefaultFontManager();
}
});
if (resourceExtractor != null) {
//等待初始化时的资源初始化完毕后才会向下执行,否则会一直阻塞
resourceExtractor.waitForCompletion();
}
return new InitResult(
PathUtils.getFilesDir(appContext),
PathUtils.getCacheDirectory(appContext),
PathUtils.getDataDirectory(appContext));
}
};
initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
}
5、initResources:将 apk 中的资源文件复制到应用本地文件中,在 DEBUG 或者在 JIT_RELEASE 模式下安装 Flutter 资源,主要由 ResourceExtractor 来异步执行资源文件的解压缩操作,最终会将 apk 中 assets 中的 Dart 资源 vm_snapshot_data、isolate_snapshot_data、kernel_blob.bin 文件安装到应用目录 app_flutter 目录下。
private ResourceExtractor initResources(@NonNull Context applicationContext) {
ResourceExtractor resourceExtractor = null;
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
//获取 flutter 数据存储路径
final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
//获取包名
final String packageName = applicationContext.getPackageName();
final PackageManager packageManager = applicationContext.getPackageManager();
final AssetManager assetManager = applicationContext.getResources().getAssets();
resourceExtractor =
new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
resourceExtractor
.addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData))
.addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData))
.addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
resourceExtractor.start();
}
return resourceExtractor;
}
6、在初始化 startInitialization 时,也会调用 ensureInitializationComplete 方法确认初始化是否完成,然后里面会把 so 文件的地址给到 shellArgs 里传入 FlutterJNI,因此我们可以通过修改 flutter 生成的代码或者使用 hook 等方式替换 List shellArgs 的 add 方法,从而改变 so 的路径,进行热修复。
public void ensureInitializationComplete(
@NonNull Context applicationContext, @Nullable String[] args) {
if (initialized) {
return;
}
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException(
"ensureInitializationComplete must be called on the main thread");
}
if (settings == null) {
throw new IllegalStateException(
"ensureInitializationComplete must be called after startInitialization");
}
try {
InitResult result = initResultFuture.get();
List<String> shellArgs = new ArrayList<>();
// 此处省略具体参数配置代码...
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
// 初始化 JNI
flutterJNI.init(
applicationContext,
shellArgs.toArray(new String[0]),
kernelPath,
result.appStoragePath,
result.engineCachesPath,
initTimeMillis);
initialized = true;
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
}
FlutterJNI 初始化
public void init(
@NonNull Context context,
@NonNull String[] args,
@Nullable String bundlePath,
@NonNull String appStoragePath,
@NonNull String engineCachesPath,
long initTimeMillis) {
if (FlutterJNI.initCalled) {
Log.w(TAG, "FlutterJNI.init called more than once");
}
//调用 JNI 中 flutter 初始化方法
FlutterJNI.nativeInit(
context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis);
FlutterJNI.initCalled = true;
}
7、在初始化资源之后就开始加载 flutter.so,这个就是 Flutter Engine 源码编译后的产物。当运行时,它被 Android 虚拟机加载到虚拟内存中。(so 是一个标准的 ELF 可执行文件,主要分为 .data 和 .text 段,分别包含了数据和指令,加载到虚拟内存后,指令可以被 CPU 执行) 加载了 flutter.so 之后,最先被执行的是里面的 JNI_OnLoad 方法 ,会注册 FlutterMain 、PlatformView、VSyncWaiter 的 jni 方法。
//src/flutter/shell/platform/android/library_loader.cc
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// 开始进行 Java VM 的初始化,是保存当前的 Java VM 对象到一个全局的变量中
fml::jni::InitJavaVM(vm);
//把当前的 thread 和 JavaVM 关联起来
JNIEnv* env = fml::jni::AttachCurrentThread();
bool result = false;
// 注册 FlutterMain,就是把 Java 层的 native 方法和 C++层的方法关联起来
result = flutter::FlutterMain::Register(env);
FML_CHECK(result);
// 注册 PlatformView
result = flutter::PlatformViewAndroid::Register(env);
FML_CHECK(result);
// 注册 VSyncWaiter.
result = flutter::VsyncWaiterAndroid::Register(env);
FML_CHECK(result);
return JNI_VERSION_1_4;
}
系统初始化完成之后,会调用 NativeInit 这个 native方法,对应的 FlutterMain.cc::Init 方法。这里初始化主要是根据传入的参数生成了一个 Settings 对象。
// src/flutter/shell/platform/android/flutter_main.cc
void FlutterMain::Init(JNIEnv* env,
jclass clazz,
jobject context,
jobjectArray jargs,
jstring kernelPath,
jstring appStoragePath,
jstring engineCachesPath,
jlong initTimeMillis) {
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);
int64_t init_time_micros = initTimeMillis * 1000;
settings.engine_start_timestamp =
std::chrono::microseconds(Dart_TimelineGetMicros() - init_time_micros);
flutter::DartCallbackCache::SetCachePath(
fml::jni::JavaStringToString(env, appStoragePath));
fml::paths::InitializeAndroidCachesPath(
fml::jni::JavaStringToString(env, engineCachesPath));
flutter::DartCallbackCache::LoadCacheFromDisk();
if (!flutter::DartVM::IsRunningPrecompiledCode() && kernelPath) {
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);
};
settings.log_message_callback = [](const std::string& tag,
const std::string& message) {
__android_log_print(ANDROID_LOG_INFO, tag.c_str(), "%.*s",
(int)message.size(), message.c_str());
};
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
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 全局变量
g_flutter_main.reset(new FlutterMain(std::move(settings)));
g_flutter_main->SetupObservatoryUriCallback(env);
}
从 args 解析出 Settings 的过程在 flutter_engine/shell/common/switches.cc,这里最重要的是 snapshot 路径的构建,构建完成的路径就是进程初始化拷贝到本地的路径, 最后生成了一个 FlutterMain 对象保存在全局静态变量中。
if (aot_shared_library_name.size() > 0) {
for (std::string_view name : aot_shared_library_name) {
settings.application_library_path.emplace_back(name);
}
} else if (snapshot_asset_path.size() > 0) {
settings.vm_snapshot_data_path =
fml::paths::JoinPaths({snapshot_asset_path, vm_snapshot_data_filename});
settings.vm_snapshot_instr_path = fml::paths::JoinPaths(
{snapshot_asset_path, vm_snapshot_instr_filename});
settings.isolate_snapshot_data_path = fml::paths::JoinPaths(
{snapshot_asset_path, isolate_snapshot_data_filename});
settings.isolate_snapshot_instr_path = fml::paths::JoinPaths(
{snapshot_asset_path, isolate_snapshot_instr_filename});
}
后记
以上主要是 Flutter 2 FlutterEngineGroup 初始化的过程,下一节我们开始学习通过 FlutterEngineGroup创建 FlutterEngine 并绑定的 UI 页面的流程。
转载自:https://juejin.cn/post/6995321720588468261