阿里妹导读:具备必定规模的 App 一般有一套成熟通用的基础库,尤为是阿里系 App,通常须要依赖不少体系内的基础库。那么使用 Flutter 从新从头开发 App 的成本和风险都较高。因此在 Native App 进行渐进式迁移是 Flutter 技术在现有 Native App 进行应用的稳健型方式。
今天咱们来看看,闲鱼团队如何在这个实践过程当中沉淀出一套独具特点的混合技术方案。编程
闲鱼目前采用的混合方案是共享同一个引擎的方案。这个方案基于这样一个事实:任什么时候候咱们最多只能看到一个页面,固然有些特定的场景你能够看到多个 ViewController ,可是这些特殊场景咱们这里不讨论。浏览器
咱们能够这样简单去理解这个方案:咱们把共享的 Flutter View 当成一个画布,而后用一个 Native 的容器做为逻辑的页面。每次在打开一个容器的时候咱们经过通讯机制通知 Flutter View 绘制成当前的逻辑页面,而后将 Flutter View 放到当前容器里面。缓存
这个方案没法支持同时存在多个平级逻辑页面的状况,由于你在页面切换的时候必须从栈顶去操做,没法再保持状态的同时进行平级切换。举个例子:有两个页面A,B,当前B在栈顶。切换到A须要把B从栈顶 Pop 出去,此时B的状态丢失,若是想切回B,咱们只能从新打开B以前页面的状态没法维持住。框架
如在 pop 的过程中,可能会把 Flutter 官方的 Dialog 进行误杀。并且基于栈的操做咱们依赖对 Flutter 框架的一个属性修改,这让这个方案具备了侵入性的特色。工具
重构计划性能
在闲鱼推动 Flutter 化过程中,更加复杂的页面场景逐渐暴露了老方案的局限性和一些问题。因此咱们启动了代号 FlutterBoost(向C++ Boost库致敬)的新混合技术方案。此次新的混合方案咱们的主要目标有:学习
跟老方案相似,新的方案仍是采用共享引擎的模式实现。主要思路是由 Native 容器 Container 经过消息驱动 Flutter 页面容器 Container,从而达到 Native Container与 Flutter Container 的同步目的。咱们但愿作到 Flutter 渲染的内容是由 Naitve 容器去驱动的。测试
简单的理解,咱们想作到把 Flutter 容器作成浏览器的感受。填写一个页面地址,而后由容器去管理页面的绘制。在 Native 侧咱们只须要关心若是初始化容器,而后设置容器对应的页面标志便可。spa
主要概念插件
Native 层概念
Dart 层概念
关于页面的理解
在 Native 和 Flutter 表示页面的对象和概念是不一致的。在 Native,咱们对于页面的概念通常是 ViewController,Activity 。而对于 Flutter 咱们对于页面的概念是 Widget 。咱们但愿可统一页面的概念,或者说弱化抽象掉 Flutter 自己的 Widget 对应的页面概念。换句话说,当一个 Native 的页面容器存在的时候, FlutteBoost 保证必定会有一个 Widget 做为容器的内容。因此咱们在理解和进行路由操做的时候都应该以 Native 的容器为准, Flutter Widget 依赖于 Native 页面容器的状态。
那么在 FlutterBoost 的概念里说到页面的时候,咱们指的是 Native 容器和它所附属的 Widget 。全部页面路由操做,打开或者关闭页面,实际上都是对 Native 页面容器的直接操做。不管路由请求来自何方,最终都会转发给 Native 去实现路由操做。这也是接入 FlutterBoost 的时候须要实现 Platform 协议的缘由。
另外一方面,咱们没法控制业务代码经过 Flutter 自己的 Navigator 去 push 新的 Widget 。对于业务不经过 FlutterBoost 而直接使用 Navigator 操做 Widget 的状况,包括 Dialog 这种非全屏 Widget,咱们建议是业务本身负责管理其状态。这种类型 Widget 不属于 FlutterBoost 所定义的页面概念。
理解这里的页面概念,对于理解和使用 FlutterBoost 相当重要。
与老方案主要差异
前面咱们提到老方案在 Dart 层维护单个 Navigator 栈结构用于 Widget 的切换。而新的方案则是在 Dart 侧引入了 Container 的概念,再也不用栈的结构去维护现有的页面,而是经过扁平化 key-value 映射的形式去维护当前全部的页面,每一个页面拥有一个惟一的 id 。这种结构很天然的支持了页面的查找和切换,再也不受制于栈顶操做的问题,以前的一些因为 pop 致使的问题迎刃而解。也不须要依赖修改 Flutter 源码的形式去进行页面栈操做,去掉了实现的侵入性。
实际上咱们引入的 Container 就是 Navigator 的,也就是说一个 Native 的容器对应了一个 Navigator 。那这是如何作到的呢?
多 Navigator 的实现
Flutter 在底层提供了让你自定义 Navigator 的接口,咱们本身实现了一个管理多个 Navigator 的对象。当前最多只会有一个可见的 Flutter Navigator ,这个Navigator 所包含的页面也就是咱们当前可见容器所对应的页面。
Native 容器与 Flutter 容器( Navigator )是一一对应的,生命周期也是同步的。当一个 Native 容器被建立的时候, Flutter 的一个容器也被建立,它们经过相同的 id 关联起来。当 Native 的容器被销毁的时候, Flutter 的容器也被销毁。 Flutter 容器的状态是跟随 Native 容器,这也就是咱们说的 Native 驱动。由 Manager 统一管理切换当前在屏幕上展现的容器。
咱们用一个简单的例子描述一个新页面建立的过程:
这就是一个新页面建立的主要逻辑,销毁和进入后台等操做也相似有 Native 容器事件去进行驱动。
基本原理
Flutter 技术链主要由 C++实现的 Flutter Engine 和 Dart 实现的 Framework 组成(其配套的编译和构建工具咱们这里不参与讨论)。Flutter Engine 负责线程管理,Dart VM 状态管理和 Dart 代码加载等工做。而 Dart 代码所实现的 Framework 则是业务接触到的主要 API,诸如 Widget 等概念就是在 Dart 层面 Framework 内容。
一个进程里面最多只会初始化一个 Dart VM。然而一个进程能够有多个 Flutter Engine,多个 Engine 实例共享同一个 Dart VM。
咱们来看具体实现,在 iOS 上面每初始化一个 FlutterViewController 就会有一个引擎随之初始化,也就意味着会有新的线程(理论上线程能够复用)去跑 Dart 代码。Android 相似的 Activity 也会有相似的效果。若是你启动多个引擎实例,注意此时Dart VM 依然是共享的,只是不一样 Engine 实例加载的代码跑在各自独立的 Isolate。
引擎深度共享
在混合方案方面,咱们跟 Google 讨论了可能的一些方案。Flutter 官方给出的建议是从长期来看,咱们应该支持在同一个引擎支持多窗口绘制的能力,至少在逻辑上作到 FlutterViewController 是共享同一个引擎的资源的。换句话说,咱们但愿全部绘制窗口共享同一个主 Isolate。
但官方给出的长期建议目前来讲没有很好的支持。
多引擎模式
咱们在混合方案中解决的主要问题是如何去处理交替出现的 Flutter 和 Native 页面。Google 工程师给出了一个 Keep It Simple 的方案:对于连续的 Flutter 页面(Widget)只须要在当前 FlutterViewController 打开便可,对于间隔的 Flutter 页面咱们初始化新的引擎。
例如,咱们进行下面一组导航操做:
咱们只须要在 Flutter Page1 和 Flutter Page3 建立不一样的 Flutter 实例便可。
这个方案的好处就是简单易懂,逻辑清晰,可是也有潜在的问题。若是一个 Native页面一个 Flutter 页面一直交替进行的话,Flutter Engine 的数量会线性增长,而Flutter Engine 自己是一个比较重的对象。
多引擎模式的问题
所以,综合多方面考虑,咱们没有采用多引擎混合方案。
目前 FlutterBoost 已经在生产环境支撑着在闲鱼客户端中全部的基于 Flutter 开发业务,为更加负复杂的混合场景提供了支持,稳定为亿级用户提供服务。
咱们在项目启动之初就但愿 FlutterBoost 可以解决 Native App 混合模式接入 Flutter 这个通用问题。因此咱们把它作成了一个可复用的 Flutter 插件,但愿吸引更多感兴趣的朋友参与到 Flutter 社区的建设。在有限篇幅中,咱们分享了闲鱼在 Flutter 混合技术方案中积累的经验和代码。欢迎兴趣的同窗可以积极与咱们一块儿交流学习。
扩展补充
在两个 Flutter 页面进行切换的时候,由于咱们只有一个 Flutter View 因此须要对上一个页面进行截图保存,若是 Flutter 页面多截图会占用大量内存。这里咱们采用文件内存二级缓存策略,在内存中最多只保存2-3个截图,其他的写入文件按需加载。这样咱们能够在保证用户体验的同时在内存方面也保持一个较为稳定的水平。
页面渲染性能方面, Flutter 的 AOT 优点展露无遗。在页面快速切换的时候, Flutter 可以很灵敏的相应页面的切换,在逻辑上创造出一种 Flutter 多个页面的感受。
项目开始的时候咱们基于闲鱼目前使用的 Flutter 版本进行开发,然后进行了 Release 1.0 兼容升级测试目前没有发现问题。
只要是集成了 Flutter 的项目均可以用官方依赖的方式很是方便的以插件形式引入 FlutterBoost ,只须要对工程进行少许代码接入便可完成接入。 详细接入文档,请参阅GitHub主页官方项目文档。
本文来自云栖社区合做伙伴“阿里技术”,如需转载请联系原做者。