码上用它开始Flutter混合开发——FlutterBoost

开源地址: https://github.com/alibaba/flutter_boost

为何须要混合方案

具备必定规模的App一般有一套成熟通用的基础库,尤为是阿里系App,通常须要依赖不少体系内的基础库。那么使用Flutter从新从头开发App的成本和风险都较高。因此在Native App进行渐进式迁移是Flutter技术在现有Native App进行应用的稳健型方式。闲鱼在实践中沉淀出一套本身的混合技术方案。在此过程当中,咱们跟Google Flutter团队进行着密切的沟通,听取了官方的一些建议,同时也针对咱们业务具体状况进行方案的选型以及具体的实现。git

官方提出的混合方案

基本原理

Flutter技术链主要由C++实现的Flutter Engine和Dart实现的Framework组成(其配套的编译和构建工具咱们这里不参与讨论)。Flutter Engine负责线程管理,Dart VM状态管理和Dart代码加载等工做。而Dart代码所实现的Framework则是业务接触到的主要API,诸如Widget等概念就是在Dart层面Framework内容。github

一个进程里面最多只会初始化一个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 Page2 -> Native Page1 -> Flutter Page3

咱们只须要在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之间的通讯,多引擎实例也让这件事情更加复杂。

所以,综合多方面考虑,咱们没有采用多引擎混合方案。

现状与思考

前面咱们提到多引擎存在一些实际问题,因此闲鱼目前采用的混合方案是共享同一个引擎的方案。这个方案基于这样一个事实:任什么时候候咱们最多只能看到一个页面,固然有些特定的场景你能够看到多个ViewController,可是这些特殊场景咱们这里不讨论。

咱们能够这样简单去理解这个方案:咱们把共享的Flutter View当成一个画布,而后用一个Native的容器做为逻辑的页面。每次在打开一个容器的时候咱们经过通讯机制通知Flutter View绘制成当前的逻辑页面,而后将Flutter View放到当前容器里面。

老方案在Dart侧维护了一个Navigator栈的结构。栈数据结构特色就是每次只能从栈顶去操做页面,每一次在查找逻辑页面的时候若是发现页面不在栈顶那么须要往回Pop。这样中途Pop掉的页面状态就丢失了。这个方案没法支持同时存在多个平级逻辑页面的状况,由于你在页面切换的时候必须从栈顶去操做,没法再保持状态的同时进行平级切换。

举个例子:有两个页面A,B,当前B在栈顶。切换到A须要把B从栈顶Pop出去,此时B的状态丢失,若是想切回B,咱们只能从新打开B以前页面的状态没法维持住。这也是老方案最大的一个局限。

如在pop的过程中,可能会把Flutter 官方的Dialog进行误杀。这也是一个问题。

并且基于栈的操做咱们依赖对Flutter框架的一个属性修改,这让这个方案具备了侵入性的特色。这也是咱们须要解决的一个问题。

具体细节,你们能够参考老方案开源项目地址:

https://github.com/alibaba-flutter/hybrid_stack_manager

新一代混合技术方案 FlutterBoost

重构计划

在闲鱼推动Flutter化过程中,更加复杂的页面场景逐渐暴露了老方案的局限性和一些问题。因此咱们启动了代号FlutterBoost(向C++ Boost致敬)的新混合技术方案。此次新的混合方案咱们的主要目标有:

  • 可复用通用型混合方案
  • 支持更加复杂的混合模式。好比支持主页Tab这种状况
  • 无侵入性方案:再也不依赖修改Flutter的方案
  • 支持通用页面生命周期
  • 统一明确的设计概念

跟老方案相似,新的方案仍是采用共享引擎的模式实现。主要思路是由Native容器Container经过消息驱动Flutter页面容器Container,从而达到Native Container与Flutter Container的同步目的。咱们但愿作到Flutter渲染的内容是由Naitve容器去驱动的。

简单的理解,咱们想作到把Flutter容器作成浏览器的感受。填写一个页面地址,而后由容器去管理页面的绘制。在Native侧咱们只须要关心若是初始化容器,而后设置容器对应的页面标志便可。

主要概念

