Flutter深入之插件新API——Embedding-V2

在Flutter1.12版本中正式将Embedding-V2API在Android平台默认开启,所有官方插件都迁移到了新的API。Embedding-V2APi的优势在于针对混合开发提供了更好的支持。

版本配置

GeneratedPluginRegistrant生成内容配置,通过在AndroidManifest.xml中配置Embedding的版本来确定工具生成的GeneratedPluginRegistrant类的内容。

1
2
3
4
5
<!-- Don't delete the meta-data below.
It is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />

具体判定逻辑在flutter_tools项目中

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
  AndroidEmbeddingVersion getEmbeddingVersion() {
if (isModule) {
// A module type's Android project is used in add-to-app scenarios and
// only supports the V2 embedding.
return AndroidEmbeddingVersion.v2;
}
if (appManifestFile == null || !appManifestFile.existsSync()) {
return AndroidEmbeddingVersion.v1;
}
xml.XmlDocument document;
try {
document = xml.parse(appManifestFile.readAsStringSync());
} on xml.XmlParserException {
throwToolExit('Error parsing $appManifestFile '
'Please ensure that the android manifest is a valid XML document and try again.');
} on FileSystemException {
throwToolExit('Error reading $appManifestFile even though it exists. '
'Please ensure that you have read permission to this file and try again.');
}
for (xml.XmlElement metaData in document.findAllElements('meta-data')) {
final String name = metaData.getAttribute('android:name');
if (name == 'flutterEmbedding') {
final String embeddingVersionString = metaData.getAttribute('android:value');
if (embeddingVersionString == '1') {
return AndroidEmbeddingVersion.v1;
}
if (embeddingVersionString == '2') {
return AndroidEmbeddingVersion.v2;
}
}
}
return AndroidEmbeddingVersion.v1;
}
}

创建时机变更

旧API

旧的创建插件的时机一般是在FlutterActivity的子类onCreate()回调时,手动调用下面的方法来创建所有的插件。

1
GeneratedPluginRegistrant.registerWith(PluginRegistry registry);

新API

新的API将插件与视图进行解绑,将插件与引擎进行绑定,并且在引擎创建的时候提供了自动创建插件的功能。

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
public FlutterEngine(
@NonNull Context context,
@NonNull FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins
) {

this.pluginRegistry = new FlutterEnginePluginRegistry(
context.getApplicationContext(),
this,
flutterLoader
);
// 默认为true,自动创建插件
if (automaticallyRegisterPlugins) {
registerPlugins();
}
}

// 通过反射调用GeneratedPluginRegistrant.registerWith(FlutterEngine engine)方法
private void registerPlugins() {
try {
Class<?> generatedPluginRegistrant = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
Method registrationMethod = generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
registrationMethod.invoke(null, this);
} catch (Exception e) {
Log.w(TAG, "Tried to automatically register plugins with FlutterEngine ("
+ this + ") but could not find and invoke the GeneratedPluginRegistrant.");
}
}

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初始化

开篇

前面两篇我们讲到了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;
}

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

Your browser is out-of-date!

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

×