Flutter: analysis of the principle of flutter boost


We learned before How Flutter interacts with Native (Android) With this knowledge, it is easy to understand the principle of flutter boost. So how does it work?

Flutter boost defines an Activity - BoostFlutterActivity. When it is used, an Intent will be created through newengineintensbuilder. Its build code is as follows:

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);

It can be seen that it supports not only route, but also parameter passing params, which is exactly what we need. How is this implemented?

First, look at its onCreate function:

protected void onCreate(@Nullable Bundle savedInstanceState) {
    delegate = new FlutterActivityAndFragmentDelegate(this);

Create a view through createFlutterView and setContentView. In createFlutterView:

protected View createFlutterView() {
    return delegate.onCreateView(null,null,null);

delegate is the FlutterActivityAndFragmentDelegate object. Its onCreateView:

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this);
    flutterView = new XFlutterView(host.getActivity(), FlutterBoost.instance().platform().renderMode(), host.getTransparencyMode());
    return flutterSplashView;

The first line passes containermanager () generateSyncer creates an mSyncer. containerManager() gets a FlutterViewContainerManager object. Its generateSyncer:

public IOperateSyncer generateSyncer(IFlutterViewContainer container) {
    ContainerRecord record = new ContainerRecord(this, container);
    mRefs.add(new ContainerRef(record.uniqueId(),container));
    return record;

Here you can see that mSyncer is actually a ContainerRecord. This is very important. It will be used to implement route later.

However, we haven't seen how URLs and params are used yet. Looking back at the onResume function of BoostFlutterActivity, all lifecycle functions of BoostFlutterActivity will call the corresponding function of delegate, so we can directly look at its onResume:

public void onResume() {

You can see that the onAppear function of mSyncer is called at the beginning, and mSyncer already knows that it is ContainerRecord, so its onAppear:

public void onAppear() {
    mState = STATE_APPEAR;

The point is mProxy Appearance (), this mProxy is an internal class MethodChannelProxy. Its appear ance:

private void appear() {
    mState = STATE_APPEAR;

Here we see the mContainer Getcontainerurl() and mContainer Getcontainerurlparams (), this mContainer is the first flutterboost Instance() Containermanager() Generatesynchronizer (this); The passed in this is FlutterActivityAndFragmentDelegate. Its two functions (of the Host interface) call the functions corresponding to BoostFlutterActivity (which implements the Host):

public String getContainerUrl() {
    if (getIntent().hasExtra(EXTRA_URL)) {
        return getIntent().getStringExtra(EXTRA_URL);
    return "";

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;

This is the url and params we passed in. So next, let's look at how the invokeChannelUnsafe function is executed:

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);

The FlutterBoost channel() returns the FlutterBoostPlugin. Its invokeMethodUnsafe layer by layer calls are finally executed:

public void invokeMethod(final String name, Serializable args, MethodChannel.Result result) {
    mMethodChannel.invokeMethod(name, args, result);

mMethodChannel is a MethodChannel type, which we explained earlier. It is one of the interaction methods between native and fluent. So finally, the flutter's didShowPageContainer is executed, and the url and params are passed in as parameters. So how does flutter handle it? After searching, it is found that in the ContainerCoordinator class:

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);
    return Future<dynamic>(() {});
  bool nativeContainerDidShow(
  String name,
  Map<String, dynamic> params,
  String pageId,
) {
      ?.showContainer(_createContainerSettings(name, params, pageId));

  // Compatible to accessibility mode on Android.
  if (Platform.isAndroid) {
    try {
      final SemanticsOwner owner =
      final SemanticsNode root = owner?.rootSemanticsNode;
    } catch (e) {
      assert(false, e.toString());

The corresponding execution method of didShowPageContainer is nativeContainerDidShow. The first line of code executes containerManager showContainer, containerManager is a bootcontainermanager. Its showContainer:

void showContainer(BoostContainerSettings settings) {
    if (settings.uniqueId == _onstage.settings.uniqueId) {
      _onShownContainerChanged(null, settings.uniqueId);

    final int index = _offstage.indexWhere((BoostContainer container) =>
        container.settings.uniqueId == settings.uniqueId);
    if (index > -1) {
      _onstage = _offstage.removeAt(index);

      setState(() {});

      for (final BoostContainerObserver observer in FlutterBoost
          .observersOf<BoostContainerObserver>()) {
        observer(ContainerOperation.Onstage, _onstage.settings);
      Logger.log('ContainerObserver#2 didOnstage');
    } else {

If the page does not exist before, execute pushContainer(settings):

 void pushContainer(BoostContainerSettings settings) {
    assert(settings.uniqueId != _onstage.settings.uniqueId);
    assert(_offstage.every((BoostContainer container) =>
        container.settings.uniqueId != settings.uniqueId));

    _onstage = BoostContainer.obtain(widget.initNavigator, settings);

    setState(() {});

    for (final BoostContainerObserver observer in FlutterBoost
        .observersOf<BoostContainerObserver>()) {
      observer(ContainerOperation.Push, _onstage.settings);
    Logger.log('ContainerObserver#2 didPush');

Here, the bootcontainer Obtain to create a widget and assign it to_ onstage, the source code of this function:

 factory BoostContainer.obtain(
      Navigator navigator,
      BoostContainerSettings settings,
      ) =>
        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>[
        onUnknownRoute: navigator.onUnknownRoute,

You can see that this is to create widget s through RouteFactory, which we are familiar with. In this way, the router is implemented and the parameters are transferred.

Observe the BoostContainerManager (container_mannager.dart) to find that_ onstage is the currently displayed page, and_ The offstage page is the previous level page, and the layout is actually all stacked:

final List<BoostContainer> containers = <BoostContainer>[];

assert(_onstage != null, 'Should have a least one BoostContainer');

So by changing_ onstage and_ offstage is used to switch pages. Therefore, the essence of flutter boost is to use a page to switch different contents, and all pages share a flutter engine (all pages enter a page, so initialRoute is fixed). In this way, except for the first time, opening it again will be very fast, and the startup speed will be accelerated.

In this way, we have a general understanding of the startup principle of flutter boost. Of course, flutter boost has many functions, but after understanding the startup principle, we can try to implement a simple framework by ourselves.


ios is actually similar to android. ios interacts with fluent in the same three ways.

In ios, we use the FlutterViewController to display the flutter page. You can refer to Flutter mixed development: introducing flutter into existing iOS projects , so this is equivalent to the FlutterActivity in android. At the same time, we know that the function didShowPageContainer is used to display the page. Because this function is defined in flutter, it should also be called at the ios level. This is relatively simple.

We can find flbflutterviewcontainer M (shutter_boost/ios/classes/container/). Search this file for didShowPageContainer and find the following code:

- (void)viewDidAppear:(BOOL)animated
    [FLUTTER_APP addUniqueViewController:self];
    //Ensure flutter view is attached.
    [self attatchFlutterEngine];
    [BoostMessageChannel didShowPageContainer:^(NSNumber *result) {}
    //NOTES: be sure to update after show, otherwise it will flash; Or cause sideslip. When returning, the previous page will be the same as the top page
    [self surfaceUpdated:YES];
    [super viewDidAppear:animated];
FLBFlutterViewContainer inherit ios of UIViewController´╝îsee FLBFlutterViewContainer.h
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
#import "FLBFlutterContainer.h"

@interface FLBFlutterViewContainer  : FlutterViewController<FLBFlutterContainer>
@property (nonatomic,copy,readwrite) NSString *name;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (void)surfaceUpdated:(BOOL)appeared;

The viewDidAppear function is inherited from him. Its life cycle is similar to that of resume in android. Therefore, execute the didShowPageContainer at this stage and complete the widget switching in the shutter.

So we can see that the principle of ios is basically similar to that of android. It also rewrites the class that carries the flutter page, and then notifies flutter interactively. In flutter, it is the way of switching widget s on a single page. In this way, a flutter engine can be used to initialize and warm up the engine in advance (running in ios) to improve the loading efficiency.

Through the above principles, we can develop a simple flutter startup plugin by ourselves. We will implement it in the next article.

Posted by infratl on Tue, 31 May 2022 10:29:37 +0530