阅读 2162

Flutter 混合栈复用原理

本文将以 Android 视角,来扒一扒 Flutter 混合栈的前世今生。其实也就是从 1.0 正式发布到现在 1.9 版本的一些变更。

本文将会从以下几个方面来分析:

  • 什么是 Flutter 混合栈
  • 为什么会产生问题
  • 处理混合栈的相关框架
  • 官方的处理方案

一、什么是混合栈

一个新的技术的兴起,必然是一步一步向前的,Flutter 在成熟的 Android、iOS 的大环境下的生存,必要与 native 融合。

一些成熟的 APP ,如果想使用 Flutter 技术,必然不会完全使用 Flutter 重写,那样的成本太高,所以 Native + Flutter 的项目就出现了,Native 页面与 Flutter 页面共存,甚至交替呈现在用户手机上。

那什么是混合栈呢?

相信你已经有了答案,下面我们用 Android 的视角来重新审视一下混合栈。

我们使用 flutter boost 框架提供的 demo 来看一下混合栈的效果(debug包)

二、为什么会产生问题

知道了混合栈问题,接下来我们要考虑为什么会出现这样的问题。

目标是什么

现状是一个 Activity 中存在多个 Flutter Page,我们想要的是 Flutter Page 与 Android Activity 可以一一对应,这样便可以简单的将两个页面栈合并成一个栈,并且在性能上不会产生影响,这就是我们处理混合栈的目标。

如上图混合栈所示,Android 中以 FlutterView 承载 Flutter 的展示,默认情况下不同 FlutterView 创建了不同的 engine。如果一个 Flutter Page 对应一个 Activity, 这就导致了资源的多次重复创建和内存的不共享。

如果极端情况下,Native => Flutter => Native => … => Native => Flutter 会是什么情况呢?后果不堪设想,当然,可以从业务上避免这样的问题,但是作为框架的制定者,必须要考虑到这样的问题。

接下来我们看如何解决问题。需要从原理入手,需要阅读源码

Flutter 架构图

从最经典的 Flutter 架构图入手

Flutter System Overview

从图中可以看到 3 层架构,每层提供了不同的能力

  • Framework:这一层提供了Flutter 的常用控件,也提供了用于绘制的一些准备工作,详见Flutter 从加载到显示
  • Engine:这一层提供了Flutter的2D图形渲染库Skia和用于垃圾收集的面向对象语言的 Dart VM,并将它们托管在Shell中。这里也提供了 Platform Channels 等与 Native 交互的 API。可以参考这篇文章The Engine architecture
  • Embedder:这一层提供了不同平台的嵌入 API 如 Andorid、iOS。使得 Flutter 可以运行在不同的嵌入式平台。这里有一片关于自定义 Embedder 的文章Custom Flutter Engine Embedders

Flutter 从加载到显示
mp.weixin.qq.com/s/ncViI0KGi…

The Engine architecture
github.com/flutter/flu…

Custom Flutter Engine Embedders
github.com/flutter/flu…

在 Android 中的 Flutter

我们看一下创建 Flutter 项目中自动生成的 Android 工程,内部使用的是 io.flutter.app 包内的 FlutterActivity,暂且不讨论 io.flutter.embedding.android 包相关内容,后面会分析。

在 Android 中使用 Flutter 是这样的

io.flutter.app.Activity内容

如上图所示,图中罗列了一些类,这里讲解一下

  • FlutterView:Android 中用于展示 Flutter 页面的控件,一个 FlutterView 中可以展示多个 Flutter Widget,官方的注释是:"An Android View containing a Flutter app"。内部包含了 FlutterNativeView。
  • FlutterNativeView:每个 FlutterView 中包含一个FlutterNativeView,该类的主要作用是 Android 与 Flutter 之间的通信,保持生命周期与 Activity 及 FlutterView 同步。内部包含了 DartExecutor。
  • DartExecutor:根据名称我们可以了解,这个类就是 Dart VM 相关处理 Java 与 C/C++ 的调用。官方的注释是:"Configures, bootstraps, and starts executing Dart code"

其实 Flutter 的运行机制就是这样的,Flutter 由 FlutterView 呈现。每一个 FlutterView,会对应的创建一个 FlutterNativeView,创建一个 Dart VM。而不同 FlutterView 内部的 Dart 代码内存无法共享。

源码阅读

建议读者阅读一下代码

