Flutter:浅析flutter-boost原理

2022-05-31 09:32:30 浏览数 (1)

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:

代码语言:javascript复制
@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)对应的函数:

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

代码语言:javascript复制
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,这个函数源码:

代码语言:javascript复制
 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,下一篇我们就来实现它。

0 人点赞