前言
在 native 和 Flutter 的混合开发中,混合栈是一个绕不开的话题。在 Flutter
2.x 版本 FlutterEngineGroup
的多引擎方案之前,直接使用 Flutter
多引擎的内存占用是比较大的。所以存在业界开源比较有名的单引擎方案 FlutterBoost。FlutterBoost 的地址是:https://github.com/alibaba/flutter_boost我们调用了它的方案后,顺便来看下它内部的工作流程和设计思路。
引擎复用
在 application 初始化的时候,会调 FlutterBoost
的 setup:
public void setup(Application application, FlutterBoostDelegate delegate, Callback callback, FlutterBoostSetupOptions options) {
// 1. initialize default engine
FlutterEngine engine = getEngine();
if (engine == null) {
if (options.flutterEngineProvider() != null) {
FlutterEngineProvider provider = options.flutterEngineProvider();
engine = provider.provideFlutterEngine(application);
}
if (engine == null) {
engine = new FlutterEngine(application, options.shellArgs());
}
FlutterEngineCache.getInstance().put(ENGINE_ID, engine);
}
if (!engine.getDartExecutor().isExecutingDart()) {
// Pre-warm the cached FlutterEngine.
engine.getNavigationChannel().setInitialRoute(options.initialRoute());
engine.getDartExecutor().executeDartEntrypoint(new DartExecutor.DartEntrypoint(
FlutterMain.findAppBundlePath(), options.dartEntrypoint()));
}
// 2. set delegate
getPlugin().setDelegate(delegate);
//3. register ActivityLifecycleCallbacks
setupActivityLifecycleCallback(application, isBackForegroundEventOverridden);
}
这里会创建复用的 Flutter
引擎。并且设置 delegate, 绑定生命周期的监听。最终 FlutterEngineCache
这个类会维护创建的 FlutterEngine
对象。
在 FlutterActivity
里面会执行 delegate#onAttach 方法 这里的 flutterEngine 应该是复用唯一的,如果是null,说明是页面重建。就是去创建:
provideFlutterEngine 的逻辑由容器提供
FlutterActivity默认没有提供
FlutterFragment使用 Activity提供,如果Activity不是
FlutterEngineProvider, 也返回nul
View同理。如果没有提供,那么会创建一个
FlutterEngine不过这里是有默认的引擎id的。也就是
flutter_boost_default_engine`
FlutterEngineCache.getInstance().put(ENGINE_ID, engine);
getPlugin().setDelegate(delegate);
setupActivityLifecycleCallback(application, isBackForegroundEventOverridden);
代码语言:javascript复制void onAttach(@NonNull Context context) {
if (flutterEngine == null) {
setupFlutterEngine();
}
}
混合栈路由
先看 Dart
侧的主要功能
dart
- 需要创建并初始化一个 Binding
BoostFlutterBindingBoostFlutterBinding
是WidgetsFlutterBinding
的子类 实现了
initInstances
handleAppLifecycleStateChanged
主要是处理生命周期变化的。
- 创建app,里面放boost的组件
FlutterBoostApp
,传入路由表、initroute和拦截器 里面其实是 appBuilder 决定。在initState
里会创建一个 BoostContainer。创建了NativeRouterApi
和BoostFlutterRouterApi
下一个frame完成的时候会执行:这个后面再看。
refreshOnPush(initialContainer);
_boostFlutterRouterApi.isEnvReady =true;
_addAppLifecycleStateEventListener();
BoostOperationQueue.instance.runPendingOperations();
- 开启新的页面
里面通过 FlutterBoostAppState
操作,在 instance 的时候初始化:
最后push是通过 FlutterBoostAppState
的 pushWithInterceptor
实现: 先忽略拦截器的逻辑,直接看不拦截的:
flutter页面和非flutter页面分别走 pushWithResult
和 nativeROuterApi
如果withCotainer为true,则需要新的容器页,通过native去打开。如果withcontainer为false,则不需要新的容器。调用 pushPage
这里触发 notifyListeners
: 这里的监听反应是 setState, BoostContainerState
会刷新。flutterboost的层级为
HeroControllerScope下面的层级还包括了NavigatorExt
,这个是 flutterboost里自己封装的 Navigator 组件。页面监听则由BoostNavigatorObserver
负责:
Flutter内部跳转则根据 container.pages 来渲染页面:
如果 withContainer 是true:
则通过native去进行跳转:
这里通过dev.flutter.pigeon.NativeRouterApi.pushFlutterRoute
这个channel 进行通信 这里会打开新的 Flutter 容器页。接着看如果打开的路由不是一个 Flutter
页面:
调用 dev.flutter.pigeon.NativeRouterApi.pushNativeRoute
这个 channel
var result = topContainer.addPage(BoostPage.create(pageInfo));
// addPage
Future<T> addPage<T extends Object>(BoostPage page) {
if (page != null) {
_pages.add(page);
notifyListeners();
return page.popped;
}
return null;
}
- 关闭页面 使用
BoostNavigator
的pop
关闭一个页面。这里根据页面是否能回退决定是退出一个页面还是退出整个容器:
退出容器调用dev.flutter.pigeon.NativeRouterApi.popRoute
这个 channel
Android
- 跳转flutter:用这个
Intent
去跳转。
Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(
FlutterBoostActivity.class
)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
.destroyEngineWithActivity(false)
.url("flutterPage")
.urlParams(params)
.build(this);
- FlutterBoostActivity 继承了
FlutterActivity
,实现了FlutterViewContainer
接口。onCreate
里面调用 添加Flutter容器,如果当前容器size为1,把flutter的生命周期改为 resumedonResume
: 先尝试销毁前一个容器。在这里调用performDetach
方法 接着调用performAttach
然后调用FlutterPlugin
的onContainerAppeared
,这里会打开flutter响应的路由
FlutterBoost.instance().getPlugin().onContainerCreated(this);
代码语言:javascript复制// FlutterBootPlugin onContainerCreated
FlutterContainerManager.instance()
.addContainer(container.getUniqueId(), container);
if (FlutterContainerManager.instance().getContainerSize() == 1) {
FlutterBoost.instance()
.changeFlutterAppLifecycle(FlutterBoost.FLUTTER_APP_STATE_RESUMED);
}
代码语言:javascript复制public void onResume() {
super.onResume();
final FlutterContainerManager containerManager = FlutterContainerManager.instance();
stage = LifecycleStage.ON_RESUME;
FlutterViewContainer top = containerManager.getTopContainer();
if (top != null && top != this) top.detachFromEngineIfNeeded();
performAttach();
textureHooker.onFlutterTextureViewRestoreState();
FlutterBoost.instance().getPlugin().onContainerAppeared(this);
getFlutterEngine().getLifecycleChannel().appIsResumed();
platformPlugin.updateSystemUiOverlays();
}
- 处理跳转的 plugin部分
dev.flutter.pigeon.NativeRouterApi.pushFlutterRoute
这个是flutter使用新的容器跳转flutter页面的处理部分:
这里调用了 pushFlutterRoute
方法。具体实现在 FlutterBoostPlugin
里;这里最终逻辑由 FlutterBoostDelegate
的 pushFlutterRoute
提供。boost的demo里如下实现:这里可以从api看出来容器是复用的。dev.flutter.pigeon.NativeRouterApi.pushNativeRoute
处理打开新的native页面。和上面的类似,走FlutterBoostDelegate
的 pushNativeRoute
方法。逻辑参考demo:如果是我们自己的代码,可以根据 FLutterBoostRouteOptions
里面的字段来判断我们需要跳转的页面。dev.flutter.pigeon.NativeRouterApi.popRoute
退出容器页面。先调用 FlutterBoostDelegate
的 popRoute
方法:这里返回false表示不拦截。默认返回也是false。当退出此容器后,会调用销毁逻辑:finishContainer 由每个容器的实现类去实现:
都是把所在的 Activity
给 finish 掉。
@Override
public void pushFlutterRoute(FlutterBoostRouteOptions options) {
Class<? extends FlutterBoostActivity> activityClass = options.opaque() ? FlutterBoostActivity.class : TransparencyPageActivity.class;
Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(activityClass)
.destroyEngineWithActivity(false)
// 注意:这里需要回传dart带过来的uniqueId,否则页面退出时传参可能失败。
// 但,如果是从Native打开Flutter页面,请不要给uniqueId赋*任何值*!!!
.uniqueId(options.uniqueId())
.backgroundMode(options.opaque() ? BackgroundMode.opaque : BackgroundMode.transparent)
.url(options.pageName())
.urlParams(options.arguments())
.build(FlutterBoost.instance().currentActivity());
FlutterBoost.instance().currentActivity().startActivity(intent);
}
代码语言:javascript复制@Override
public void pushNativeRoute(FlutterBoostRouteOptions options) {
Intent intent = new Intent(FlutterBoost.instance().currentActivity(), NativePageActivity.class);
FlutterBoost.instance().currentActivity().startActivityForResult(intent, options.requestCode());
}
代码语言:javascript复制// demo
@Override
public boolean popRoute(FlutterBoostRouteOptions options) {
Toast.makeText(FlutterBoost.instance().currentActivity(), "自定义popRoute处理逻辑", Toast.LENGTH_SHORT).show();
return false;
}
代码语言:javascript复制// FlutterBoostPlugin popRoute
FlutterViewContainer container = FlutterContainerManager.instance()
.findContainerById(uniqueId);
if (container != null) {
container.finishContainer((Map<String, Object>)
(Object) params.getArguments());
}
结构
从 native 侧的代码里可以看到 FlutterBoost 的一些概念:
- FlutterEngineCache 引擎管理类
- FlutterBoost 主要逻辑的维护者
- FlutterBoostPlugin 负责Flutter侧和native侧的通信
- FlutterViewContainer Flutter页面承载
从 dart 侧可以总结出:
- Container flutter容器
- ContainerManager 容器管理类,负责跳转等操作
总结
这里就大概了解了 FlutterBoost
的工作流程。我们也可以总结一些优缺点:优点
- 单引擎实现,内存占用比较低
- 页面生命周期管理做的比较到位
- 3.x 版本理论上和 Flutter 版本无关,不需要把 Flutter 版本和库版本绑定
但是 boost 的可见缺点也比较多
- 接入相对复杂。但是3.x相对之前版本简单了很多。文档也比较齐全了
- 侵入了 Flutter Android 侧的流程,native 层很多没有使用官方 ``io.flutter.embedding.android
里的内容,后续
Flutter` 升级后不一致的风险比较大。 - github issus 还比较多。针对最新版本的
Flutter
适配的也比较慢,看起来其主要服务对象还是闲鱼内部。