Native层概念

  • Container:Native容器,平台Controller,Activity,ViewController
  • Container Manager:容器的管理者
  • Adaptor:Flutter是适配层
  • Messaging:基于Channel的消息通讯

Dart层概念

  • Container:Flutter用来容纳Widget的容器,具体实现为Navigator的派生类-
  • Container Manager:Flutter容器的管理,提供show,remove等Api
  • Coordinator: 协调器,接受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。这种结构很天然的支持了页面的查找和切换,再也不受制于栈顶操做的问题,以前的一些因为pop致使的问题迎刃而解。同时也再也不须要依赖修改Flutter源码的形式去进行实现,除去了实现的侵入性。

那这是如何作到的呢?

多Navigator的实现

Flutter在底层提供了让你自定义Navigator的接口,咱们本身实现了一个管理多个Navigator的对象。当前最多只会有一个可见的Flutter Navigator,这个Navigator所包含的页面也就是咱们当前可见容器所对应的页面。

Native容器与Flutter容器(Navigator)是一一对应的,生命周期也是同步的。当一个Native容器被建立的时候,Flutter的一个容器也被建立,它们经过相同的id关联起来。当Native的容器被销毁的时候,Flutter的容器也被销毁。Flutter容器的状态是跟随Native容器,这也就是咱们说的Native驱动。由Manager统一管理切换当前在屏幕上展现的容器。

咱们用一个简单的例子描述一个新页面建立的过程:

  1. 建立Native容器(iOS ViewController,Android Activity or Fragment)。
  2. Native容器经过消息机制通知Flutter Coordinator新的容器被建立。
  3. Flutter Container Manager进而获得通知,负责建立出对应的Flutter容器,而且在其中装载对应的Widget页面。
  4. 当Native容器展现到屏幕上时,容器发消息给Flutter Coordinator通知要展现页面的id.
  5. Flutter Container Manager找到对应id的Flutter Container并将其设置为前台可见容器。

这就是一个新页面建立的主要逻辑,销毁和进入后台等操做也相似有Native容器事件去进行驱动。

总结

目前FlutterBoost已经在生产环境支撑着在闲鱼客户端中全部的基于Flutter开发业务,为更加负复杂的混合场景提供了支持。同时也解决了一些历史遗留问题。

咱们在项目启动之初就但愿FlutterBoost可以解决Native App混合模式接入Flutter这个通用问题。因此咱们把它作成了一个可复用的Flutter插件,但愿吸引更多感兴趣的朋友参与到Flutter社区的建设。咱们的方案可能不是最好的,这个方案距离完美还有很大的距离,咱们但愿经过多分享交流以推进Flutter技术社区的发展与建设。咱们更但愿看到社区可以涌现出更加优秀的组件和方案。

在有限篇幅中,咱们分享了闲鱼在Flutter混合技术方案中积累的经验和代码。欢迎兴趣的同窗可以积极与咱们一块儿交流学习。

扩展补充

性能相关

在两个Flutter页面进行切换的时候,由于咱们只有一个Flutter View因此须要对上一个页面进行截图保存,若是Flutter页面多截图会占用大量内存。这里咱们采用文件内存二级缓存策略,在内存中最多只保存2-3个截图,其他的写入文件按需加载。这样咱们能够在保证用户体验的同时在内存方面也保持一个较为稳定的水平。

页面渲染性能方面,Flutter的AOT优点展露无遗。在页面快速切换的时候,Flutter可以很灵敏的相应页面的切换,在逻辑上创造出一种Flutter多个页面的感受。

Release 1.0支持

项目开始的时候咱们基于闲鱼目前使用的Flutter版本进行开发,然后进行了Release 1.0兼容升级测试目前没有发现问题。

接入

只要是集成了Flutter的项目均可以用官方依赖的方式很是方便的以插件形式引入FlutterBoost,只须要对工程进行少许代码接入便可完成接入。
详细接入文档,请参阅GitHub主页官方项目文档。

现已开源

目前,新一代混合栈已经在闲鱼全面应用。咱们很是乐意将沉淀的技术回馈给社区。欢迎你们一块儿贡献,一块儿交流,携手共建Flutter社区。

原文连接 本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索