flutter_boost源码浅析

5,132 阅读7分钟

作者: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可知,绑定好后,即可开始渲染。


以下代码为Dart层

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还为我们躺了哪些不为人知的坑

键盘、无障碍、状态栏、白屏