本文将以 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 架构图入手
[图片上传失败...(image-8a77ce-1571282540292)]
从图中可以看到 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 包相关内容,后面会分析。
在 Android 中使用 Flutter 是这样的
如上图所示,图中罗列了一些类,这里讲解一下
- 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
https://github.com/alibaba/flutter_boost/releases/tag/0.0.420
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
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/3038039Flutter 新锐专家之路:混合开发片
https://juejin.im/post/5b764acb51882532dc1812b1微店的 Flutter 混合栈管理技术实践
https://juejin.im/post/5c419c07f265da616f703aa1Flutter 实现原理及在马蜂窝的跨平台开发事件
https://juejin.im/post/5d37b328518825453b6057b2
网友评论