io.flutter.app.FlutterActivit
Io.flutter.app.FlutterActivityDelegate
io.flutter.view.FlutterMain
io.flutter.view.FlutterView
io.flutter.view.FlutterNativeView
io.flutter.embedding.engine.dart.DartExecutor
io.flutter.embedding.engine.FlutterJNI

我们能做什么

通过上面的介绍,应该依然了解 Flutter 在 Android 上的运行机制,如果阅读了源码应该有更深的印象。在这样的运行机制中,我们能做什么呢?其实 Flutter Boost 框架给了我们解决的思路,但是这里我还是希望读者能自己来想想,如果是你自己来实现,该怎么做呢?

三、混合栈处理框架

来看看社区为我们提供的方案吧。

网上的文章很多,在文章最后提供一些链接,有兴趣的读者可以都看一下。这里仅以 Flutter Boost 为例,来分析一下。

Flutter Boost 的处理方案可以分成两个版本,使用了两种方案,可以作为混合栈方案的两种代表思路。

Flutter Boost 0.0.4+版本

alibaba/flutter_boost 0.0.420
github.com/alibaba/flu…

FlutterView 复用方案

框架中从 FlutterActivityDelegate#onCreate 方法入手,重写创建 FlutterView 的流程,复用 FlutterView 来实现。

我们先来看一下这个版本的接入方式,在 Application 中初始化

    FlutterBoostPlugin.init(new IPlatform() {
        ...
          
        /**
         * 获取应用入口的Activity,这个Activity在应用交互期间应该是一直在栈底的
         * 提供给框架内部,后续创建FlutterView用
         */
        @Override
        public Activity getMainActivity() {
            if (MainActivity.sRef != null) {
                return MainActivity.sRef.get();
            }
            return null;
        }
        ...
    });
复制代码

我们来看一下 FlutterActivityDelegate#onCreate 是如何处理的。

# FlutterActivityDelegate.java    
		@Override
    public void onCreate(Bundle savedInstanceState) {
				...
				// 获取 flutterView
        flutterView = viewFactory.createFlutterView(activity);
        // 为空则创建,否则直接使用
        if (flutterView == null) {
        		// 创建 FlutterView 的同时,创建 FlutterNative、DartExecutor
            FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
            flutterView = new FlutterView(activity, null, nativeView);
            flutterView.setLayoutParams(matchParent);
            activity.setContentView(flutterView);
            launchView = createLaunchView();
            if (launchView != null) {
                addLaunchView();
            }
        }
      ...
    }

复制代码

createFlutterView 的实现是在 FlutterActivity 中的。

# FlutterActivity.java
    @Override
    public FlutterView createFlutterView(Context context) {
        return null;
    }
复制代码

而 Flutter Boost 框架重写了 createFlutterView 方法

# BoostFlutterActivity.java
		public FlutterView createFlutterView(Context context) {
        return FlutterBoostPlugin.viewProvider().createFlutterView(this);
    }
复制代码

真正返回的是这里构造的

# FlutterViewProvider.java
		@Override
    public BoostFlutterView createFlutterView(IFlutterViewContainer container) {
    		// 在 Application 中提供的 mPlatform,将缓存的 Activity 提供给这里
        Activity activity = mPlatform.getMainActivity();

        if(activity == null) {
            Debuger.log("create Flutter View not with MainActivity");
            activity = container.getActivity();
        }
        // 如果为 null 则创建然后缓存,有值则直接使用
        if (mFlutterView == null) {
        		// BoostFlutterView 继承自 FlutterView
            mFlutterView = new BoostFlutterView(activity, null, createFlutterNativeView(container));
        }
        return mFlutterView;
    }
复制代码

这样就复用了 FlutterView。

复用 FlutterView 需要在 Activity 切换的时候进行 view 的 attach、detach,该版本使用了截图方案。

Flutter Boost 0.1.5+版本

FlutterEngine 复用

从接入方式来看,在 Application 中初始化,提供了一个回调方法,提供 BoostFlutterEngine 即是 FlutterEngine 实例。

    FlutterBoost.init(new Platform() {
      	...
        @Override
        public IFlutterEngineProvider engineProvider() {
            return new BoostEngineProvider() {
                @Override
                public BoostFlutterEngine createEngine(Context context) {
                    return new BoostFlutterEngine(context, new DartExecutor.DartEntrypoint(
                            context.getResources().getAssets(),
                            FlutterMain.findAppBundlePath(context),
                            "main"), "/");
                }
            };
        }
      	...
    });
复制代码

