乐趣区

GMTC2019闲鱼基于Flutter的架构演进与创新

2012 年应届毕业加入阿里巴巴,主导了闲鱼基于 Flutter 的新混合架构,同时推进了 Flutter 在闲鱼各业务线的落地。未来将持续关注终端技术的演变及趋势

Flutter 的优势与挑战

Flutter 是 Google 开源的跨端便携 UI 工具包,除了具有非常优秀的跨端渲染一致性,还具备非常高效的研发体验,丰富的开箱即用的 UI 组件,以及跟 Native 媲美的性能体验。由于它的众多优势,也使得 Flutter 成为了近些年来热门的新技术。

通过以上的特点可以看出,Flutter 可以极大的加速客户端的研发效率,与此同时得到优秀的性能体验,基于我的思考,Flutter 会为以下团队带来较大的收益:

  • 中小型的客户端团队非常适合 Flutter 开发,不仅一端编写双端产出,还有效的解决了小团队需要双端人员(iOS:Android)占比接近 1:1 的限制,在项目快速推进过程中,能让整个团队的产能最大化。
  • App 在 Android 市场占比远高于 iOS 的团队,比如出海东南亚的一些 App,Android 市场整体占比在 90% 以上,通过 Flutter 可以将更多的人力 Focus 在 Android 市场上,同时通过在 iOS 端较小的投入,在结果上达到买一送一的效果。
  • 以量产 App 为主要策略的团队,不论是量产 ToB 的企业 App,还是有针对性的产出不同领域的 ToC 的 App 的公司,都可以通过一端开发多端产出的 Flutter 得到巨大的产能提升。

闲鱼在以上的场景中属于第一种场景,服务 3 亿用户的闲鱼 App 的背后,开发资源投入很少,与竞对相比,我们是一只再小不过的团队,在这种场景下,Flutter 为闲鱼业务的稳定发展以及提供更多的创新产品给予了很大的帮助。

但与此同时,Flutter 在设计上带来的优势同时又会带来新的问题。所有的新技术都是脱胎于老技术的,Flutter 也不例外,其身上带有很多 Chrome 的影子。我们再做一层简化,如果我们认为 Flutter 是一个使用 Dart 语言的浏览器容器,请大家思考一下两个问题如何解决。

  • 如果在一个已经存在的 App 中加入 Flutter,如何让 Native 与 Flutter 进行无缝的衔接,同时保证相互开发之间的隔离性
  • 如果在 Flutter 的容器中,使用已有的 Native UI 组件,在 Flutter 与 Native 渲染机制不同的情况下,怎么保证两者的无缝衔接以及高性能。

闲鱼的架构演进与创新

带着上面两个问题,我们来到闲鱼场景下的具体 Case 以及解决方案的演进过程。

已有 App+Flutter 容器

在这种情况下,闲鱼需要考虑的是首先要考虑引入 Flutter 容器后的内存压力,保证不要产生更多的内存溢出。与此同时我们希望能让 Flutter 和 Native 之间的页面切换是顺畅的,对不同技术栈之间的同学透明。因此我们有针对性的进行了多次迭代。

在没有任何改造的情况下以 iOS 为例,你可以通过创建新的 FlutterViewController 来创建一个新的 Flutter 容器,这个方案下,当创建多个 FlutterViewController 时会同时在内存中创建多个 Flutter Engine 的 Runtime(虽然底层 Dart VM 依然只有一个),这对内存消耗是相当大的,同时多个 Flutter Engine 的 Runtime 会造成每个 Runtime 内的数据无法直接共享,造成数据同步困难。

这种情况下,闲鱼选择了全局共享同一个 FlutterViewController 的方式保证了内存占用的最小化,同时通过基础框架 Flutter Boost 提供了 Native 栈与 Flutter 栈的通信与管理,保证了当 Native 打开或关闭一个新的 Flutter 页面时,Dart 侧的 Navigator 也做到自动的打开或关闭一个新的 Widget。目前 Google 官方的提供的方案上就是参考闲鱼早先的这个版本进行的实现的。

