作者:Firewayer,饿了么Android工程师
注:以下分析基于flutter_boost 0.1.61 版本进行,Native以android为准。
注:更全面的前世今生详见原作者分析,本文侧重源码实现层。
关键词:flutter、flutter_boost、架构、分析、源码
1.简介
新一代Flutter-Native混合解决方案。 FlutterBoost是一个Flutter插件,它可以轻松地为现有原生应用程序提供Flutter混合集成方案。FlutterBoost的理念是将Flutter像Webview那样来使用。在现有应用程序中同时管理Native页面和Flutter页面并非易事。 FlutterBoost帮你处理页面的映射和跳转,你只需关心页面的名字和参数即可(通常可以是URL)。
在混合开发模式中(Native+Flutter),不同于官方多engine的混合模式,boost以Engine复用的方式避免多Engine时的内存问题,比如:图片缓存、插件管理、多isolate页面通信等等, 另外Boost也特别统一了Native&&Flutter页面模型 。
2.整体思路
本文将通过基本用法、架构分析、源码分析的方式,了解flutter_boost的工作机制,解决了哪些问题。如以下几个问题:
- Boost是如何实现Engine复用的。
- Boost是如何在Engine复用的情况下管理多页面插件状态同步。
- Boost是如何打开Flutter/Native页面的。
- Boost还为我们躺了哪些不为人知的坑
3.基本用法
3.1 初始化
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
FlutterBoost.singleton.registerPageBuilders({
'flutterPage': (pageName, params, _) {
return FlutterRouteWidget(params:params);
},
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container());
}
void _onRoutePushed(
String pageName, String uniqueId, Map params, Route route, Future _) {
}
}
FlutterBoost.instance().init(new Platform());
3.2 Native启动Flutter页面(Android)
Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
.backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
activity.startActivityForResult(intent,requestCode);
3.3 Flutter启动Native页面(Dart)
FlutterBoost.singleton.open("sample://nativePage")
4.整体架构
Native层概念
- Container:Native容器,FlutterActivityAndFragmentDelegate.Host的实现,默认为BoostFlutterActivity
- Container Manager:容器的管理者,IContainerManager的实现,具体为FlutterViewContainerManager
- Messaging:基于Channel的消息通信,具体为FlutterBoostPlugin
Dart层概念
- Container:Flutter用来容纳Widget的容器,具体实现为Navigator的派生类-BoostContainer
- Container Manager:Flutter容器的管理,提供show,remove等Api,具体为BoostContainerManager
- Coordinator: 协调器,接受Messaging消息,负责调用Container Manager的状态管理。具体为ContainerCoordinator
- Messaging:基于Channel的消息通信,具体为BoostChannel
Boost中 Container、Manager对象相互映射、相互协作,Dart层根据Native同步过来的生命周期事件进行响应。
每个Container都有其独立的FlutterView,通过单例的FlutterEngine驱动渲染及插件注册通信, 并具有唯一Id,注册于Manager Map表中,通过Key-Value的形式获取而非栈结构。
5.Native启动flutter页面源码流程分析(Android)
时序图
5.1.发起Native启动Flutter页面调用
Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
.backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
activity.startActivityForResult(intent,requestCode);
5.2.进入BoostFlutterActivity&&onCreate生命周期回调
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
switchLaunchThemeForNormalTheme();
super.onCreate(savedInstanceState);
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
//Flutter相关操作代理对象,以组合的形式解藕复用Activity&&Fragment与Flutter相关的逻辑。
delegate = new FlutterActivityAndFragmentDelegate(this);
//1.准备FlutterEngine 2.初始化Activity&&Engine之间的配置
delegate.onAttach(this);
configureWindowForTransparency();
//创建FlutterView,并装载进Activity
setContentView(createFlutterView());
configureStatusBarForFullscreenFlutterExperience();
}
5.3.FlutterActivityAndFragmentDelegate.onAttach
void onAttach(@NonNull Context context) {
//合法性检查
ensureAlive();
//确保所需Flutter环境初始化完毕
if (FlutterBoost.instance().platform().whenEngineStart() == FlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED) {
FlutterBoost.instance().doInitialFlutter();
}
//若为新Activity,则需绑定Engine
if (flutterEngine == null) {
setupFlutterEngine();
}
···
host.configureFlutterEngine(flutterEngine);
host.getActivity().getWindow().setFormat(PixelFormat.TRANSLUCENT);
}
5.4.FlutterBoost.doInitialFlutter
public void doInitialFlutter() {
//FlutterBoost为单例,mEngine也只需初始化一遍
if (mEngine != null) return;
//注意此处会调用FlutterMain.startInitialization、ensureInitializationComplete。
FlutterEngine flutterEngine = createEngine();
if (mPlatform.lifecycleListener != null) {
mPlatform.lifecycleListener.onEngineCreated();
}
if (flutterEngine.getDartExecutor().isExecutingDart()) {
return;
}
//设置getNavigationChannel初始路由
if (mPlatform.initialRoute() != null) {
flutterEngine.getNavigationChannel().setInitialRoute(mPlatform.initialRoute());
}
//Dart代码入口
DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint(
FlutterMain.findAppBundlePath(),
"main"
);
//执行Dart代码
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
mRegistry = new BoostPluginRegistry(createEngine());
//注册所有插件
mPlatform.registerPlugins(mRegistry);
}
做过Flutter-Android热修复应该知道一旦ensureInitializationComplete被调用,Dart代码即加载进内存,此时无法替换产物热修。
由上述代码也可知,Boost情况下插件与Engine绑定,也只会被注册一次,Engine是单例,插件也是单例。那Boost是如何做到多个Activity的onActivityResult&&onNewIntent等生命周期方法正常回调到各自的Activity的呢,奥秘全在BoostPluginRegistry中。
5.5.BoostPluginRegistry
public class BoostPluginRegistry implements PluginRegistry {
···
public BoostPluginRegistry(FlutterEngine flutterEngine) {
this.flutterEngine = flutterEngine;
this.shimRegistrarAggregate = new BoostRegistrarAggregate();
this.flutterEngine.getPlugins().add(this.shimRegistrarAggregate);
}
public Registrar registrarFor(String pluginKey) {
Log.v("ShimPluginRegistry", "Creating plugin Registrar for '" + pluginKey + "'");
if (this.pluginMap.containsKey(pluginKey)) {
throw new IllegalStateException("Plugin key " + pluginKey + " is already in use");
} else {
this.pluginMap.put(pluginKey, (Object) null);
BoostRegistrar registrar = new BoostRegistrar(pluginKey, this.pluginMap);
this.shimRegistrarAggregate.addPlugin(registrar);
return registrar;
}
}
想弄清楚Flutter中插件机制,理解清楚以下名次的意义是非常重要的。
- PluginRegistry:插件注册中心, 顾名思义,管理插件的注册。
- Registrar:注册者, 注册者可以被插件所使用,提供插件所需要的Context、添加事件监听API等等...
- BoostRegistrarAggregate: 插件注册者的集合,FlutterPlugin, ActivityAware 2个接口的实现者,用于与Engine交互
Activity在生命周期方法中通过Delegate对象同步状态给Engine, Engine将状态同步给BoostRegistrarAggregate, Aggregate分发给所有的Registrar, Registrar同步所得的信息至具体的插件,在Registrar$onAttachedToActivity会将如onActivityForResult监听器添加至FlutterEngineActivityPluginBinding中,这些监听器会被Activity-->Delegate-->FlutterEngine-->FlutterEnginePluginRegistry-->Listener最终触发。至此,所有的状态信息都会回调到对应插件中。
5.6.BoostFlutterActivity.onResume
@Override
protected void onResume() {
super.onResume();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
delegate.onResume();
}
5.7.FlutterActivityAndFragmentDelegate.onResume
void onResume() {
//同步器、将状态通过Channel同步至Dart层
mSyncer.onAppear();
Log.v(TAG, "onResume()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsResumed();
//判断当前Activity是否为顶层Activity(即最新绑定到Registry的Binding),如是,则通过Engine触发状态分发
BoostPluginRegistry registry = (BoostPluginRegistry) FlutterBoost.instance().getPluginRegistry();
ActivityPluginBinding binding = registry.getRegistrarAggregate().getActivityPluginBinding();
if (binding != null && (binding.getActivity() != this.host.getActivity())) {
//这里调用到插件的链路为FlutterEnginePluginRegistry.attachToActivity --> BoostRegistrarAggregate.onAttachedToActivity
// --> BoostRegistrar.onAttachedToActivity 中会将相关Registrar监听器重新添加至Binding中
flutterEngine.getActivityControlSurface().attachToActivity(
host.getActivity(),
host.getLifecycle()
);
}
}
5.8.ContainerRecord.onAppear
@Override
public void onAppear() {
···
mState = STATE_APPEAR;
通知ContainerManager push该页面
mManager.pushRecord(this);
//调用MethodChannelProxy.appear,通知Dart层Push当前页面至顶层
mProxy.appear();
//将FlutterView与Engine绑定, 触发渲染
mContainer.getBoostFlutterView().onAttach();
}
5.9.MethodChannelProxy.appear
private void appear() {
invokeChannelUnsafe("didShowPageContainer",
mContainer.getContainerUrl(),
mContainer.getContainerUrlParams(),
mUniqueId
);
mState = STATE_APPEAR;
}
5.10 XFlutterView.attachToFlutterEngine
public void attachToFlutterEngine(
@NonNull FlutterEngine flutterEngine
) {
···
this.flutterEngine = flutterEngine;
// Instruct our FlutterRenderer that we are now its designated RenderSurface.
FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
···
//从FlutterEngine中得到Render后,绑定在RenderSurface
//Surface可指定2种类型 surface、texture, 默认为前者
//最终会调用到RenderSurface.attachToRenderer
flutterRenderer.attachToRenderSurface(renderSurface);
// Initialize various components that know how to process Android View I/O
// in a way that Flutter understands.
···
}
5.11 RenderSurface.attachToRenderer
public interface RenderSurface {
/**
* Invoked by the owner of this {@code RenderSurface} when it wants to begin rendering
* a Flutter UI to this {@code RenderSurface}.
*
* The details of how rendering is handled is an implementation detail.
*/
void attachToRenderer(@NonNull FlutterRenderer renderer);
}
通过attachToRenderer可知,绑定好后,即可开始渲染。
5.12 ContainerCoordinator._onMethodCall
Future<dynamic> _onMethodCall(MethodCall call) {
switch (call.method) {
···
case "didShowPageContainer":
{
String pageName = call.arguments["pageName"];
Map params = call.arguments["params"];
String uniqueId = call.arguments["uniqueId"];
//将当前页面展示
nativeContainerDidShow(pageName, params, uniqueId);
}
break;
Native层的Channel信息最终会被Dart层的BoostChannel所接收,转发至coordinator._onMethodCall处理
5.13 ContainerCoordinator.nativeContainerDidShow
bool nativeContainerDidShow(String name, Map params, String pageId) {
//展示Container
FlutterBoost.containerManager
?.showContainer(_createContainerSettings(name, params, pageId));
//在Android上对无障碍辅助模式的兼容
···
//生命周期同步
performContainerLifeCycle(_createContainerSettings(name, params, pageId),
ContainerLifeCycle.Appear);
Logger.log(
'native containner did show,\nmanager dump:\n${FlutterBoost.containerManager?.dump()}');
return true;
}
5.14 BoostContainerManager.showContainer
//If container exists bring it to front else
//create a container.
void showContainer(BoostContainerSettings settings) {
if (settings.uniqueId == _onstage.settings.uniqueId) {
_onShownContainerChanged(null, settings.uniqueId);
return;
}
final int index = _offstage.indexWhere((BoostContainer container) =>
container.settings.uniqueId == settings.uniqueId);
if (index > -1) {
_offstage.add(_onstage);
_onstage = _offstage.removeAt(index);
setState(() {});
for (BoostContainerObserver observer in FlutterBoost
.singleton.observersHolder
.observersOf<BoostContainerObserver>()) {
observer(ContainerOperation.Onstage, _onstage.settings);
}
Logger.log('ContainerObserver#2 didOnstage');
} else {
pushContainer(settings);
}
}
5.15 BoostContainerManager.pushContainer
void pushContainer(BoostContainerSettings settings) {
assert(settings.uniqueId != _onstage.settings.uniqueId);
assert(_offstage.every((BoostContainer container) =>
container.settings.uniqueId != settings.uniqueId));
//切换stage,即Container
_offstage.add(_onstage);
_onstage = BoostContainer.obtain(widget.initNavigator, settings);
触发渲染
setState(() {});
for (BoostContainerObserver observer in FlutterBoost
.singleton.observersHolder
.observersOf<BoostContainerObserver>()) {
observer(ContainerOperation.Push, _onstage.settings);
}
Logger.log('ContainerObserver#2 didPush');
}
5.16 BoostContainerManager.setState
@override
void setState(VoidCallback fn) {
if (SchedulerBinding.instance.schedulerPhase ==
SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
_refreshOverlayEntries();
});
} else {
_refreshOverlayEntries();
}
fn();
//return super.setState(fn);
}
5.17 BoostContainerManager._refreshOverlayEntries
void _refreshOverlayEntries() {
//获取全局OverlayState对象,这是非常关键的对象,管理整个Widget对象显示,详见Overlay解释
final OverlayState overlayState = _overlayKey.currentState;
if (overlayState == null) {
return;
}
if (_leastEntries != null && _leastEntries.isNotEmpty) {
for (_ContainerOverlayEntry entry in _leastEntries) {
entry.remove();
}
}
final List<BoostContainer> containers = <BoostContainer>[];
containers.addAll(_offstage);
assert(_onstage != null, 'Should have a least one BoostContainer');
//将_onstage插入到队列最末端
containers.add(_onstage);
//遍历生成Entry
_leastEntries = containers
.map<_ContainerOverlayEntry>(
(BoostContainer container) => _ContainerOverlayEntry(container))
.toList(growable: false);
//插入所有Overlay Entry,即可让当前BoostContainer展示在最顶部
overlayState.insertAll(_leastEntries);
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
final String now = _onstage.settings.uniqueId;
if (_lastShownContainer != now) {
final String old = _lastShownContainer;
_lastShownContainer = now;
_onShownContainerChanged(old, now);
}
//更新焦点
updateFocuse();
});
}
5.18 Overlay
/// A [Stack] of entries that can be managed independently.
///
/// Overlays let independent child widgets "float" visual elements on top of
/// other widgets by inserting them into the overlay's [Stack]. The overlay lets
/// each of these widgets manage their participation in the overlay using
/// [OverlayEntry] objects.
///
/// Although you can create an [Overlay] directly, it's most common to use the
/// overlay created by the [Navigator] in a [WidgetsApp] or a [MaterialApp]. The
/// navigator uses its overlay to manage the visual appearance of its routes.
///
/// See also:
///
/// * [OverlayEntry].
/// * [OverlayState].
/// * [WidgetsApp].
/// * [MaterialApp].
class Overlay extends StatefulWidget {
}
由注释可知,正是Overlay对象管理了boost中页面widget的展示,Overlay可以独立的管理一个栈的widget, 通过overlayState.insertAll(_leastEntries)插入一系列的widget, 使栈的最后一个Entry(Widget)浮在前面所有Widget的上方,如是一个全屏的Widget,即是我们所说的页面展示。
至此,一次完整的Native打开Flutter页面的核心逻辑已浮出水面。
6.总结
Flutter_boost是一套不错的混合栈方案,在Flutter业界得到广泛应用。随着官方FlutterSDK的更新而不断进化,不同版本之间也存在不少技术差异,由于官方对混合栈的支持不是很完善,所以混合栈方案中总有这样或那样的“坑”,需要开发者对方案知根知底才能顺滑使用。回到前面提的几个问题:
- Boost是如何实现Engine复用的。
由于Boost在Native层通过单例提供Engine对象,自然Engine是复用的,在多个flutter页面切换时,与Engine相绑定的surface将切换至当前Activity.XFlutterView.surface渲染,Native将状态同步至Dart,由Dart层的ContainerManager调度OverlayEntry顺序,达到Push/Pop页面的效果。
- Boost是如何在Engine复用的情况下管理多页面插件状态同步。
如上述源码分析中的描述,Boost通过保持Engine的单例来保持插件的单例,Engine与PluginRegistry、BoostRegistrarAggregate、Registrar三者紧密协作,通过层层转发回调,在每一次attach/detach 重新注册/注销监听器来响应多页面的生命周期状态,将状态同步至各插件。
- Boost是如何打开Flutter/Native页面的。
如上述源码所述,Boost无论是Dart层还是Native层打开Flutter页面最终都会转发到Native的,当从Dart层打开Flutter页面时,通过BoostChannel将参数传递至Native,Native通过打开BoostFlutterActivity(通常)打开Flutter页面,在onCreate中装载FlutterView, 在onResume中通知Dart层渲染、将FlutterView与Engine绑定并开始渲染,getContainerUrl方法告知Dart层页面的路由名称,Dart层通过Overlay的管理方式将需要展示的Widget“浮”在所有页面之上。
- Boost还为我们躺了哪些不为人知的坑
键盘、无障碍、状态栏、白屏