在看 BoostFlutterActivity 实现

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
				...
        mFlutterEngine = createFlutterEngine();
        mFlutterView = createFlutterView(mFlutterEngine);
        setContentView(mFlutterView);
				...
    }
		...
    protected BoostFlutterEngine createFlutterEngine(){
        return FlutterBoost.singleton().engineProvider().provideEngine(this);
    }
复制代码

真正返回的是这里构造的

 # com.idlefish.flutterboost.BoostEngineProvider
 		@Override
    public BoostFlutterEngine provideEngine(Context context) {
        Utils.assertCallOnMainThread();

        if (mEngine == null) {
            FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
            FlutterMain.ensureInitializationComplete(
                    context.getApplicationContext(), flutterShellArgs.toArray());
						// 这里调用的方法就是初始化时重写内容
            mEngine = createEngine(context.getApplicationContext());

            final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
            if(stateListener != null) {
                stateListener.onEngineCreated(mEngine);
            }
        }
        return mEngine;
    }
复制代码

该版本的处理方式与 io.flutter.embedding 包中处理方式基本相同,使用了 flutter.jar 中的 FlutterSurfaceView 和 FlutterTextureView 来最终的展示 Flutter 界面。接下来我们看看 Flutter 官方为我们提供的方式。

官方提供的方式

Experimental: Adding Flutter to Android
github.com/flutter/flu…

通过文档我们就可以知道,该方式和 Flutter 项目自动生成的 Android 工程不同,使用的大多为 io.flutter.embedding 包中的内容,并且提供了使用缓存的 FlutterEngine 的方式。使用了 FlutterEngineCache 类进行对 FlutterEngine 的 key-value 缓存。

在 flutter.jar 中可以看到,共存了两个 FlutterActivity、FlutterView 等。

FlutterActivity

io.flutter.app.FlutterActivity
io.flutter.embedding.FlutterActivity

FlutterView

io.flutter.view.FlutterView
io.flutter.embedding.FlutterView

这里简单介绍一下

# io.flutter.embedding.FlutterActivity.java
	@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    delegate = new FlutterActivityAndFragmentDelegate(this);
    // 创建 Flutter
    // 提供 FlutterEngine 并绑定到 Activity、创建并配置 PlatformPlugin、
    delegate.onAttach(this);
    ...
    // 创建 Flutter 的 View 并绑定到 Activity
    setContentView(createFlutterView());
    ...
  }
	@NonNull
  private View createFlutterView() {
    return delegate.onCreateView(
        null /* inflater */,
        null /* container */,
        null /* savedInstanceState */);
  }
复制代码

加载到 Activity 上的 View 是如何创建的

# io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.java  
	@NonNull
  View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
		...
		// 创建了一个 FlutterView
    flutterView = new FlutterView(host.getActivity(), host.getRenderMode(), host.getTransparencyMode());
		// 创建了一个 FlutterSplashView,包裹了一个 FlutterView 的View
    flutterSplashView = new FlutterSplashView(host.getContext());
		...
		// 在 FlutterView 展示第一帧之前,先展示提供的 splashScreen
    flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());

    return flutterSplashView;
  }
复制代码

看一下 FlutterView 是如何实现的

  private void init() {
		// 根据 renderMode 模式来选择使用 SurfaceView/TextureView,解决了 SurfaceView 对动画支持差的诟病
    switch (renderMode) {
      case surface:
        FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == TransparencyMode.transparent);
        renderSurface = flutterSurfaceView;
        addView(flutterSurfaceView);
        break;
      case texture:
        FlutterTextureView flutterTextureView = new FlutterTextureView(getContext());
        renderSurface = flutterTextureView;
        addView(flutterTextureView);
        break;
    }
    setFocusable(true);
    setFocusableInTouchMode(true);
  }
复制代码

大致的流程就是这样的。

小结

Flutter 的混合栈处理在这一年多内,也有了很大的发展,从整个发展历程来梳理,可能对混合栈的方案有更深的理解。最后附上整理的混合栈相关文章

Flutter 混合栈处理相关文章:

码上用它开始 Flutter 混合开发——FlutterBoos
my.oschina.net/yunqi/blog/…

Flutter 新锐专家之路:混合开发片
juejin.im/post/5b764a…

微店的 Flutter 混合栈管理技术实践
juejin.im/post/5c419c…

Flutter 实现原理及在马蜂窝的跨平台开发事件
juejin.im/post/5d37b3…

让 Flutter 真正支持 View级别的混合开发
mp.weixin.qq.com/s?__biz=MzI…

关注下面的标签,发现更多相似文章
评论