然而在这种情况下,如果出现如闲鱼图中所示多个 Tab 的场景下,整个堆栈逻辑就会产生混乱,因此闲鱼在这个基础上对 Flutter Boost 的方案进行了升级并开源,通过在 Dart 侧提供一个 BoostContainerManager 的方式,提供了对多个 Navigator 的管理能力,如果打比方来看这件事,就相当于,针对 Flutter 的容器提供了一个类似 WebView 的 OpenWindow 的能力,每做一次 OpenWindow 的调用,就会产生一个新的 Navigator,这样开发者就可以自由的选择是在 Navigator 里进行 Push 和 Pop,还是直接通过 Flutter Boost 新开一个 Navigator 进行独立管理。

Flutter Boost 目前已在 github 开源,由于闲鱼目前线上版本只支持 Flutter 1.2 的版本,因此需要支持 1.5 的同学等稍等,我们会在近期更新支持 1.5 的 Flutter Boost 版本。

Flutter 页面 +Native UI

由于闲鱼是一个闲置交易社区,因此图片和视频相对较多,对图片视频的线上性能以及内存占用有较严格的要求。目前 Flutter 已提供的几种方案中(Platform View 以及 Flutter Plugin),不论是对内存的占用还是整个的线上流畅度上还存在一定的问题,这就造成了当大部分同学跟闲鱼一样实现一个复杂的图文 Feed 推荐场景的时候,非常容易产生内存溢出。而实际上,闲鱼在以上的场景下有针对性的做出了较大的优化。

在整个的 Native UI 到 Flutter 渲染引擎桥接的过程中,我们选用了 Flutter Plugin 中提供的 FlutterTextureRegistry 的能力,在去年上半年我们优先针对视频的场景进行了优化,优化的思路主要是针对 Flutter Engine 底层的外接纹理接口进行修改,将原有接口中必须传入一个 PixelBuffer 的内存对象这一限制做了扩展,增加一个新的接口保证其可以传入一个 GPU 对象的 TextureID。

如图中所示,优化后的整个链路 Flutter Engine 可以直接通过 Native 端已经生成好的 TextureID 进行 Flutter 侧的渲染,这样就将链路从 Native 侧生成的 TextureID->copy 的内存对象 PixelBuffer-> 生成新的 TextureID-> 渲染,转变为 Native 侧生成的 TextureID-> 渲染。整个链路极大的缩短,保证了整个的渲染效率以及更小的内存消耗。闲鱼在将这套方案上线后,又尝试将该方案应用于图片渲染的场景下,使得图片的缓存,CDN 优化,图片裁切等方案与 Native 归一,在享受已有集团中间件的性能优化的同时,也得到了更小的内存消耗,方案落地后,内存溢出大幅减少。

目前该方案由于需要配合 Flutter Engine 的修改,因此暂时无法提供完整的方案至开源社区,我们正在跟 google 积极沟通整个修改方案,相信在这一两个月内会将试验性的 Engine Patch 开源至社区,供有兴趣的同学参考。

复杂业务场景的架构创新实践

将以上两个问题解决以后,闲鱼开始了 Flutter 在业务侧的全面落地,然而很快又遇到新的问题,在多人协作过程中:

  • 如何提供一些标准供大家进行参考保证代码的一致性
  • 如何将复杂业务进行有效的拆解变成子问题
  • 如何保证更多的同学可以快速上手并写出性能和稳定性都不错的代码

在方案的前期,我们使用了社区的 Flutter Redux 方案,由于最先落地的详情,发布等页面较为复杂,因此我们有针对性的对 View 进行了组件化的拆分,但由于业务的复杂性,很快这套方案就出现了问题,对于单个页面来说,State 的属性以及 Reducer 的数量都非常多,当产生新需求堆叠的时候,修改困难,容易产生线上问题。

针对以上的情况,我们进行了整个方案的第二个迭代,在原有 Page 的基础上提供了 Component 的概念,使得每个 Component 具备完整的 Redux 元素,保证了 UI,逻辑,数据的完整隔离,每个 Component 单元下代码相对较少,易于维护和开发,但随之而来的问题是,当页面需要产生数据同步时,整个的复杂性飙升,在 Page 的维度上失去了统一状态管理的优势。

在这种情况下闲鱼换个角度看端侧的架构设计,我们参考 React Redux 框架中的 Connect 的思想,移除掉在 Component 的 Store,随之而来的是新的 Connector 作为 Page 和 Component 的数据联通的桥梁,我们基于此实现了 Page State 到 Component State 的转换,以及 Component State 变化后对 Page State 的自动同步,从而保证了将复杂业务有效的拆解成子问题,同时享受到统一状态管理的优势。与此同时基于新的框架,在统一了大家的开发标准的情况下,新框架也在底层有针对性的提供了对长列表,多列表拼接等 case 下的一些性能优化,保证了每一位同学在按照标准开发后,可以得到相对目前市面上其他的 Flutter 业务框架相比更好的性能。

