Flutter进阶:混合开发FlutterBoost
闲鱼在实践中沉淀出⼀套⾃⼰的混合技术⽅案。在此过程中,我们跟Google Flutter团队进⾏着密切的沟通,听取了官⽅的⼀些建议,同时也针对我们业务具体情况进⾏⽅案的选型以及具体的实现。
官⽅提出的混合⽅案
基本原理
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
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之间的通信,多引擎实例也让这件事情更加复杂。
因此,综合多⽅⾯考虑,我们没有采⽤多引擎混合⽅案。
现状及思考flutter支持鸿蒙吗
前⾯我们提到多引擎存在⼀些实际问题,所以闲鱼⽬前采⽤的混合⽅案是共享同⼀个引擎的⽅案。这个⽅案基于这样⼀个事实:任何时候我们最多只能看到⼀个页⾯,当然有些特定的场景你可以看到多个ViewController,但是这些特殊场景我们这⾥不讨论。
我们可以这样简单去理解这个⽅案:我们把共享的Flutter View当成⼀个画布,然后⽤⼀个Native的容器作为逻辑的页⾯。每次在打开⼀个容器的时候我们通过通信机制通知Flutter View绘制成当前的逻辑页⾯,然后将Flutter View放到当前容器⾥⾯。
这个⽅案⽆法⽀持同时存在多个平级逻辑页⾯的情况,因为你在页⾯切换的时候必须从栈顶去操作,⽆法再保持状态的同时进⾏平级切换。举个例⼦:有两个页⾯A,B,当前B在栈顶。切换到A需要把B从栈顶Pop出去,此时B的状态丢失,如果想切回B,我们只能重新打开B之前页⾯的状态⽆法维持住。
如在pop的过程当中,可能会把Flutter 官⽅的Dialog进⾏误杀。⽽且基于栈的操作我们依赖对Flutter框架的⼀个属性修改,这让这个⽅案具有了侵⼊性的特点。
新⼀代混合技术⽅案 FlutterBoost
重构计划
在闲鱼推进Flutter化过程当中,更加复杂的页⾯场景逐渐暴露了⽼⽅案的局限性和⼀些问题。所以我们启动了代号FlutterBoost(向C++ Boost 库致敬)的新混合技术⽅案。这次新的混合⽅案我们的主要⽬标有:
可复⽤通⽤型混合⽅案
⽀持更加复杂的混合模式,⽐如⽀持主页Tab这种情况
⽆侵⼊性⽅案:不再依赖修改Flutter的⽅案
⽀持通⽤页⾯⽣命周期
统⼀明确的设计概念
跟⽼⽅案类似,新的⽅案还是采⽤共享引擎的模式实现。主要思路是由Native容器Container通过消息驱动Flutter页⾯容器Container,从⽽达到Native Container与Flutter Container的同步⽬的。我们希望做到Flutter渲染的内容是由Naitve容器去驱动的。
简单的理解,我们想做到把Flutter容器做成浏览器的感觉。填写⼀个页⾯地址,然后由容器去管理页⾯的绘制。在Native侧我们只需要关⼼如果初始化容器,然后设置容器对应的页⾯标志即可。
image.png
Native层概念
Container:Native容器,平台Controller,Activity,ViewController
Container Manager:容器的管理者
Adaptor:Flutter是适配层
Messaging:基于Channel的消息通信
4
Dart层概念
Container:Flutter⽤来容纳Widget的容器,具体实现为Navigator的派⽣类-
Container Manager:Flutter容器的管理,提供show,remove等Api
Coordinator: 协调器,接受Messaging消息,负责调⽤Container Manager的状态管理。
Messaging:基于Channel的消息通信
5
关于页⾯的理解
在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容器(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容器事件去进⾏驱动。
⽬前FlutterBoost已经在⽣产环境⽀撑着在闲鱼客户端中所有的基于Flutter开发业务,为更加负复杂的混合场景提供了⽀持,稳定为亿级⽤户提供服务。
我们在项⽬启动之初就希望FlutterBoost能够解决Native App混合模式接⼊Flutter这个通⽤问题。所以我们把它做成了⼀个可复⽤的Flutter插件,希望吸引更多感兴趣的朋友参与到Flutter社区的建设。
性能相关
在两个Flutter页⾯进⾏切换的时候,因为我们只有⼀个Flutter View所以需要对上⼀个页⾯进⾏截图保存,如果Flutter页⾯多截图会占⽤⼤量内存。这⾥我们采⽤⽂件内存⼆级缓存策略,在内存中最多只保存2-3个截图,其余的写⼊⽂件按需加载。这样我们可以在保证⽤户体验的同时在内存⽅⾯也保持⼀个较为稳定的⽔平。
页⾯渲染性能⽅⾯,Flutter的AOT优势展露⽆遗。在页⾯快速切换的时候,Flutter能够很灵敏的相应页⾯的切换,在逻辑上创造出⼀种Flutter 多个页⾯的感觉。
2
Release1.0的⽀持
项⽬开始的时候我们基于闲鱼⽬前使⽤的Flutter版本进⾏开发,⽽后进⾏了Release 1.0兼容升级测试⽬前没有发现问题。
3
接⼊
只要是集成了Flutter的项⽬都可以⽤官⽅依赖的⽅式⾮常⽅便的以插件形式引⼊FlutterBoost,只需要对⼯程进⾏少量代码接⼊即可完成接⼊。详细接⼊⽂档,请参阅GitHub主页官⽅项⽬⽂档。
4
现已开源
⽬前,新⼀代混合栈已经在闲鱼全⾯应⽤。我们⾮常乐意将沉淀的技术回馈给社区。欢迎⼤家⼀起贡献,⼀起交流,携⼿共建Flutter社区。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。