穆萨妹编者按:具备一定规模的 App 一般来说有两套成形通用型的此基础库,尤其是穆萨系 App,一般须要倚赖很多管理体系内的此基础库。那么使用 Flutter 再次一气呵成合作开发 App 的生产成本和风险都较低。所以在 Native App 展开渐进北迁是 Flutter 控制技术在原有 Native App 展开应用领域的平稳型方式。
今天他们来看一看,eBay项目组怎样在那个课堂教学操作过程中结晶出两套各具特色的混和控制技术计划。
概要思索
eBay目前采用的混和计划是共享资源同两个发动机的计划。那个计划如前所述这种两个历史事实:任何这时候他们最多根本无法看见两个网页,当然有些某一的情景你能看见数个 ViewController ,但是这些特定情景他们这里不探讨。
我们能这种单纯去认知那个计划:他们把共享资源的 Flutter View 当做两个画笔,接着用两个 Native 的罐子作为方法论的网页。每天在关上两个罐子的这时候他们通过通讯监督机制通告 Flutter View 绘出成现阶段的方法论网页,接着将 Flutter View 放在现阶段罐子里头。
那个计划难以支持与此同时存在数个Kendujhar方法论网页的情况,因为你在网页转换的这时候必须从args去操作方式,难以再保持状况的与此同时展开Kendujhar转换。举个范例:有两个网页A,B,现阶段B在args。转换到A须要把B从args Pop 进来,这时B的状况遗失,如果想切回B,他们根本无法再次关上B之前网页的状况难以保持住。
如在 pop 的操作过程之中,可能会把 Flutter 非官方的 Dialog 展开误伤。而且如前所述栈的操作方式他们倚赖对 Flutter 架构的两个特性修正,这让那个计划具备了侵入性的特征。
第三代混和控制技术计划 FlutterBoost
解构计划
在eBay推进 Flutter 化操作过程之中,更加复杂的网页情景逐渐暴露了老计划的局限性和一些问题。所以他们启动了代号 FlutterBoost(向C++ Boost库致敬)的新混和控制技术计划。这次新的混和计划他们的主要目标有:
可复用通用型型混和计划支持更加复杂的混和模式,比如支持主页Tab这种情况无侵入性计划:不再倚赖修正 Flutter 的计划支持通用型网页生命周期统一明确的设计概念跟老计划类似,新的计划还是采用共享资源发动机的模式实现。主要思路是由 Native 罐子 Container 通过消息驱动 Flutter 网页罐子 Container,从而达到 Native Container与 Flutter Container 的同步目的。他们希望做到 Flutter 渲染的内容是由 Naitve 罐子去驱动的。
单纯的认知,他们想做到把 Flutter 罐子做成浏览器的感觉。填写两个网页地址,接着由罐子去管理网页的绘出。在 Native 侧他们只须要关心如果初始化罐子,接着设置罐子对应的网页标志即可。
主要概念
Native 层概念
Container:Native 罐子,平台 Controller,Activity,ViewControllerContainer Manager:罐子的管理者Adaptor:Flutter 是适配层Messaging:如前所述 Channel 的消息通讯Dart 层概念
Container:Flutter 用来容纳 Widget 的罐子,具体实现为 Navigator 的派生类Container Manager:Flutter 罐子的管理,提供 show,remove 等 ApiCoordinator: 协调器,接受 Messaging 消息,负责调用 Container Manager 的状况管理。Messaging:如前所述 Channel 的消息通讯关于网页的认知
在 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 。这种结构很自然的支持了网页的查找和转换,不再受制于args操作方式的问题,之前的一些由于 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 罐子( iOS ViewController,Android Activity or Fragment )。Native 罐子通过消息监督机制通告 Flutter Coordinator 新的罐子被创建。Flutter Container Manager 进而得到通告,负责创建出对应的 Flutter 罐子,并且在其中装载对应的 Widget 网页。当 Native 容器展示到屏幕上时,罐子发消息给 Flutter Coordinator 通告要展示网页的 id 。Flutter Container Manager 找到对应 id 的 Flutter Container 并将其设置为前台可见罐子。这就是两个新网页创建的主要方法论,销毁和进入后台等操作方式也类似有 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 本身是两个比较重的对象。
多发动机模式的问题
冗余的资源问题:多发动机模式下每个发动机之间的 Isolate 是相互独立的。在方法论上这并没有什么坏处,但是发动机底层其实是维护了图片缓存等比较消耗内存的对象。想象一下,每个发动机都维护自己一份图片缓存,内存压力将会非常大。插件注册的问题:插件倚赖 Messenger 去传递消息,而目前 Messenger 是由 FlutterViewController(Activity) 去实现的。如果你有数个 FlutterViewController ,插件的注册和通讯将会变得混乱难以维护,消息的传递的源头和目标也变得不可控。Flutter Widget 和 Native 的网页差异化问题: Flutter 的网页是 Widget, Native 的网页是 VC 。方法论上来说他们希望消除 Flutter 网页与 Naitve 网页的差异,否则在展开网页埋点和其它一些统一操作方式的这时候都会遇到额外的复杂度。增加页面之间通讯的复杂度:如果所有 Dart 代码都运行在同两个发动机示例,它们共享资源两个 Isolate ,能用统一的编程架构展开 Widget 之间的通讯,多发动机示例也让这件事情更加复杂。因此,综合多方面考虑,他们没有采用多发动机混和计划。
总结
目前 FlutterBoost 已经在生产环境支撑着在eBay客户端中所有的如前所述 Flutter 合作开发业务,为更加负复杂的混和情景提供了支持,稳定为亿级用户提供服务。
他们在项目启动之初就希望 FlutterBoost 能够解决 Native App 混和模式接入 Flutter 那个通用型问题。所以他们把它做成了两个可复用的 Flutter 插件,希望吸引更多感兴趣的朋友参与到 Flutter 社区的建设。在有限篇幅中,他们分享了eBay在 Flutter 混和控制技术计划中积累的经验和代码。欢迎兴趣的同学能够积极与他们一起交流学习。
扩展补充
性能相关
在两个 Flutter 网页展开转换的这时候,因为他们只有两个 Flutter View 所以须要对上两个网页展开截图保存,如果 Flutter 网页多截图会占用大量内存。这里他们采用文件内存二级缓存策略,在内存中最多只保存2-3个截图,其余的写入文件按需加载。这种他们能在保证用户体验的与此同时在内存方面也保持两个较为稳定的水平。
网页渲染性能方面, Flutter 的 AOT 优势展露无遗。在网页快速转换的这时候, Flutter 能够很灵敏的相应网页的转换,在方法论上创造出一种 Flutter 数个网页的感觉。
Release1.0的支持
项目开始的这时候他们如前所述eBay目前使用的 Flutter 版本展开合作开发,而后展开了 Release 1.0 兼容升级测试目前没有发现问题。
接入
只要是集成了 Flutter 的项目都能用非官方倚赖的方式非常方便的以插件形式引入 FlutterBoost ,只须要对工程展开少量代码接入即可完成接入。 详细接入文档,请参阅GitHub主页非官方项目文档。
作者: 福居