目前这套方案 Fish Redux 已经在 github 开源,目前支持 1.5 版本,感兴趣的同学可以去 github 进行了解。

研发智能化在闲鱼的应用

闲鱼在去年经历了业务的快速成长,在这个阶段上,我们同时进行了大量的 Flutter 的技术改造和升级,在尝试新技术的同时,如何能保证线上的稳定,线下的有更多的时间进行新技术的尝试和落地,我们需要一些新的思路和工作方式上的改变。

以我们日常工作为例,Flutter 的研发同学,在每次开发完成后,需要在本地进行 Flutter 产物的编译并上传到远端 Repo,以便对 Native 同学透明,保证日常的研发不受 Flutter 改造的干扰。在这个过程中,Flutter 侧的业务开发同学面临着很多打包上传更新同步等繁琐的工作,一不小心就会出错,后续的排查等让 Flutter 前期的开发变成了开发 5 分钟,打包测试 2 小时。同时 Flutter 到底有没有解决研发效率快的问题,以及同学们在落地过程中有没有 Follow 业务架构的标准,这一切都是未知的。

在痛定思痛以后,我们认为数据化 + 自动化是解决这些问题的一个较好的思路。因此我们首先从源头对代码进行管控,通过 commit,将代码与后台的需求以及 bug 一一关联,对于不符合要求的 commit 信息,不允许进行代码合并,从而保证了后续数据报表分析的数据源头是健康的。

在完成代码和任务关联后,通过 webhook 就可以比较轻松的完成后续的工作,将每次的 commit 有效的关联到我们的持续集成平台的任务上来,通过闲鱼 CI 工作平台将日常打包自动化测试等流程变为自动化的行为,从而极大的减少了日常的工作。粗略统计下来,在去年自动化体系落地的过程中单就自动打 Flutter 包上传以及触发最终的 App 打包这一流程就让每位同学每天节省一个小时以上的工作量,效果非常明显。另外,基于代码关联需求的这套体系,可以相对容易的构建后续的数据报表对整个过程和结果进行细化的分析,用数据驱动过程改进,保证新技术的落地过程的收益有理有据。

总结与展望

回顾一下上下文

  • Flutter 的特性非常适合中小型客户端团队 /Android 市场占比较高的团队 / 量产 App 的团队。同时由于 Flutter 的特性导致其在混合开发的场景下面存在一定劣势。
  • 闲鱼团队针对混合开发上的几个典型问题提供了对应的解决方案,使整个方案达到上线要求,该修改会在后续开放给 google 及社区。
  • 为全面推动 Flutter 在业务场景下的落地,闲鱼团队通过多次迭代演进出 Fish Redux 框架,保证了每位同学可以快速写出相对优秀的 Flutter 代码。
  • 新技术的落地过程中,在过程中通过数据化和自动化的方案极大的提升了过程中的效率,为 Flutter 在闲鱼的落地打下了坚实的基础。

除了本文提及的各种方案外,闲鱼目前还在多个方向上发力,并对针对 Flutter 生态的未来进行持续的关注,分享几个现在在做的事情

  • Flutter 整个上层基础设施的标准化演进,混合工程体系是否可以在上层完成类似 Spring-boot 的完整体系构架,帮助更多的 Flutter 团队解决上手难,无行业标准的问题。
  • 动态性能力的扩展,在符合各应用商店标准的情况下,助力业务链路的运营效率提升,保证业务效果。目前闲鱼已有的动态化方案会后续作为 Fish-Redux 的扩展能力提供动态化组件能力 + 工具链体系。
  • Fish-Redux + UI2Code,打通代码生成链路和业务框架,保证在团队标准统一的情况下,将 UI 工作交由机器生成。
  • Flutter + FaaS,让客户端同学可以成为全栈工程师,通过前后端一体的架构设计,极大的减少协同,提升效率。

让工程师去从事更多创造性的工作,是我们一直努力的目标。闲鱼团队也会在新的一年更多的完善 Flutter 体系的建设,将更多已有的沉淀回馈给社区,帮助 Flutter 社区一起健康成长。


本文作者:闲鱼技术 - 宗心

阅读原文

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

退出移动版