前序
要解决的问题以及现在的常用解决方案就不再详细叙述,主要就是要做混合开发这件事。
背景
FlutterEngineGroup是由官方推出的Flutter+Native混合开发解决方案,与FlutterBoost 不同的是,使用的是多引擎的处理方式,并优化了每个引擎的大小以及数据处理方式,让大部分数据实现共享,只有部分生命周期等内容做了隔离。
实现方式
相较于单Engine形式,多Engine无需关心页面栈等问题,但新增了一个问题,就是多Engine中的数据共享。
多Engine的方式,如果我们自己设计,那需要解决一下三个重点问题: 1、Engine 资源共享 2、与Native的数据传递方式 3、native插件的调用方式
2、3问题可以看做是同一个问题,有两种不同的实现,一种是每个Engine对应一个Native Channel,另一张是对于Native只对应一个Native Channel,明显第二种方法更能直接解决问题,一会儿我们可以看一下FlutterEngineGroup是如何做的。
Engine资源共享
为什么要做共享
如果不共享数据空间,每个Engine的资源独立,Flutter页面间的信息同步以及全局变量的响应都会变得很麻烦。
处理方式
1、多Engine的数据共享,开发者经常用的是通过Native进行中转,大部分的数据内容都可以存在于Native中,然后由Native发起数据同步,实现方案也比较简单直接 2、为所有Engine做一个数据缓存池,不同Engine的数据直接与缓存池中数据同步。最大的问题在于多Engine意味着多线程,数据同步时需要考虑线程安全问题。
接下来我们看一下Flutter Engine Group究竟是如何实现的。
具体实现
if (activeEngines.size() == 0) {
engine = createEngine(context);
engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint);
} else {
engine = activeEngines.get(0).spawn(context, dartEntrypoint);
}
private native FlutterJNI nativeSpawn(
long nativeSpawningShellId,
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction);
// Signature is similar to RunBundleAndSnapshotFromLibrary but it can't change
// the bundle path or asset manager since we can only spawn with the same
// AOT.
//
// The shell_holder instance must be a pointer address to the current
// AndroidShellHolder whose Shell will be used to spawn a new Shell.
//
// This creates a Java Long that points to the newly created
// AndroidShellHolder's raw pointer, connects that Long to a newly created
// FlutterJNI instance, then returns the FlutterJNI instance.
static jobject SpawnJNI(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jstring jEntrypoint,
jstring jLibraryUrl) {
jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor);
if (jni == nullptr) {
FML_LOG(ERROR) << "Could not create a FlutterJNI instance";
return nullptr;
}
fml::jni::JavaObjectWeakGlobalRef java_jni(env, jni);
std::shared_ptr<PlatformViewAndroidJNI> jni_facade =
std::make_shared<PlatformViewAndroidJNIImpl>(java_jni);
auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint);
auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl);
auto spawned_shell_holder =
ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl);
if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) {
FML_LOG(ERROR) << "Could not spawn Shell";
return nullptr;
}
jobject javaLong = env->CallStaticObjectMethod(
g_java_long_class->obj(), g_long_constructor,
reinterpret_cast<jlong>(spawned_shell_holder.release()));
if (javaLong == nullptr) {
FML_LOG(ERROR) << "Could not create a Long instance";
return nullptr;
}
env->SetObjectField(jni, g_jni_shell_holder_field, javaLong);
return jni;
}
总体来讲步骤很简单,就是使用了shell_holder 生成了一个新的shell在处理后续的记载等。
结论
FlutterEngineGroup 依旧使用的是第一种处理方式实现数据同步的。总结原因应该是由于第二种实现方案对于引擎的改造过大,同步机制也有有较大变化,从个人角度出发还是比较期待第二种实现方案。 当前方案,对于数据生命周期与页面生命周期不一致时,即多页面使用相同数据等场景,无法支持,只能通过Native Channel进行数据同步。这就需要约束相关开发者,页面就是一个APP,全局变量等内容只能由Native去做。
问题2 和 问题3的解决方式
由刚才的实现逻辑可以看出,每个Engine独立一个Method Channel进行与Native的交互。
fun attach() {
DataModel.instance.addObserver(this)
channel.invokeMethod("setCount", DataModel.instance.counter)
channel.setMethodCallHandler { call, result ->
when (call.method) {
"incrementCount" -> {
DataModel.instance.counter = DataModel.instance.counter + 1
result.success(null)
}
"next" -> {
this.delegate.onNext()
result.success(null)
}
else -> {
result.notImplemented()
}
}
}
}
在每次Activity启动时,重新注册一次对应的Channel方法。
总结
FlutterEngineGroup的方案,感觉比较仓促,相当于只是简单的把Engine每次启动的内容变少了,而数据共享等问题没有得到实现。这就导致单独Flutter环境无法适配数据生命周期比页面生命周期长的情况,需要Native来做桥接。
|