Flutter深入之flutter-build-bundle命令如何编译Dart?

开篇

上一篇我们讲到,在flutterTask中会调用flutter build bundle命令来编译dart代码,生成dart资源。那么build bundle命令是如何编译dart代码的?编译后生成了哪些资源?这些资源都是些什么?

flutter-build-bundle-产物

  • app.dill : 这就是dart代码编译后的二级制文件

  • Frontend_server.d : 这里面放的是frontend_server.dart.snapshot的绝对路径,使用该snapshot来编译dart代码生成上面的app.dill

    ​ flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot

  • snapshot_blob.bin.d : 这里面放的是所有参与编译的dart文件的绝对路径的集合,包括项目的代码和flutterSdk的代码以及pub库中的三方代码。

  • snapshot_blob.bin.d.fingerprint : 这里面放的是snapshot_blob.bin.d中的所有文件的绝对路径以及每个文件所对应的md5值。使用这个md5来判断该文件是否有修改。在每次编译的时候会判断,如果没有文件修改,则直接跳过编译。

Flutter深入之flutter run命令究竟做了什么?

开篇

当我们创建一个Flutter App项目后,在当前项目路径下运行命令flutter run,就可以编译生成一个APK,并且将APK安装到模拟器中并启动。那么Flutter究竟是如何编译Dart资源的?又是如何将Dart资源放入到APK中?

接下来让我们慢慢跟着代码来分析flutter run 命令的执行过程。

flutter命令

在Mac上,flutter命令是指 ${flutterSdk}/bin/flutter 这个shell程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
... 
update逻辑
...
PROG_NAME="$(path_uri "$(follow_links "$BASH_SOURCE")")"
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)"

FLUTTER_TOOLS_DIR="$FLUTTER_ROOT/packages/flutter_tools"
SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot"
STAMP_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.stamp"
SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart"
DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk"
DART="$DART_SDK_PATH/bin/dart"
PUB="$DART_SDK_PATH/bin/pub"

// 实际上抛除更新逻辑,flutter的这个shell就是直接执行了dart命令
// $FLUTTER_TOOL_ARGS 这个参数可以无视
// $SNAPSHOT_PATH 这个指的就是$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot这个snapshot
// $@ 这个就是flutter 后面跟的参数
"$DART" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"

我们可以看到,实际上flutter run 命令,执行的就是 dart flutter_tools.snapshot run

Flutter深入之flutter初始化

开篇

前面两篇我们讲到了Flutter应用如何编译,并将编译结果如何放入APK中。那么当应用启动时,又是如何执行到main.dart中的main方法呢?Flutter的绘制内容又是如何被显示的呢?

flutter-启动流程

FlutterApplication

在应用启动的Application中会执行一些初始化操作,比如初始化一些路径文件夹等配置,搬移flutter资源,最为重要的是安装flutter.so。在前面的编译讲解中,我们知道该so就是flutter-engine。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# FlutterMain.java
public static void startInitialization(Context applicationContext, Settings settings) {
...
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
// 初始化路径等配置
initConfig(applicationContext);
// 检测aot
initAot(applicationContext);
// 搬移flutter资源
initResources(applicationContext);
// 加载so
System.loadLibrary("flutter");

// initTimeMicros是java层初始化loadLibrary所花费的时间。
// 因为so库中需要记录flutter开始的时间,但是在加载so库之前,
// 是无法调用so中的api,所以将加载so库所花费的时间传下去,然后so库中使用当前时间回退该花费时间,就是应用启动的真正时间。
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
}
1
2
3
4
5
6
7
8
9
# flutter_main.cc
static void RecordStartTimestamp(JNIEnv* env,
jclass jcaller,
jlong initTimeMillis) {
int64_t initTimeMicros =
static_cast<int64_t>(initTimeMillis) * static_cast<int64_t>(1000);
// initTimeMicros是java层初始化所花费的时间,使用当前时间回退该花费时间,就是应用启动的真正时间。
blink::engine_main_enter_ts = Dart_TimelineGetMicros() - initTimeMicros;
}

在加载so库完成后,会触发JNI回调JNI_OnLoad,开始动态注册JNI方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# library_loader.cc
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// 将javaVM保存成全局变量
fml::jni::InitJavaVM(vm);
// 获取JNIEnv,通过JNIEnv.RegisterNatives来动态注册
JNIEnv* env = fml::jni::AttachCurrentThread();
bool result = false;

// 注册FlutterMain.java中的native方法
result = shell::FlutterMain::Register(env);
FML_CHECK(result);

// 注册FlutterCallbackInformation.java、FlutterView.java、FlutterNativeView.java中的native方法
result = shell::PlatformViewAndroid::Register(env);
FML_CHECK(result);

// 注册VsyncWaiter.java中的native方法
result = shell::VsyncWaiterAndroid::Register(env);
FML_CHECK(result);

return JNI_VERSION_1_4;
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×