Android
之前我们了解了native和flutter的几种交互,有了这个知识就很容易理解flutter-boost原理。那么它是怎么实现的?
flutter-boost自定义了一个Activity —— BoostFlutterActivity,使用的时候会通过NewEngineIntentBuilder创建一个Intent,它的build代码:
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函数:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... delegate = new FlutterActivityAndFragmentDelegate(this); delegate.onAttach(this); ... setContentView(createFlutterView()); ... } 复制代码
通过createFlutterView创建了一个view,并setContentView。在createFlutterView中:
protected View createFlutterView() { return delegate.onCreateView(null,null,null); } 复制代码
delegate是FlutterActivityAndFragmentDelegate对象,它的onCreateView:
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:
public void onResume() { mSyncer.onAppear(); ... } 复制代码
可以看到一开始就调用了mSyncer的onAppear函数,而mSyncer前面已经知道是ContainerRecord,那么它的onAppear:
@Override public void onAppear() { ... mState = STATE_APPEAR; mManager.pushRecord(this); mProxy.appear(); mContainer.getBoostFlutterView().onAttach(); } 复制代码
重点是mProxy.appear(),这个mProxy是内部类MethodChannelProxy,它的appear:
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函数是怎么执行的:
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层层调用最终执行:
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类中:
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):
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则是前级页面,而布局时其实全部堆叠的:
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发现下面的代码:
- (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。