浅析 FlutterBoost

2022-12-05 14:13:50 浏览数 (2)

前言

在 native 和 Flutter 的混合开发中,混合栈是一个绕不开的话题。在 Flutter 2.x 版本 FlutterEngineGroup 的多引擎方案之前,直接使用 Flutter 多引擎的内存占用是比较大的。所以存在业界开源比较有名的单引擎方案 FlutterBoost。FlutterBoost 的地址是:https://github.com/alibaba/flutter_boost我们调用了它的方案后,顺便来看下它内部的工作流程和设计思路。

引擎复用

在 application 初始化的时候,会调 FlutterBoost 的 setup:

代码语言:javascript复制
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`

代码语言:javascript复制
FlutterEngineCache.getInstance().put(ENGINE_ID, engine);

getPlugin().setDelegate(delegate);
setupActivityLifecycleCallback(application, isBackForegroundEventOverridden);
代码语言:javascript复制
void onAttach(@NonNull Context context) {
 if (flutterEngine == null) {
  setupFlutterEngine();
 }
}

混合栈路由

先看 Dart 侧的主要功能

dart

  1. 需要创建并初始化一个 Binding BoostFlutterBindingBoostFlutterBindingWidgetsFlutterBinding 的子类 实现了
  • initInstances
  • handleAppLifecycleStateChanged

主要是处理生命周期变化的。

  1. 创建app,里面放boost的组件 FlutterBoostApp ,传入路由表、initroute和拦截器 里面其实是 appBuilder 决定。在 initState 里会创建一个 BoostContainer。创建了 NativeRouterApiBoostFlutterRouterApi下一个frame完成的时候会执行:这个后面再看。
代码语言:javascript复制
refreshOnPush(initialContainer);
_boostFlutterRouterApi.isEnvReady =true;
_addAppLifecycleStateEventListener();
BoostOperationQueue.instance.runPendingOperations();
  1. 开启新的页面

里面通过 FlutterBoostAppState 操作,在 instance 的时候初始化:

最后push是通过 FlutterBoostAppStatepushWithInterceptor 实现: 先忽略拦截器的逻辑,直接看不拦截的:

flutter页面和非flutter页面分别走 pushWithResultnativeROuterApi

如果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

代码语言:javascript复制

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;
}
  1. 关闭页面 使用 BoostNavigatorpop 关闭一个页面。这里根据页面是否能回退决定是退出一个页面还是退出整个容器:

退出容器调用dev.flutter.pigeon.NativeRouterApi.popRoute 这个 channel

Android

  1. 跳转flutter:用这个 Intent 去跳转。
代码语言:javascript复制
Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(
 FlutterBoostActivity.class
)
 .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
 .destroyEngineWithActivity(false)
 .url("flutterPage")
 .urlParams(params)
 .build(this);
  1. FlutterBoostActivity 继承了 FlutterActivity ,实现了 FlutterViewContainer 接口。onCreate 里面调用 添加Flutter容器,如果当前容器size为1,把flutter的生命周期改为 resumedonResume : 先尝试销毁前一个容器。在这里调用 performDetach 方法 接着调用 performAttach然后调用 FlutterPluginonContainerAppeared ,这里会打开flutter响应的路由
代码语言:javascript复制
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();
}
  1. 处理跳转的 plugin部分dev.flutter.pigeon.NativeRouterApi.pushFlutterRoute这个是flutter使用新的容器跳转flutter页面的处理部分:

这里调用了 pushFlutterRoute 方法。具体实现在 FlutterBoostPlugin 里;这里最终逻辑由 FlutterBoostDelegatepushFlutterRoute 提供。boost的demo里如下实现:这里可以从api看出来容器是复用的。dev.flutter.pigeon.NativeRouterApi.pushNativeRoute处理打开新的native页面。和上面的类似,走FlutterBoostDelegatepushNativeRoute 方法。逻辑参考demo:如果是我们自己的代码,可以根据 FLutterBoostRouteOptions 里面的字段来判断我们需要跳转的页面。dev.flutter.pigeon.NativeRouterApi.popRoute退出容器页面。先调用 FlutterBoostDelegatepopRoute 方法:这里返回false表示不拦截。默认返回也是false。当退出此容器后,会调用销毁逻辑:finishContainer 由每个容器的实现类去实现:

都是把所在的 Activity 给 finish 掉。

代码语言:javascript复制
@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 适配的也比较慢,看起来其主要服务对象还是闲鱼内部。

0 人点赞