Flutter 混合栈复用原理

奇技指南

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

本文转载自奇舞移动技术

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

  • 什么是 Flutter 混合栈

  • 为什么会产生问题

  • 处理混合栈的相关框架

  • 官方的处理方案

什么是混合栈

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

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

那什么是混合栈呢?

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

640?wx_fmt=jpeg

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

640?wx_fmt=gif

为什么会产生问题

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

目标是什么

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

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

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

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

Flutter 架构图

从最经典的 Flutter 架构图入手

640?wx_fmt=jpeg

从图中可以看到 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 从加载到显示

    https://mp.weixin.qq.com/s/ncViI0KGikPUIZ7BlEHGOA

  • The Engine architecture

    https://github.com/flutter/flutter/wiki/The-Engine-architecture

  • Custom Flutter Engine Embedders

    https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders

在 Android 中的 Flutter

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

在 Android 中使用 Flutter 是这样的

640?wx_fmt=jpeg

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

  • 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/flutterboost 0.0.420

    https://github.com/alibaba/flutterboost/releases/tag/0.0.420

FlutterView 复用方案

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

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

FlutterBoostPlugin.init(newIPlatform() {	
        ...	

	
        /**	
         * 获取应用入口的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+版本

  • alibaba/flutterboost 0.1.50

    https://github.com/alibaba/flutter_boost/releases/tag/0.1.50

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

    https://github.com/flutter/flutter/wiki/Experimental:-Adding-Flutter-to-Android

通过文档我们就可以知道,该方式和 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

    https://my.oschina.net/yunqi/blog/3038039

  • Flutter 新锐专家之路:混合开发片                                                              https://juejin.im/post/5b764acb51882532dc1812b1

  • 微店的 Flutter 混合栈管理技术实践                                                           https://juejin.im/post/5c419c07f265da616f703aa1

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

  • 让 Flutter 真正支持 View级别的混合开发                                                  http://weixin.qq.com/r/zTo4PaHEQ2k-KOVeb29J

推荐阅读

 

最新活动

360技术嘉年华第八季——测试之美

精彩内容都在

360技术嘉年华第八季——测试之美

识别下方二维码或点击阅读原文,立即报名

640?wx_fmt=png

界世的你当不

只做你的肩膀

640?wx_fmt=jpeg
640?wx_fmt=jpeg

 360官方技术公众号 

技术干货|一手资讯|精彩活动

空·

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值