Flutter 混合工程的结构,主要存在以下两种模式:
- 统一管理模式
- 三端分离模式
所谓统一管理模式,就是一个标准的 Flutter Application 工程,而其中 Flutter 的产物工程目录(ios 和 android )是可以进行原生混编的工程,如 React Native 进行混合开发那般,在工程项目中进行混合开发就好。但是这样的缺点是当原生项目业务庞大起来时,Flutter 工程对于原生工程的耦合就会非常严重,当工程进行升级时会比较麻烦。因此这种混合模式只适用于 Flutter 业务主导、原生功能为辅的项目。但早期 Google 未支持 Flutter Module 时,进行混合开发也只存在这一种模式。
后来 Google 对混合开发有了更好的支持,除了 Flutter Application,还支持 Flutter Module。所谓 Flutter Module,恰如其名,就是支持以模块化的方式将 Flutter 引入原生工程中,它的产物就是 iOS 下的 Framework 或 Pods、Android 下的 AAR,原生工程就像引入其他第三方 SDK 那样,使用 Maven 和 Cocoapods 引入 Flutter Module 即可。从而实现真正意义上的三端分离的开发模式。
一 混合栈
混合导航栈主要需要解决以下四种场景下的问题:
- Native 跳转 Flutter
- Flutter 跳转 Flutter
- Flutter 跳转 Native
- Native 跳转 Native
1.Native 跳转 Flutter
Native -> Flutter,这种情况比较简单,Flutter Engine 已经为我们提供了现成的 Plugin,即 iOS 下的 FlutterViewController 与 Android 下的 FlutterView(自行包装一下可以实现 FlutterActivity),所以这种场景我们直接使用启动了的 Flutter Engine 来初始化 Flutter 容器,为其设置初始路由页面之后,就可以以原生的方式跳转至 Flutter 页面了。
// Existing code omitted.
// 省略已经存在的代码
- (void)showFlutter {
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
2.Flutter 跳转 Flutter
Flutter -> Flutter,业内存在两种方案,分别是:
- 使用 Flutter 本身的 Navigator 导航栈
- 创建新的 Flutter 容器后,使用原生导航栈
3.Flutter 跳转 Native
Flutter -> Native,需要注意的时,这里的跳转其实是包含了两种情况:
- 打开原生页面(open,包括但不限于 push)
- 回退到原生页面(close,包括但不限于 pop)。
4.Native 跳转 Native
Native -> Native,这种情况没有什么好说的,直接使用原生的导航栈即可。
二 混合模式
为了解决混合栈问题,以及弥补 Flutter 自身对混合开发支持的不足,业内提出了一些混合栈框架,总得来说,离不开这四种混合模式:
- Flutter Boost 为代表的类 WebView 导航栈
- Flutter Thrio 为代表的 Navigator 导航栈
- 多 Engine 混合模式
- View 级别的混合模式
1. Flutter Boost
Flutter Boost 是闲鱼团队开源的 Flutter 混合框架,成熟稳定,业内影响力高,在导航栈的处理思路上没有绕开我们在 3.2 节中谈及的混合栈原理,但需要注意的是,当 Flutter 跳转 Flutter 时,它采用的是 new 一个新的 FlutterViewController 后使用原生导航栈跳转的方式。
这么做的好处是使用者(业务开发者)操作 Flutter 容器就如同操作 WebView 一样,而 Flutter 页面就如同 Web 页面,逻辑上简单清晰,将所有的导航路由逻辑收归到原生侧处理。如下图,是调用 open 方法时 Flutter Boost 的时序图(关键函数路径),这里可以看到两点信息:
- 混合导航栈的逻辑主要包括原生层、通信层、Dart 层。
- Flutter Boost 的 open 方法实现逻辑相对简单。
但是它也有缺点,就是每次打开 Flutter 页面都需要 new 一个 ViewController,在连续的 Flutter 跳转 Flutter 的场景下有额外的内存开销。针对这个问题,又有团队开发了 Flutter Thrio。
2.Flutter Thrio
上面我们说到,Flutter 跳转 Flutter 这种场景 Flutter Boost 存在额外的内存开销,故哈啰出行团队开源了 Flutter Thrio 混合框架,其针对 Flutter Boost 做出的最重要的改变在于:Flutter 跳转 Flutter 这种场景下,Thrio 使用了 Flutter Navigator 导航栈。
在连续的 Flutter 页面跳转场景下,内存测试图表如下:
从这张图表中我们可以得到以下几点信息:
- 红色区域是启动 Flutter Engine 的内存增量,基本接近 30MB,Flutter Engine 是一个比较重的对象。
- FlutterViewController 带来的内存增量普遍在 12~15MB 左右。
可见,在这种场景下,Thrio 还是做出了一定的优化的。但与之带来的,就是实现的复杂性。我们谈到 Flutter Boost 的优点是简单,路由全部收归原生导航栈。而 Flutter Thrio 混用了原生导航栈和 Flutter Navigator,因此实现会相对更复杂一下。这里我梳理了一下 Flutter Thrio open 时关键函数路径,可以看到,Thrio 的导航管理确实是复杂了一些。
3 多 Engine 模式
以上我们谈及的两种混合框架都是单引擎的,对应的,也存在多引擎的框架。在谈多引擎之前,还是需要先介绍一下关于 Engine、Dart VM、isolate 几个前置知识点。
Dart VM、Engine 与 isolate
(a)Dart 虚拟机创建完成之后,需要创建 Engine 对象,然后会调用 DartIsolate::CreateRootIsolate() 来创建 isolate。
(b)每一个 Engine 实例都为 UI、GPU、IO、Platform Runner 创建各自新的 Thread。
(c)isolate,顾名思义,内存在逻辑上是隔离的。
(d)isolate 中的 code 是按顺序执行的,任何 Dart 程序的并发都是运行多个 isolate 的结果。当然我们可以开启多个 isolate 来处理 CPU 密集型任务。
根据(a)我们可以推出: (1) 每个 Engine 对应一个 isolate 对象,即 Root Isolate。 根据(b)我们可以推出: (2) Engine 是一个比较重的对象(前文也有所提及)。 根据(c)和 (1) 我们可以推出: (3) Engine 与 Engine 之间相互隔离。 根据(d)和 (3) 我们可以推出: (4) Engine 没有共享内存的并发,没有竞争的可能性,不需要锁,也就不存在死锁问题。
多 Engine 模式有以下几个特征:
- App 内存在多个引擎
- 每个引擎内有若干个 FlutterVC
- Engine 与 Engine 之间是隔离的
由于 Engine 之间没有共享内存,这种天然的隔离性其实弊大于利,在混合开发的视角下,一个 App 需要维护两套缓存池——原生缓存池与 DartVM 所持有的缓存池,但是随着开启多 Engine 的介入,后者缓存池的资源又互不相通,导致资源开销变得更加巨大。
为了解决传统的多 Engine 模式所带来的这些问题,又有团队提出了基于 View 级别的混合模式。
4.View 级别的混合模式
基于 View 级别的混合模式,核心是为每个 window 加入 windowId 的概念,以便它们去共享同一份 Root Isolate。我们刚才说到,一个 isolate 具有一个 ui.window 单例对象,那么只需要做一点修改,把 Flutter Engine 加入 ID 的概念传给 Dart 层,让 Dart 层存在多个 window,就可以实现多个 Flutter Engine 共享一个 isolate 了。