Android
之前我们了解了Flutter如何与Native(Android)进行交互,有了这个知识就很容易理解flutter-boost原理。那么它是怎么实现的?
flutter-boost自定义了一个Activity —— BoostFlutterActivity,使用的时候会通过NewEngineIntentBuilder创建一个Intent,它的build代码:
代码语言:javascript复制public Intent build(@NonNull Context context) {
...
return new Intent(context, activityClass)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
.putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false)
.putExtra(EXTRA_URL, url)
.putExtra(EXTRA_PARAMS, serializableMap);
}
可以看到,它不仅仅支持route,同时还支持传参params,这正是我们需要的,那么这是怎么实现的?
首先看它的onCreate函数:
代码语言:javascript复制@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);
...
setContentView(createFlutterView());
...
}
通过createFlutterView创建了一个view,并setContentView。在createFlutterView中:
代码语言:javascript复制protected View createFlutterView() {
return delegate.onCreateView(null,null,null);
}
delegate是FlutterActivityAndFragmentDelegate对象,它的onCreateView:
代码语言:javascript复制public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this);
ensureAlive();
flutterView = new XFlutterView(host.getActivity(), FlutterBoost.instance().platform().renderMode(), host.getTransparencyMode());
...
mSyncer.onCreate();
return flutterSplashView;
}
第一行通过containerManager().generateSyncer
创建了一个mSyncer,containerManager()得到的是一个FlutterViewContainerManager对象,它的generateSyncer:
@Override
public IOperateSyncer generateSyncer(IFlutterViewContainer container) {
...
ContainerRecord record = new ContainerRecord(this, container);
...
mRefs.add(new ContainerRef(record.uniqueId(),container));
return record;
}
这里可以看到mSyncer实际上是一个ContainerRecord。这个很重要,后面会通过它实现route。
但是目前还没看到url和params是如何被使用的,那么回头看BoostFlutterActivity的onResume函数,BoostFlutterActivity所有生命周期函数都会调用delegate对应的函数,所以直接看它的onResume:
代码语言:javascript复制public void onResume() {
mSyncer.onAppear();
...
}
可以看到一开始就调用了mSyncer的onAppear函数,而mSyncer前面已经知道是ContainerRecord,那么它的onAppear:
代码语言:javascript复制@Override
public void onAppear() {
...
mState = STATE_APPEAR;
mManager.pushRecord(this);
mProxy.appear();
mContainer.getBoostFlutterView().onAttach();
}
重点是mProxy.appear(),这个mProxy是内部类MethodChannelProxy,它的appear:
代码语言:javascript复制private void appear() {
invokeChannelUnsafe("didShowPageContainer",
mContainer.getContainerUrl(),
mContainer.getContainerUrlParams(),
mUniqueId
);
mState = STATE_APPEAR;
}
这里我们看到了mContainer.getContainerUrl()
和mContainer.getContainerUrlParams()
,这个mContainer就是最开始FlutterBoost.instance().containerManager().generateSyncer(this);
传入的this,就是FlutterActivityAndFragmentDelegate,它的这两个函数(Host接口的)则调用了BoostFlutterActivity(实现了Host)对应的函数:
@Override
public String getContainerUrl() {
if (getIntent().hasExtra(EXTRA_URL)) {
return getIntent().getStringExtra(EXTRA_URL);
}
return "";
}
@Override
public Map getContainerUrlParams() {
if (getIntent().hasExtra(EXTRA_PARAMS)) {
SerializableMap serializableMap = (SerializableMap) getIntent().getSerializableExtra(EXTRA_PARAMS);
return serializableMap.getMap();
}
Map<String, String> params = new HashMap<>();
return params;
}
这就是我们传入的url和params。所以接下来就来看invokeChannelUnsafe函数是怎么执行的:
代码语言:javascript复制public void invokeChannelUnsafe(String method, String url, Map params, String uniqueId) {
HashMap<String, Object> args = new HashMap<>();
args.put("pageName", url);
args.put("params", params);
args.put("uniqueId", uniqueId);
FlutterBoost.instance().channel().invokeMethodUnsafe(method, args);
}
FlutterBoost的channel()返回的是FlutterBoostPlugin,它的invokeMethodUnsafe层层调用最终执行:
代码语言:javascript复制public void invokeMethod(final String name, Serializable args, MethodChannel.Result result) {
...
mMethodChannel.invokeMethod(name, args, result);
}
mMethodChannel是MethodChannel类型,这个我们之前重点讲解了,是native和flutter的交互方式之一。所以最终就是执行了flutter的didShowPageContainer,并将url和params作为参数传入。那么flutter中如何处理的?经过搜索发现是在ContainerCoordinator类中:
代码语言:javascript复制Future<dynamic> _onMethodCall(MethodCall call) {
final String pageName = call.arguments['pageName'] as String;
final Map<String, dynamic> params =
(call.arguments['params'] as Map<dynamic, dynamic>)
?.cast<String, dynamic>();
final String uniqueId = call.arguments['uniqueId'] as String;
switch (call.method) {
...
case 'didShowPageContainer':
nativeContainerDidShow(pageName, params, uniqueId);
break;
...
}
return Future<dynamic>(() {});
}
bool nativeContainerDidShow(
String name,
Map<String, dynamic> params,
String pageId,
) {
FlutterBoost.containerManager
?.showContainer(_createContainerSettings(name, params, pageId));
// Compatible to accessibility mode on Android.
if (Platform.isAndroid) {
try {
final SemanticsOwner owner =
WidgetsBinding.instance.pipelineOwner?.semanticsOwner;
final SemanticsNode root = owner?.rootSemanticsNode;
root?.detach();
root?.attach(owner);
} catch (e) {
assert(false, e.toString());
}
}
didShowPageContainer对应的执行方法是nativeContainerDidShow,它的第一行代码执行了containerManager.showContainer
,containerManager是BoostContainerManager,它的showContainer:
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 (final BoostContainerObserver observer in FlutterBoost
.singleton.observersHolder
.observersOf<BoostContainerObserver>()) {
observer(ContainerOperation.Onstage, _onstage.settings);
}
Logger.log('ContainerObserver#2 didOnstage');
} else {
pushContainer(settings);
}
}
如果该页面之前不存在,则执行pushContainer(settings):
代码语言:javascript复制 void pushContainer(BoostContainerSettings settings) {
assert(settings.uniqueId != _onstage.settings.uniqueId);
assert(_offstage.every((BoostContainer container) =>
container.settings.uniqueId != settings.uniqueId));
_offstage.add(_onstage);
_onstage = BoostContainer.obtain(widget.initNavigator, settings);
setState(() {});
for (final BoostContainerObserver observer in FlutterBoost
.singleton.observersHolder
.observersOf<BoostContainerObserver>()) {
observer(ContainerOperation.Push, _onstage.settings);
}
Logger.log('ContainerObserver#2 didPush');
}
这里通过BoostContainer.obtain
来创建一个widget并赋值给_onstage,这个函数源码:
factory BoostContainer.obtain(
Navigator navigator,
BoostContainerSettings settings,
) =>
BoostContainer(
key: GlobalKey<BoostContainerState>(),
settings: settings,
onGenerateRoute: (RouteSettings routeSettings) {
if (routeSettings.name == '/') {
return BoostPageRoute<dynamic>(
pageName: settings.name,
params: settings.params,
uniqueId: settings.uniqueId,
animated: false,
settings: RouteSettings(
name: settings.name,
arguments: routeSettings.arguments,
),
builder: settings.builder,
);
} else {
return navigator.onGenerateRoute(routeSettings);
}
},
observers: <NavigatorObserver>[
ContainerNavigatorObserver.bindContainerManager(),
HeroController(),
],
onUnknownRoute: navigator.onUnknownRoute,
);
可以看到这就是通过我们熟悉的RouteFactory来创建widget,这样就实现了router,同时也实现的传参。
观察BoostContainerManager(container_mannager.dart)可以发现,_onstage是当前展示的页面,而_offstage则是前级页面,而布局时其实全部堆叠的:
代码语言:javascript复制final List<BoostContainer> containers = <BoostContainer>[];
containers.addAll(_offstage);
assert(_onstage != null, 'Should have a least one BoostContainer');
containers.add(_onstage);
这样就通过改变_onstage和_offstage来实现页面的切换,所以flutter-boost本质上是用一个页面切换不同的内容,而所有页面都公用一个flutter engine(都是进一个页面,所以initialRoute固定),这样除了第一次打开,后面再次打开就会很快,实现的启动加速。
这样我们就大致的了解了flutter-boost的启动原理,当然flutter-boost还有很多功能,不过了解了这个启动原理,我们可以试着自己来实现一个简单的框架。
iOS
ios其实与android类似,ios与flutter的交互同样是上面三种方式。
在ios中我们通过FlutterViewController来展示flutter页面,可以参考Flutter混合开发:在已有iOS项目中引入Flutter,所以这个相当于android中的FlutterActivity,同时我们在上面知道最终是通过didShowPageContainer这个函数来展示页面,因为这个定义在flutter中,所以在ios层面应该也是调用这个函数,这样就比较简单了。
在flutter-boost的ios源码中我们可以找到FLBFlutterViewContainer.m(flutter_boost/ios/Classes/container/)这个文件,在这个文件中搜索didShowPageContainer发现下面的代码:
代码语言:javascript复制- (void)viewDidAppear:(BOOL)animated
{
[FLUTTER_APP addUniqueViewController:self];
//Ensure flutter view is attached.
[self attatchFlutterEngine];
[BoostMessageChannel didShowPageContainer:^(NSNumber *result) {}
pageName:_name
params:_params
uniqueId:self.uniqueIDString];
//NOTES:务必在show之后再update,否则有闪烁; 或导致侧滑返回时上一个页面会和top页面内容一样
[self surfaceUpdated:YES];
[super viewDidAppear:animated];
}
FLBFlutterViewContainer继承ios的UIViewController,见FLBFlutterViewContainer.h
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
#import "FLBFlutterContainer.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLBFlutterViewContainer : FlutterViewController<FLBFlutterContainer>
@property (nonatomic,copy,readwrite) NSString *name;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (void)surfaceUpdated:(BOOL)appeared;
@end
NS_ASSUME_NONNULL_END
viewDidAppear这个函数就是从他继承过来的,其生命周期与android中的resume类似,所以在这个阶段执行didShowPageContainer,在flutter中完成widget切换。
所以可以看到ios的原理与android基本类似,也是通过重写承载flutter页面的类,然后通过交互方式通知flutter,flutter中就是单页面切换widget的方式,这样就可以使用一个flutter engine,提前初始化并预热(ios中是运行)这个engine来提高加载效率。
通过上面的原理,我们可以自己开发一个简单的flutter启动plugin,下一篇我们就来实现它。