无论是纯flutter应用还是flutter混合开发应用,flutter编写的代码都需要一个容器进行加载展示给用户,这与webview从本质上是一样的。因此有必要学习这个容器的主要功能、用法,以及在必要的时候去开发实现一些扩展功能或者修改一些默认行为,以便更好地与APP现有的运行框架融合。本文从flutter提供的embedding包入手分析,通过梳理flutter的加载过程了解其核心代码,以便后期进行定制开发。
embedding包核心代码分析
本节所讨论的代码位于io.flutter.embedding 包中,包括.android ,.engine 。
FlutterActivity
当我们运行flutter的example工程时,其MainActivity就是继承该类。通过查看源码可以发现这个Activity有两种启动方式:NewEngine 和CachedEngine ,分别对应flutter engine的两种管理策略。我们看下这两种启动方式的参数区别:
NewEngineIntentBuilder | CachedEngineIntentBuilder |
---|
Class<? extends FlutterActivity> activityClass :启动activity类名,可以是FlutterActivity的子类,因此这里可以自定义 | Class<? extends FlutterActivity> activityClass | String initialRoute :初始路由地址,默认“/” | String cachedEngineId :FlutterEngineCache的存储key | String backgroundMode :背景模式,分为透明(对应FlutterTextureView)和不透明(对应FlutterSurfaceView)两种。默认不透明,除非有明确的需求,否则不建议使用透明 | String backgroundMode |
可以看到除了个别参数不一致,其余参数都是一样的。NewEngine 方式因为每次都要构造一个新的engine实例,因此需要指定一个initialRoute;CachedEngine 方式由于需要从FlutterEngineCache 中获取缓存的engine,因此需要指定cachedEngineId。
从onCreate 方法开始,我们看到核心逻辑都在FlutterActivityAndFragmentDelegate 中,而Activity/Fragment的功能被抽象为FlutterActivityAndFragmentDelegate.Host 接口以统一Activity/Fragment二者的行为。这个接口是整个embedding包的重点之一,因此我们重点看下里面有哪些方法。
FlutterActivityAndFragmentDelegate.Host
由于接口方法比较多的,我把这些接口分为几类:
参数配置类
@NonNull Context getContext() :返回activity本身@Nullable Activity getActivity() :返回activity本身@NonNull Lifecycle getLifecycle() :返回lifecycle实例@NonNull FlutterShellArgs getFlutterShellArgs() :flutter命令参数,参见FlutterShellArgs @Nullable String getCachedEngineId() :获取engine缓存key,用于engine复用场景boolean shouldDestroyEngineWithHost() :是否当host销毁时同时销毁engine,intent传入@NonNullString getDartEntrypointFunctionName() :dart执行入口方法名,默认“main”,manifest中配置@NonNull String getAppBundlePath() :自定义bundle路径,仅测试用,实际运行采用的是FlutterLoader#findAppBundlePath() @Nullable String getInitialRoute() :初始路由地址,支持intent传入和manifest中配置(当flutter页面作为启动页时)。当这个方法返回null且shouldHandleDeeplinking返回true时,初始路由地址将由Intent.getData()决定boolean shouldHandleDeeplinking() :是否处理Deeplinking,默认false。仅当getInitialRoute()返回null时生效,manifest中配置@NonNull RenderMode getRenderMode() :surface(默认)/texture两种模式,用于初始化FlutterView,intent传入@NonNull TransparencyMode getTransparencyMode() :opaque(默认)/transparent两种模式,用于初始化FlutterView,intent传入boolean shouldAttachEngineToActivity() :控制该activity中的FlutterFragment是否自动attach其engine到activity中,默认true。用于engine复用场景boolean shouldRestoreAndSaveState() :控制是否执行onRestore/onSave InstanceState方法,默认为true,在engine复用场景需要考虑实际情况
功能类
@Nullable SplashScreen provideSplashScreen() 来源:io.flutter.embedding.android.SplashScreenProvider ,提供flutter页面启动屏,默认null。可以通过在manifest中配置drawable资源,或者自己覆写此方法返回完全自定义的SplashScreen,参见DrawableSplashScreen 。@Nullable FlutterEngine provideFlutterEngine(@NonNull Context context) 来源:io.flutter.embedding.android.FlutterEngineProvider ,提供一个engine实例给FlutterFragment 用,通常FlutterFragment 不应该自己new一个engine,而应该托付给FlutterActivity 去生成。默认返回null,这样FlutterActivityAndFragmentDelegate 内部会自己去生成。调用入口在io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#setupFlutterEngine 。@Nullable PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) 提供一个PlatformPlugin 实例,在FlutterFragment 和FlutterActivity 中都是直接new了一个并返回,没有托付关系。boolean popSystemNavigator() 来源:io.flutter.plugin.platform.PlatformPlugin.PlatformPluginDelegate ,处理原生页面栈退出逻辑,默认返回false,交由flutter framework处理,默认逻辑是finish activity或pop fragment(代码在io.flutter.plugin.platform.PlatformPlugin#popSystemNavigator )。如果想自己处理,则可以覆写此方法。
生命周期类
void detachFromFlutterEngine() 当engine被attach到其他activity时回调,此时应当停止当前activity与engine的交互。void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) 来源:io.flutter.embedding.android.FlutterEngineConfigurator ,当engine被创建后并attach到FragmentActivity后回调,默认调用GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine) 。void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) 来源:io.flutter.embedding.android.FlutterEngineConfigurator ,engine detach或destroy前回调,与configureFlutterEngine()回调呼应,默认空实现。void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) FlutterSurfaceView被创建后attach前回调,默认空实现void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView) FlutterTextureView被创建后attach前回调,默认空实现void onFlutterUiDisplayed() FlutterView开始绘制时回调,默认为一个无关实现void onFlutterUiNoLongerDisplayed() FlutterView停止绘制时回调,默认空实现
终于把Host的方法讲完了,由于这些方法都是供FlutterActivityAndFragmentDelegate 调用的,所以真正的加载流程还得看这个类。
FlutterActivityAndFragmentDelegate
本节介绍FlutterActivityAndFragmentDelegate 几个比较重要的方法。
void onAttach(@NonNull Context context)
- activity的onCreate方法
- fragment的onAttach方法
创建FlutterActivityAndFragmentDelegate实例,并随即调用了其onAttach方法。
void onAttach(@NonNull Context context) {
ensureAlive();
if (flutterEngine == null) {
setupFlutterEngine();
}
if (host.shouldAttachEngineToActivity()) {
flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
}
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
host.configureFlutterEngine(flutterEngine);
}
看下FlutterEngine是怎么来的,重点关注其构造参数:
void setupFlutterEngine() {
String cachedEngineId = host.getCachedEngineId();
if (cachedEngineId != null) {
flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
isFlutterEngineFromHost = true;
if (flutterEngine == null) {
throw new IllegalStateException(
"The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
+ cachedEngineId
+ "'");
}
return;
}
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
flutterEngine =
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
false,
host.shouldRestoreAndSaveState());
isFlutterEngineFromHost = false;
}
- isFlutterEngineFromHost:这个参数会影响到engine的销毁逻辑,可以理解为engine是否为用户自己生成(维护)的一个标记。
- automaticallyRegisterPlugins:是否在engine构造方法中注册插件。默认的engine填的是false,也就是默认是不会在构造engine时注册插件,通过前面的分析我们知道默认会在
Host#configureFlutterEngine 中才去注册插件。
接下来是ActivityControlSurface#attachToActivity :
@Override
public void attachToActivity(@NonNull ExclusiveAppComponent<Activity> exclusiveActivity, @NonNull Lifecycle lifecycle) {
if (this.exclusiveActivity != null) {
this.exclusiveActivity.detachFromFlutterEngine();
}
detachFromAppComponent();
if (this.activity != null) {
throw new AssertionError("Only activity or exclusiveActivity should be set");
}
this.exclusiveActivity = exclusiveActivity;
attachToActivityInternal(exclusiveActivity.getAppComponent(), lifecycle);
}
private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) {
this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle);
flutterEngine
.getPlatformViewsController()
.attach(activity, flutterEngine.getRenderer(), flutterEngine.getDartExecutor());
for (ActivityAware activityAware : activityAwarePlugins.values()) {
if (isWaitingForActivityReattachment) {
activityAware.onReattachedToActivityForConfigChanges(activityPluginBinding);
} else {
activityAware.onAttachedToActivity(activityPluginBinding);
}
}
isWaitingForActivityReattachment = false;
}
从这里回过头去看host.shouldAttachEngineToActivity() 这个值,当我们想要复用engine时,即同一个engine要attach到不同的activity时,应该返回true还是false呢?其实还是应该返回true的,我想这也是这个方法默认返回true的原因。不过也有特殊情况:当同一个activity中包含多个FlutterFragment ,当这些FlutterFragment 共享同一个engine时,由于PlatformViewsController 只能attach一次(见PlatformViewsController#attach ),此时第二个FlutterFragment 执行flutterEngine.getActivityControlSurface().attachToActivity() 时就会奔溃。 怎么办呢?其实就要判断当前activity是否已存在engine实例。FlutterActivity 嵌套FlutterFragment 的情况是不会发生的,所以我们只要考虑同一个activity多个FlutterFragment 的情况。具体代码实现应该是在FlutterFragment 的subclass中,重写shouldAttachEngineToActivity:
- 判断当前activity是否是
FlutterActivity ,如果是则返回true或super实现; - 获取activity的fragmentManager,遍历所有fragments,当自身不是第一个
FlutterFragment 实例时,返回false,否则返回true; - 作为2的备用方案:为activity实例设置一个tag,shouldAttachEngineToActivity()中先读取这个tag,若为null则返回true,随机设置tag为非空;
接着是创建PlatformPlugin ,这个比较简单了。由于PlatformPlugin 是FlutterActivityAndFragmentDelegate 实例的成员变量且没有共享机制,所以每次都构建新的对象就行了。
最后是configureFlutterEngine()回调的执行,也比较简单,当activity为FlutterActivity及其子类时,最终是执行了FlutterActivity#configureFlutterEngine :
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
反射调用了:
@Keep
public final class GeneratedPluginRegistrant {
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
flutterEngine.getPlugins().add(new XXXPlugin());
flutterEngine.getPlugins().add(new YYYPlugin());
...
}
}
最终的添加方法:
@Override
public void add(@NonNull FlutterPlugin plugin) {
if (has(plugin.getClass())) {
return;
}
plugins.put(plugin.getClass(), plugin);
plugin.onAttachedToEngine(pluginBinding);
if (plugin instanceof ActivityAware) {
ActivityAware activityAware = (ActivityAware) plugin;
activityAwarePlugins.put(plugin.getClass(), activityAware);
if (isAttachedToActivity()) {
activityAware.onAttachedToActivity(activityPluginBinding);
}
}
if (plugin instanceof ServiceAware) {
ServiceAware serviceAware = (ServiceAware) plugin;
serviceAwarePlugins.put(plugin.getClass(), serviceAware);
if (isAttachedToService()) {
serviceAware.onAttachedToService(servicePluginBinding);
}
}
if (plugin instanceof BroadcastReceiverAware) {
BroadcastReceiverAware broadcastReceiverAware = (BroadcastReceiverAware) plugin;
broadcastReceiverAwarePlugins.put(plugin.getClass(), broadcastReceiverAware);
if (isAttachedToBroadcastReceiver()) {
broadcastReceiverAware.onAttachedToBroadcastReceiver(broadcastReceiverPluginBinding);
}
}
if (plugin instanceof ContentProviderAware) {
ContentProviderAware contentProviderAware = (ContentProviderAware) plugin;
contentProviderAwarePlugins.put(plugin.getClass(), contentProviderAware);
if (isAttachedToContentProvider()) {
contentProviderAware.onAttachedToContentProvider(contentProviderPluginBinding);
}
}
}
View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
- activity的onCreate方法
- fragment的onCreateView方法
@NonNull View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (host.getRenderMode() == RenderMode.surface) {
FlutterSurfaceView flutterSurfaceView =
new FlutterSurfaceView(
host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);
host.onFlutterSurfaceViewCreated(flutterSurfaceView);
flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
} else {
FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());
host.onFlutterTextureViewCreated(flutterTextureView);
flutterView = new FlutterView(host.getActivity(), flutterTextureView);
}
flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
flutterSplashView = new FlutterSplashView(host.getContext());
...
flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
flutterView.attachToFlutterEngine(flutterEngine);
return flutterSplashView;
}
这个方法也是最关键的方法之一,从这里我们可以了解到FlutterView 的创建和初始化流程。
void onStart()
- activity的onStart方法
- fragment的onStart方法
void onStart() {
ensureAlive();
doInitialFlutterViewRun();
}
看下这个doInitialFlutterViewRun() 做了什么:
private void doInitialFlutterViewRun() {
if (host.getCachedEngineId() != null) {
return;
}
if (flutterEngine.getDartExecutor().isExecutingDart()) {
return;
}
String initialRoute = host.getInitialRoute();
if (initialRoute == null) {
initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
if (initialRoute == null) {
initialRoute = DEFAULT_INITIAL_ROUTE;
}
}
flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
String appBundlePathOverride = host.getAppBundlePath();
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
}
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}
在这个方法中,真正启动执行了dart代码,也看到了initialRoute 是如何设置给engine的。
看到这里不知道你是否跟我一样有个疑问:NavigationChannel#setInitialRoute 执行的时候,dart的main方法还没有运行,也就是dart vm那边相关的channel还有被初始化,难道channel消息也具有“sticky”特性吗?后面章节将对此有所介绍。
void onResume()
- activity的onResume方法
- fragment的onResume方法
void onResume() {
ensureAlive();
flutterEngine.getLifecycleChannel().appIsResumed();
}
void onPause()
- activity的onPause方法
- fragment的onPause方法
void onPause() {
ensureAlive();
flutterEngine.getLifecycleChannel().appIsInactive();
}
void onStop()
- activity的onStop方法
- fragment的onStop方法
void onStop() {
ensureAlive();
flutterEngine.getLifecycleChannel().appIsPaused();
}
从onStop开始,FlutterActivity 在调用delegate的方法前,都要做delegate的非空检查。这是因为在engine复用的场景下,启动一个新的FlutterActivity 复用当前页面的engine并finish当前页面时,会触发当前页面的FlutterActivity#detachFromFlutterEngine 回调,导致delegate被release掉。
void onDestroyView()
- activity的onDestroy/detachFromFlutterEngine方法
- fragment的onDestroyView/detachFromFlutterEngine方法
- 代码分析
与onCreateView相呼应,用于FlutterView 相应的资源回收工作。
void onDestroyView() {
ensureAlive();
flutterView.detachFromFlutterEngine();
flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
}
void onDetach()
- activity的onDestroy/detachFromFlutterEngine方法
- fragment的onDetach/detachFromFlutterEngine方法
void onDetach() {
ensureAlive();
host.cleanUpFlutterEngine(flutterEngine);
if (host.shouldAttachEngineToActivity()) {
if (host.getActivity().isChangingConfigurations()) {
flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges();
} else {
flutterEngine.getActivityControlSurface().detachFromActivity();
}
}
if (platformPlugin != null) {
platformPlugin.destroy();
platformPlugin = null;
}
flutterEngine.getLifecycleChannel().appIsDetached();
if (host.shouldDestroyEngineWithHost()) {
flutterEngine.destroy();
if (host.getCachedEngineId() != null) {
FlutterEngineCache.getInstance().remove(host.getCachedEngineId());
}
flutterEngine = null;
}
}
FlutterFragment
与FlutterActivity 一样,由于公共逻辑都被抽取到了FlutterActivityAndFragmentDelegate 中,包括生命周期方法内的调用逻辑大部分是相同的。而在Host部分FlutterFragment 的代码就与FlutterActivity 有所区别了,比如下面:
@Override
@Nullable
public SplashScreen provideSplashScreen() {
FragmentActivity parentActivity = getActivity();
if (parentActivity instanceof SplashScreenProvider) {
SplashScreenProvider splashScreenProvider = (SplashScreenProvider) parentActivity;
return splashScreenProvider.provideSplashScreen();
}
return null;
}
@Override
@Nullable
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
FlutterEngine flutterEngine = null;
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineProvider) {
FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity;
flutterEngine = flutterEngineProvider.provideFlutterEngine(getContext());
}
return flutterEngine;
}
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
}
}
@Override
public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).cleanUpFlutterEngine(flutterEngine);
}
}
@Override
public void onFlutterUiDisplayed() {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterUiDisplayListener) {
((FlutterUiDisplayListener) attachedActivity).onFlutterUiDisplayed();
}
}
@Override
public void onFlutterUiNoLongerDisplayed() {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterUiDisplayListener) {
((FlutterUiDisplayListener) attachedActivity).onFlutterUiNoLongerDisplayed();
}
}
可以看到FlutterFragment 是把上述方法的实现托付给宿主activity的,虽然FlutterActivity 可以满足其需求,但是我们不可能在FlutterActivity 里面去嵌套FlutterFragment 来使用。我们分析一下,当我们用一个普通的Activity去加载FlutterFragment 实例会上述方法会有什么影响:
SplashScreen provideSplashScreen() 此方法返回一个自定义启动动画,FlutterFragment 默认没有实现,由于attachedActivity是普通Activity,固然也没有实现SplashScreenProvider 接口,于是flutter页面打开时将没有自定义动画,会导致黑屏问题。FlutterEngine provideFlutterEngine(Context context) 此方法返回一个engine实例,返回null时FlutterFragment 的delegate会new一个默认的engine,看似没有问题,但是这个默认的engine的automaticallyRegisterPlugins 为false,也就是不会执行插件注册代码,同时FlutterFragment#configureFlutterEngine() 中默认也没有执行插件注册,会导致flutter插件失效。void configureFlutterEngine(FlutterEngine flutterEngine) 配置engine的hook入口,可以不实现,但是就像前面提到的,会使得flutter插件失效。void cleanUpFlutterEngine(FlutterEngine flutterEngine) 无影响void void onFlutterUiDisplayed() 无影响void void onFlutterUiNoLongerDisplayed() 无影响
为解决上述问题,我们有两种选择:
- Activity继承于SDK提供的
FlutterFragmentActivity ; - 继续使用我们自己的Activity,自定义
FlutterFragment 重写相关方法解决相应问题。
使用FlutterFragmentActivity
此类主要是构造一个FlutterFragment 并将之添加到页面中,其他Host实现与FlutterActivity 完全一致,因此可以解决上面的问题。但是有2点明显的限制:
- 由于Java的单继承特性,我们的原生Activity通常都是要继承于BaseActivity的,故无法继承
FlutterFragmentActivity ; - 从
FlutterFragmentActivity#onCreate 的实现来看,这个activity也是一个flutter full page的页面,无法显示类似FlutterFragment 与其他原生fragment一起放在ViewPager里面进行展示的效果。
出于上述原因,推荐使用自定义FlutterFragment 来解决问题。
使用自定义FlutterFragment
明确问题之后,解决方案就简单了:
public class FlutterProxyFragment extends FlutterFragment {
@Nullable
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
FlutterEngine engine = super.provideFlutterEngine(context);
if (engine == null) {
engine= new FlutterEngine(
getContext(),
getFlutterShellArgs().toArray(),
true,
shouldRestoreAndSaveState());
}
return engine;
}
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
} else {
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
}
@Override
public SplashScreen provideSplashScreen() {
SplashScreen splashScreen = super.provideSplashScreen();
if (splashScreen != null) return splashScreen;
return new LoadingSplashScreen();
}
...
}
上述代码似乎是解决了问题,但其实还有很多细节没有解决。我们对照FlutterFragmentActivity 源码,可以发现下述代码:
@Override
public void onPostResume() {
super.onPostResume();
flutterFragment.onPostResume();
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
flutterFragment.onNewIntent(intent);
super.onNewIntent(intent);
}
@Override
public void onBackPressed() {
flutterFragment.onBackPressed();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
flutterFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onUserLeaveHint() {
flutterFragment.onUserLeaveHint();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
flutterFragment.onTrimMemory(level);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
flutterFragment.onActivityResult(requestCode, resultCode, data);
}
这些需要activity辅助调用的方法都被贴心地打上了@ActivityCallThrough 标记,妈妈再也不用担心我有遗漏了!
这些方法,要么不是Fragment固有的回调方法,要么虽是固有回调方法,但是必须通过fragment调用才能生效。以onActivityResult为例,只有调用fragment.startActivityForResult 时该回调才会被触发,我们在编写flutter插件时,通常是这样的流程:
public class MyPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.ActivityResultListener{
private MethodChannel channel;
private Activity host;
ActivityPluginBinding binding;
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result){...}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
channel = new MethodChannel(binding.getBinaryMessenger(), "myplugin");
channel.setMethodCallHandler(this);
final FlutterEngine engine = binding.getFlutterEngine();
engine.getPlatformViewsController().getRegistry().registerViewFactory(
"my_view_factory",
new MyViewFactory(engine.getDartExecutor().getBinaryMessenger()));
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
channel = null;
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
mDelegate.setActivity(binding.getActivity());
host = binding.getActivity();
this.binding = binding;
binding.addActivityResultListener(this);
}
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}
@Override
public void onDetachedFromActivity() {
binding.removeActivityResultListener(this);
binding = null;
host = null;
}
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {...}
}
也就是说即便使用FlutterFragment ,我们还是使用activity.startActivityForResult 启动页面,所以FlutterFragment#onActivityResult 并不会回调,插件的onActivityResult也就无法回调了。这里提供一些解决思路:
public class FlutterProxyFragment extends FlutterFragment {
private OnBackPressedCallback mOnBackPressedCallback;
private OnBackPressedCallback fetchOnBackPressedCallback(){
if (mOnBackPressedCallback == null) {
mOnBackPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackPressed();
}
};
}
return mOnBackPressedCallback;
}
protected boolean watchOnBackPress(){
return getArguments().getBoolean("WATCH_ON_BACK_PRESS",false);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (watchOnBackPress() && context instanceof ComponentActivity && !(context instanceof FlutterFragmentActivity)) {
ComponentActivity activity = (ComponentActivity) context;
activity.getOnBackPressedDispatcher().addCallback(activity, fetchOnBackPressedCallback());
}
}
...
}
public class FlutterProxyFragment extends FlutterFragment {
private ComponentCallbacks2 mComponentCallbacks;
private ComponentCallbacks2 fetchComponentCallbacks2(){
if (mComponentCallbacks==null){
mComponentCallbacks=new ComponentCallbacks2() {
@Override
public void onTrimMemory(int level) {
FlutterProxyFragment.this.onTrimMemory(level);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {}
@Override
public void onLowMemory() {}
};
}
return mComponentCallbacks;
}
final List<Runnable> clearUpTasks = new ArrayList<>();
private void clearUp(){
Iterator<Runnable> iterator = clearUpTasks.iterator();
while (iterator.hasNext()) {
iterator.next().run();
iterator.remove();
}
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof ComponentActivity && !(context instanceof FlutterFragmentActivity)) {
Application application = (Application) context.getApplicationContext();
application.registerComponentCallbacks(fetchComponentCallbacks2());
clearUpTasks.add(() -> application.unregisterComponentCallbacks(fetchComponentCallbacks2()));
}
}
@Override
public void onDetach() {
clearUp();
super.onDetach();
}
...
}
public interface BaseEventListener{
default void onPostResumeEvent(){}
default void void onNewIntentEvent(@NonNull Intent intent){}
default void onRequestPermissionsResultEvent(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){}
default void void onUserLeaveHintEvent(){}
default void onActivityResultEvent(int requestCode, int resultCode, @Nullable Intent data){}
}
public interface BaseEventRegistry{
void addBaseEventListener(@NonNull BaseEventListener l);
void removeBaseEventListener(@NonNull BaseEventListener l);
}
public class BaseActivity extends Activity implements BaseEventRegistry{
private Set<BaseEventListener> listeners = new LinkedHashSet();
@Override
public void addBaseEventListener(@NonNull BaseEventListener l){
listeners.add(l);
}
@Override
public void removeBaseEventListener(@NonNull BaseEventListener l){
listeners.remove(l);
}
@Override
public void onPostResume() {
super.onPostResume();
for (BaseEventListener listener : listeners) {
listener.onPostResumeEvent();
}
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
for (BaseEventListener listener : listeners) {
listener.onNewIntentEvent(intent);
}
super.onNewIntent(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
for (BaseEventListener listener : listeners) {
listener.onRequestPermissionsResultEvent(requestCode, permissions, grantResults);
}
}
@Override
public void onUserLeaveHint() {
for (BaseEventListener listener : listeners) {
listener.onUserLeaveHintEvent();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
for (BaseEventListener listener : listeners) {
listener.onActivityResultEvent(requestCode, resultCode, data);
}
}
}
public class FlutterProxyFragment extends FlutterFragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof BaseEventRegistry) {
((BaseEventRegistry) context).addBaseEventListener(this);
clearUpTasks.add(() -> ((BaseEventRegistry) context).removeBaseEventListener(this));
}
}
@Override
public void onNewIntentEvent(@NonNull Intent intent) {
this.onNewIntent(intent);
}
@Override
public void onPostResumeEvent() {
this.onPostResume();
}
@Override
public void onUserLeaveHintEvent() {
this.onUserLeaveHint();
}
@Override
public void onActivityResultEvent(int requestCode, int resultCode, @Nullable Intent data) {
this.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onRequestPermissionsResultEvent(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
this.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
...
}
FlutterView
真正渲染flutter页面的控件,归属FlutterActivityAndFragmentDelegate 。主要分为两个部分:
RenderSurface :对接FlutterEngine 的FlutterRenderer ,用于画面的绘制;- Plugins/Bridge/Processor:对接
FlutterEngine 的各种Channel,用于用户交互事件处理;
由于都需要对接engine,只有attach engine后才能开始工作,当然用完还需要detach。
- attach调用链
flutterView.attachToFlutterEngine(FlutterEngine flutterEngine) flutterEngine.getPlatformViewsController().attachToView(Veiw view) - detach调用链
flutterView.detachFromFlutterEngine() flutterEngine.getPlatformViewsController().detachFromView()
从这一点看,PlatformView 和FlutterView 流程是一样的
RenderSurface
归属FlutterView 。用于呈现flutter的图像数据(FlutterRenderer 管理,实例归属engine),作用相当于画布或者屏幕,目前flutter支持三种RenderSurface :
io.flutter.embedding.android.FlutterImageView :利用普通eView将flutter图像渲染成一张图片,不支持flutter交互;io.flutter.embedding.android.FlutterSurfaceView :利用SurfaceView将flutter图像渲染成界面,支持交互;io.flutter.embedding.android.FlutterTextureView :利用TextureView将flutter图像渲染成界面,支持交互;
FlutterEngine
创建和维护与flutter交互的相关FlutterRenderer 、各种Channel等,默认由FlutterActivityAndFragmentDelegate 创建和回收实例,也支持由用于在FlutterActivity /FlutterFragment 等Host中返回自己维护的engine实例。
XXXChannel
功能型Channel种类和功能简介
这些功能型Channel底层通讯其实都是借助三种通讯型Channel实现的,后面会专门介绍。
engine在构造方法中实例化了各种功能型Channel用于特定领域的原生和dart间的通讯,包括:
AccessibilityChannel DeferredComponentChannel KeyEventChannel LifecycleChannel LocalizationChannel MouseCursorChannel NavigationChannel :用于java和dart之间路由交互。PlatformChannel RestorationChannel SettingsChannel SystemChannel TextInputChannel
有些Channel是直接使用,而有些套了一层壳成了XXXPlugin。由于功能型Channel数量比较多,不是本文的重点就不逐一详细介绍了,后面会介绍一些比较常用的Channel。
NavigationChannel 这个是我比较感兴趣的模块,看下Java端代码:
public class NavigationChannel {
@NonNull public final MethodChannel channel;
public NavigationChannel(@NonNull DartExecutor dartExecutor) {
this.channel = new MethodChannel(dartExecutor, "flutter/navigation", JSONMethodCodec.INSTANCE);
}
public void setInitialRoute(@NonNull String initialRoute) {
channel.invokeMethod("setInitialRoute", initialRoute);
}
public void pushRoute(@NonNull String route) {
channel.invokeMethod("pushRoute", route);
}
public void popRoute() {
channel.invokeMethod("popRoute", null);
}
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
channel.setMethodCallHandler(handler);
}
}
可以说是简洁得不能再简洁了,看下dart端:
//sdk/packages/flutter/lib/src/services/system_channels.dart:50
//全局单例的channel实例
static const MethodChannel navigation = OptionalMethodChannel(
'flutter/navigation',
JSONMethodCodec(),
);
//sdk/packages/flutter/lib/src/widgets/binding.dart:
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
...
// 为navigation设置回调
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
...
}
Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
// 对比java那边的代码发现:
// 1. 少了"setInitialRoute"
// 2. 多了'pushRouteInformation'
switch (methodCall.method) {
case 'popRoute':
return handlePopRoute();
case 'pushRoute':
return handlePushRoute(methodCall.arguments as String);
case 'pushRouteInformation':
return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>);
}
return Future<dynamic>.value();
}
@protected
Future<void> handlePopRoute() async {
// 依次挨个关闭页面
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
if (await observer.didPopRoute())
return;
}
// 没有可以关闭的页面,则直接关闭FlutterActivity
SystemNavigator.pop();
}
@protected
@mustCallSuper
Future<void> handlePushRoute(String route) async {
for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
if (await observer.didPushRoute(route))
return;
}
}
...
}
那么这个缺失的setInitialRoute 是在哪里实现的呢?后面dart侧启动过程简介对此有分析。
通讯型Channel类型及实现简介
channel可以分为:
MethodChannel BasicMessageChannel<T> EventChannel
我们先看下Java和dart端MethodChannel 的构造方法:
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
public BasicMessageChannel(
@NonNull BinaryMessenger messenger, @NonNull String name, @NonNull MessageCodec<T> codec) {
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
public EventChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
//sdk/packages/flutter/lib/src/services/platform_channel.dart
const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
const BasicMessageChannel(this.name, this.codec, { BinaryMessenger? binaryMessenger })
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
const EventChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger])
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
可以看到在构造Channel实例时,所需的参数是一模一样的,需要传入三个参数:
BinaryMessenger messenger 数据发送通道,无论哪种Channel,走的通道都是这个。BinaryMessenger 本身是一个接口,其真正的实现类是DartMessenger ,其他均为代理类,而最终发送数据的方法都是走的FlutterJNI 实例的native方法。String name 渠道名称,可以认为是渠道的ID。MethodCodec/MessageCodec<T> codec 数据编解码器,用于channel通讯时接口实参的编解码。
任何通讯型Channel在最终都是通过BinaryMessenger#send(String, ByteBuffer, BinaryMessenger.BinaryReply) 向flutter发送消息,且通过BinaryMessenger#setMessageHandler(String, BinaryMessageHandler) 接收回执并分发回调。
了解jsbridge这类Java-JS通讯框架的读者可能闻到了熟悉的味道,是的,流程基本是一样的。
BinaryMessenger
BinaryMessenger 本身是一个接口,真正的实现类是DartMessenger ,所以channel的整个调用链是:
FunctionChannel(Optional) -> CommunicationChannel -> BinaryMessenger(DartMessenger) -> FlutterJNI
再去扒一下这其中涉及到的FlutterJNI的方法:
- void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId)
- void dispatchPlatformMessage(@NonNull String channel, @Nullable ByteBuffer message, int position, int responseId)
- void invokePlatformMessageEmptyResponseCallback(int responseId)
- void invokePlatformMessageResponseCallback(int responseId, @Nullable ByteBuffer message, int position)
好家伙!四个方法实现了channel核心功能,果然大道至简。既然到了这里了不妨再深入一步,前面提到:
在构造通讯型channel时,需要传入一个name,而这个name是作为ID使用的,那如果编码过程不规范,不同的flutter plguin工程在构造各自的channel时使用了相同的name会有什么后果呢?我们从消息发送侧和消息接收侧两个角度来分别分析:
@NonNull private final Map<String, BinaryMessenger.BinaryMessageHandler> messageHandlers;
@Override
public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) {
if (handler == null) {
messageHandlers.remove(channel);
} else {
messageHandlers.put(channel, handler);
}
}
所以在同一个engine实例中,如果存在两个同名channel,消息接收回调会有覆盖问题,导致前一个回调失效。
通过上面的分析我们可以得出结论:
- 在构造Channel时一定要有良好的编码规范,保证name的唯一性;
- 要做好Channel的生命周期管理,及时回收Channel,尤其是及时移除MessageHandler,防止内存泄漏;
MethodCodec/MessageCodec<T>
对于MethodCodec ,Android端默认提供了两种实现:
- `StandardMethodCodec`: 单例,真正编解码交给了`StandardMessageCodec`实例
- `JSONMethodCodec`:单例,真正编解码交给了`JSONMessageCodec`实例,而`JSONMessageCodec`编解码交给了`StringCodec`实例
与之对应的,flutter端也提供了两种实现:
- `StandardMethodCodec`:真正编解码交给了`StandardMessageCodec`实例
- `JSONMethodCodec`:真正编解码交给了`JSONMessageCodec`实例,而`JSONMessageCodec`实例真正编解码交给了`StringCodec`实例
而对于MessageCodec ,flutter framework提供了4种实现:
MessageCodec编解码器 | java数据类型 | dart数据类型 | 使用情况 |
---|
StandardMessageCodec | - null
- Booleans
- Bytes, Shorts, Integers, Longs
- BigIntegers (see below)
- Floats, Doubles
- Strings
- byte[], int[], long[], double[]
- Lists of supported values
- Maps with supported keys and values
| - null: null
- Boolean: bool
- Byte, Short, Integer, Long: int
- Float, Double: double
- String: String
- byte[]: Uint8List
- int[]: Int32List
- long[]: Int64List
- double[]: Float64List
- List: List
- Map: Map
| - AccessibilityChannel
- FlutterJNI
| JSOMessageCodec | UTF-8编码的json对象 | UTF-8编码的json对象 | - LocalizationChannel
- NavigationChannel
- PlatformChannel
- TextInputChannel
| BinaryCodec | ByteBuffer | ByteData | – | StringCodec | UTF-8编码的String | UTF-8编码的String | - DartExecutor
- JSONMessageCodec
- LifecycleChannel
|
所以,对于数据编解码器,粗略可以得到下面的结论:
- MethodCodec是MessageCodec的包装类,底层其实都是MessageCodec在干活;
- java和dart端Channel对应的编解码器必须是对应的才能正常完成传输数据编解码;
FlutterJNI
实例由engine持有和销毁。dart代码调用门面类,所以用的地方有点多:FlutterEngine 、DartExecutor 、各种Channel、FlutterRenderer 等等。
DartExecutor
实例由engine创建和维护,用于原生和flutter通信。乍看似乎作用跟FlutterJNI 有点重合,但我们作为上层应用开发基本只要和DartExecutor 打交道就好了,直接使用FlutterJNI 不仅繁杂还容易出现兼容性问题,况且DartExecutor 已经为我们封装好了友好的API接口。
DartMessenger
归属DartExecutor ,是真正发送消息的BinaryMessenger ,前面介绍Channel时已经介绍过,这里略过。
dart侧启动过程简介
本来不想写dart的流程,但由于提到了java层的各种Channel,这里不得不稍微提一下。先看看java是怎么启动dart的main方法的,在前面介绍的FlutterActivityAndFragmentDelegate 中:
private void doInitialFlutterViewRun() {
...
String initialRoute = host.getInitialRoute();
if (initialRoute == null) {
initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
if (initialRoute == null) {
initialRoute = DEFAULT_INITIAL_ROUTE;
}
}
flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
String appBundlePathOverride = host.getAppBundlePath();
if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
}
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
appBundlePathOverride, host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}
默认情况下,我们没有在host.getDartEntrypointFunctionName() 中返回自定义的入口点名称,所以就走默认的,即“main”。那如果要自定义dart入口方法该怎么做呢?
- java侧:
在host.getDartEntrypointFunctionName() 返回自定义FlutterActivity /FlutterFragment ,覆写getDartEntrypointFunctionName() 方法:
class MyFlutterActivity extends FlutterActivity{
@NonNull
public String getDartEntrypointFunctionName() {
boolean useCustomDartEntry = true;
if(useCustomDartEntry){
return "userMain";
} else {
return super.getDartEntrypointFunctionName();
}
}
}
void main() {
runApp(getFirstScreen(window.defaultRouteName));
}
/// 采用@pragma('vm:entry-point')来标记入口函数
@pragma('vm:entry-point')
void userMain() {
//耗时方法要异步执行
SPUtil().init().then((v){
runApp(MaterialApp(
home: Text("这是userMain"),
));
});
}
flutter侧那么多的channel又是什么时候初始化的呢?我们先从runApp 这个方法为入口开始寻觅:
//sdk/packages/flutter/lib/src/widgets/binding.dart
void runApp(Widget app) {
//初始化WidgetsBinding实例
WidgetsFlutterBinding.ensureInitialized()
//异步执行WidgetsBinding#attachRootWidget方法,更新root widget
..scheduleAttachRootWidget(app)
//执行SchedulerBinding#scheduleWarmUpFrame立即渲染一个启动页,而不必等系统"Vsync"信号
..scheduleWarmUpFrame();
}
WidgetsBinding是所有binding的胶水类,初始化WidgetsBinding实例也就初始化了flutter bindings,但是还是无法解释runApp 执行之前,java端navigationChannel.setInitialRoute(initialRoute) 如何生效。于是继续扒window.defaultRouteName ,这里window是SingletonFlutterWindow 的全局实例(单例),defaultRouteName则调用了PlatformDispatcher#defaultRouteName ,而PlatformDispatcher也是个单例,相关代码:
/// 调用native方法获取原生侧设置的InitialRoute,接口文档也强调[`FlutterView.setInitialRoute`]
/// 和[`FlutterViewController.setInitialRoute`]必须在运行dart入口方法前调用
String get defaultRouteName => _defaultRouteName();
String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName';
至此终于有了思路,或许是用c层做桥接做了InitialRoute的缓存而已,也不存在runApp 执行之前java与dart发生交互的事情。
FlutterRenderer
归属engine,处理flutter图像渲染相关的功能,涉及到相关flutterJNI接口的调用,了解不多,这里略过。
FlutterEngineConnectionRegistry
归属engine,实现了PluginRegistry 、ActivityControlSurface 、ServiceControlSurface 、 BroadcastReceiverControlSurface 、 ContentProviderControlSurface 接口,是flutter插件的注册中心,并赋予插件与四大组件之间基本的attach/detach回调和组件独有的回调能力。
提到flutter插件需要额外关注插件的注册时机,只有执行了注册,插件才能正常使用。通过前面的代码分析,我们可以总结有以下两个注册时机:
FlutterEngine 构造方法 当automaticallyRegisterPlugins 为true时,会调用FlutterEngine#registerPlugins 进行注册;FlutterEngineConfigurator#configureFlutterEngine(@NonNull FlutterEngine flutterEngine) 在FlutterActivity 和FlutterFragmentActivity 的方法重写中,调用了GeneratedPluginRegister#registerGeneratedPlugins() 完成了插件注册;
源码看到这里的时候,一度发生逻辑混乱,PluginRegistry 的实现类一会只有一个,一会有多个,仔细看发现有两个同名接口:io.flutter.embedding.engine.plugins.PluginRegistry 和io.flutter.plugin.common.PluginRegistry ,而后者已经被废弃。读者在看flutter源码的时候要注意以embedding包为主。
PlatformViewsController
归属engine,用于实现PlatformViews功能,对接engine的PlatformViewsChannel 。前面也提到过,需要engine调用getActivityControlSurface().attachToActivity() ->PlatformViewsController#attach 才真正完成初始化。
其他
上面介绍的几个类是整个flutter运行的核心类(类似于bootstrap class),了解这些类的作用我们就从宏观上了解了flutter的运行细节。剩下就是flutter各个具体的功能模块了,按需了解即可,这里简要介绍一下。
PlatformPlugin
归属FlutterActivityAndFragmentDelegate ,对接engine的PlatformChannel 。我们从PlatformPlugin#mPlatformMessageHandler 可以了解到其主要负责处理一些琐碎的系统服务功能,包括:
- 触觉的震动、提示音;
- 屏幕方向;
- 任务管理器中任务描述信息(TaskDescription);
- 系统界面overlay相关:状态栏、导航栏显示等;
- 页面栈回退处理:activity/fragment,对接dart端的SystemNavigator;
- 读写系统剪贴板内容;
LocalizationPlugin
归属engine,对接engine的LocalizationChannel ,处理时区相关逻辑。支持获取当前时区和通知flutter时区变更。
engine管理策略
前面我们把flutter核心模块的代码基本过了一遍,对相应的类功能和用法也有了了解,本节开始将介绍一些开发相关的内容。 首要需要确定的是engine管理策略,因为不同的管理策略的代码实现复杂度差异较大,内存使用和性能方面也有所差异。flutter engine的管理模式有两种:
- 多engine模式:每次启动flutter页面都创建新的engine实例;
- 单engine模式:整个APP复用同一个engine实例;
关于engine管理策略的选择我认为没有好坏之分,否则flutter官方没有必要同时支持了这两种方式。这就跟我们在使用WebView 时是选择用单例还是创建新的实例一样,应该根据APP的业务场景来定。
多engine策略
广义上的engine隔离策略,每个flutter页面都创建新的engine,由于engine实例之间的堆内存是独立和隔离的,dart侧的缓存不可共享,需要原生搭桥进行通讯。这种策略:
- 优点:
- 实现简单,开发代码量少,不需要维护复杂的页面栈;
- flutter framework默认的策略,因此兼容性最好;
- 缺点:
- 无法使用flutter侧内存缓存;
- 多engine内存消耗会稍多;
- 每次启动flutter页面都要经历engine初始化过程;
实际上针对多engine策略的缺陷,flutter已做了优化工作:
FlutterEngine#spawn 可用于在已有engine的基础上以极低的内存和时间成本创建第二个engine,详见FlutterEngineGroup ,使用方法比较简单这里不详细介绍了;- release版的flutter编译成果物engine初始化时间实测是小于300毫秒的,用户几乎感受不到;
页面实例构建
对于activity:
public class FlutterProxyActivity extends FlutterActivity {
static Intent newEngineIntent(Context context,String pageUri){
Intent intent = new FlutterActivity.NewEngineIntentBuilder(FlutterProxyActivity.class)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
.initialRoute(pageUri)
.build(context);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return intent;
}
...
}
对于fragment:
public class FlutterProxyFragment extends FlutterFragment {
public static Fragment newEngineFragment(String pageUri) {
return new NewEngineFragmentBuilder(FlutterProxyFragment.class)
.renderMode(RenderMode.texture)
.initialRoute(pageUri)
.build();
}
...
}
这里只列举了最常见的启动参数,完全可以自定义更多参数
引擎管理
flutter默认策略
每次开启一个flutter页面时(包括activity/fragment),都创建新的engine。engine实例生命周期跟随页面实例,即页面创建同时创建engine,页面销毁同时销毁engine。这种方案的优点是实现简单,缺点就像前面提到的,内存占用问题和启动耗时问题。
FlutterEngineGroup 方案
从前面关于FlutterEngine 和FlutterActivityAndFragmentDelegate 的分析可知,默认创建engine实例调用的是engine的构造方法。FlutterEngine 同时还提供了FlutterEngine#spawn(@NonNull Context context, @NonNull DartEntrypoint dartEntrypoint) 来构建engine,从而加速实例化过程和节省内存消耗。那么优化后的方案可以这样:
- 启动第一个flutter页面时,走默认构造方法创建engine实例,并缓存engine实例;
- 启动第二个flutter页面时,走
engine#spawn 创建新的实例,并缓存engine实例,第n个flutter页面亦如是; - 关闭flutter页面时,同时销毁engine并移除engine缓存,第n个flutter页面亦如是;
这个方案的优点是从第二个flutter页面开始启动速度和内存占用都是比较理想的,并且flutter给我们提供了现成的工具类FlutterEngineGroup 。用法也很简单:
FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
DartExecutor.DartEntrypoint dartEntrypoint = new DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "entry_0"
);
FlutterEngine topEngine = engineGroup.createAndRunEngine(this, dartEntrypoint);
FlutterEngineCache.getInstance().put("entry_0", topEngine);
FlutterEngineCache.getInstance().remove("entry_0")
强烈建议看下FlutterEngineGroup的实现原理,这样可以帮助我们实现各种自定义多engine方案。
自定义策略
如果对上面的方案不满意,可以实现自己的方案,比如:
- 启动第一个flutter页面时,走默认构造方法创建engine实例,并缓存engine实例;
- 启动第二个flutter页面时,走
engine#spawn 创建新的实例,第n个flutter页面亦如是; - 关闭第二个flutter页面时,同时销毁engine,第n个flutter页面亦如是;
- 关闭第一个flutter页面时,不销毁engine;
- flutter全部关闭,再次启动第一个flutter页面,由于存在一个缓存的engine实例,于是直接使用该实例;
- 从第2步开始循环操作;
路由管理
每个engine实例在创建时可以传入initialRoute指定初始页面,并且通过URL可实现待参跳转
窗口管理
在一次flutter会话中,建议直接在当前FlutterActivity push新的flutter页面。如果这期间打开了原生的activity并覆盖在FlutterActivity 之上,此时又需要打开一个flutter页面,那么要分情况讨论:
- 新的flutter页面跟上一个是同一个会话
此时应该finishUntil上一个FlutterActivity 实例,并在该实例中push新的flutter页面。 - 新的flutter页面是一个全新的会话
此时直接创建一个新的FlutterActivity 实例并start即可。
不过这种逻辑也并不能绝对的,上述逻辑可以作为默认逻辑,也应该提供配置项让用户强制指定新flutter页面的窗口策略。比如:
- new:新开
FlutterActivity ; - current:直接在上个
FlutterActivity 打开,不管是否被覆盖; - back:对上前面的finishUntil上一个
FlutterActivity 实例; - default:上面的默认逻辑,作为缺省值;
单engine策略
广义上也就是engine复用策略。
页面实例构建
对于activity:
public class FlutterProxyActivity extends FlutterActivity {
static Intent cachedEngineIntent(Context context, String engineId) {
Intent intent = new CachedEngineIntentBuilder(FlutterProxyActivity.class, engineId)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
.destroyEngineWithActivity(true)
.build(context);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return intent;
}
...
}
对于fragment:
public class FlutterProxyFragment extends FlutterFragment {
private static final String TAG = "FlutterProxyFragment";
public static Fragment cachedEngineFragment(String engineId) {
CachedEngineFragmentBuilder builder = new CachedEngineFragmentBuilder(FlutterProxyFragment.class,engineId)
.renderMode(RenderMode.texture)
.destroyEngineWithFragment(false);
return builder.build();
}
...
}
引擎管理
由于整个APP复用一个engine,只需要关注engine的初始化和销毁时机即可。
- 初始化
- 懒加载:即应用启动时先不初始化engine,直到第一个flutter页面渲染时才初始化
- 立即加载:应用启动时立即开启线程初始化一个engine
- 销毁
- 销毁:当所有flutter页面销毁时,销毁engine,从而减少APP内存占用
- 不销毁:保持一个engine实例,不去销毁,随时快速加载flutter页面
路由管理
需要借助自定义channel实现页面更新。
窗口管理
同多engine策略,不过单engine在实现上会困难得多,因为所有FlutterActivity 都共享engine,FlutterActivity 的页面栈需要随时整体切换刷新。实现上可以参考flutter_boost等一众相关开源项目。
|