Flutter 作为革命性的跨终端解决方案,于 2018 年 12 月正式发布,仅用了不到一年的时间就在 GitHub 和 StackOverflow 上获得了比 React Native 更高的知名度。那么所有项目都应该使用 Flutter 吗?并非如此。没有最好的框架,只有最适合的框架。是什么原因让闲鱼选择了 Flutter?闲鱼在架构 Flutter 化这方面有着怎样的经验与挑战呢?带着这些问题,InfoQ 记者采访了 GMTC 专题出品人于佳(宗心),以下为采访实录。
我从 14 年开始参与手机淘宝的研发体系的升级,彼时正值淘宝 all in 无线的时期,大量研发人员涌入客户端进行开发工作。开发人员变多了,也带来了一个问题:当一百名以上的同学开出对应数量的分支进行研发时,在协同效率上出现了巨大的瓶颈,代码冲突十分严重,解决冲突的成本也着实难以承担。
手淘架构组临危受命,在没有任何业界先例的情况下,通过对比服务端的解决方案找到了工程拆分的解决方法——模块化。具体的方案是设计一个类似 Spring IoC 能力的轻量级容器,以保证各模块间的通信机制及接口调用标准。这样做的好处是:
各个模块通过接口的形式进行能力的对外暴露;
在集成阶段通过二进制包的方式保证了 App 整包的编译效率和各业务线的代码隔离。
这个方案极大的减少了集成的成本,也为后续手淘的快速迭代提供了保障,正因如此,这套基础架构的方案一直沿用至今。在前端急速发展的今天,端侧工程拆分也仍是大型客户端上常见的方法,同时对行业也有明显的借鉴意义。
而淘宝和天猫的移动端架构合并则是手淘架构升级之后的事情了。有一天主管找到我并提出要基于手淘的新架构帮助手机天猫进行研发体系升级,进行一些底层能力的整合,诸如网关、H5 容器等一系列端侧中间件标准。
如果说之前在手淘架构升级的过程中我的角色是执行者,那么这次的角色就有了很大的变化。一方面要修改代码,另一方面还要进行技术布道,同时还要说服一线开发和对方的主管,不单单是对技术与代码的执行者。外派的近一年时间中,我作为横向推动两个大的 BU 做架构整合的人,为了手机天猫的研发体系可以顺利升级,经历了很多不为人知的艰辛。虽然过程十分曲折,但结果是好的,最后我和同事一起完成了架构的合并。自此,手淘正式成为移动中台开始对集团输出能力。
2014 年,集团内部孵化了闲鱼这个创业项目,次年我加入了闲鱼团队。当时的闲鱼人力非常有限,很多基本的用户体验问题都没有解决,因此闲鱼先推进了客户端基础设施全面拥抱淘宝的步伐,完成了移动中台底层中间件的对接以及配套基础设施的迁移。这样在极大降低了成本的同时也使闲鱼大幅度提升了性能和稳定性。此后的两年中,闲鱼团队的规模一直比较小,正因如此,我们一直在寻找提升研发效率的方案,并进行很多不同的尝试,后面会详细说明。
再经历了不同的尝试后,闲鱼选择了 Flutter。而从“瞄”到 Flutter 到确定采用的这一过程,大概可以归为三个阶段:
1、2017 年,闲鱼开始进行对 Flutter 的技术调研,思路是如何令小团队的研发效能大幅度提升。之后闲鱼又进行了前期的验证及与 Google 的合作;
2、2018 年,开始进行实际的业务验证;
3、2019 年,我们确认团队可以驾驭这项技术后,开始大规模落地与推进 Flutter 在闲鱼的应用。
目前闲鱼线上的主链路几乎已经完全拥抱 Flutter,仅剩的一些业务也会在后续逐步进行迁移。
闲鱼在确定 Flutter 之前做过哪些尝试呢?首先来看当时的背景。
众所周知,在阿里的前端搭建体系里,Weex 是主流方案,但当时闲鱼的产品主链路需要一个高性能、不降级的方案,该场景下这一类技术无法满足闲鱼客户端的需求。因此是否适合闲鱼客户端主链路使用成为了我们对技术方案的选择的主要考虑方向。
首先我们想到了 Native。起初我们尝试在 Native 双端都设计有同样概念的编程框架(OC/Java),统一端侧的代码标准,但落地的过程中发现两套代码及两个平台的差异很难避免。
然后闲鱼又尝试了集团的 Weex 作为主链路的方案。首先,闲鱼对主链路的稳定性要求较高,由于 Weex 的动态特性,线上只有一套代码,就有可能造成老版本的兼容性问题。在产品主链路,即使有千分之一的页面由于兼容性问题也会引起舆情问题,这是业务方无法接受的。其次,Weex 的配套设施对客户端开发来讲存在一定成本。最后我们得出结论,Weex 在导购体系和活动等场景下有比较好的性能提升且集团的配套设施比较完善,但可能不太适用于主链路的业务。
在 Native 和 Weex 都不适用的情况下,闲鱼继续着探索之路,在持续探索的过程中,偶然间发现了 Flutter 这个方案。首先,从其设计理念来看,Flutter 具有更好的多端一致性,优秀的性能,高效的研发配套工具,更贴近客户端的研发体系等等。从客户端的角度出发,我们觉得这就是主链路苦寻的方案。其次,Flutter 背靠 Google 的同时又是开源产品,这点也给了我们信心。所以,闲鱼团队决定尝试 Flutter。
提到 Flutter 就自然会想到 RN,对于 RN 来说,闲鱼团队在落地过程中并没有直接使用过。我认为 RN 和 Weex 比较像,整个研发体系包括工具链上面还是更偏向前端,Weex 的主要优势是研发效率和动态性,在性能侧相比 H5 会有一些优势,因此比较适合代替现有的 H5 方案。而 Flutter 更偏向客户端的研发体系,研发效率和性能以及多端的强一致性是它的优势,基于这些优势,特别适合代替现有主链路的 Native 方案。
在引入 Flutter 前,开发间协同成本很高,导致协同效率低,例如双端开发分别需要与测试协同,双端开发之间需要协同等等,这样就导致协同成本过高。通过 Flutter 的落地,协同效率至少提升一倍。在目前的闲鱼大部分需求都是一个客户端同学独立完成的,研发侧在效率部分目前有将近 80% 左右的客户端代码是双端共享的,而且比例还在提升。
在早期我们做过一个实验,在低端机上,Flutter 开发的新详情的性能是优于老详情的性能的。由于对绘制侧的统一优化以及对 GPU 的使用,让很多开发同学可以在复杂场景写出性能不错的代码。性能侧目前还有很多工作要做,由于 Flutter 的性能基线跟 Native 不太一样,在业内严谨的性能标准还没有出现。闲鱼团队最近还在与 Google 的同学沟通,看后续是否能一起定义 Flutter 侧性能的行业标准,并帮助完善部分 Flutter 社区的性能优化工具。
质量侧 iOS 在内存消耗以及 crash 率上略高于 Android,因此闲鱼在 Engine 侧做了一些优化和定制,阿里内部有比较严格的对 crash 率的标准,可以说目前看下来质量满足上线要求。
闲鱼 App 引入 Flutter 后,在多方面都有了显著的提升,但落地过程并非一帆风顺,挑战一直伴随着整个落地过程。大致分为三个阶段:
第一个阶段主要的问题是行业内没有把 Flutter 放入已有工程体系进行开发的先例。
我们在这个过程中从工程架构、混合栈调用、打包构建、协同模式上都做了一些创新,保证了 Flutter 能融入已有闲鱼的客户端工程体系之内。后续我们推动了 Google 在 milestone 的变更,重点开始关注混合架构下的研发体验和配套设施,就是今天大家看到的闲鱼了。同时这个过程中我们沉淀出了现在团队在开源社区推出的混合栈框架 Flutter_Boost。
第二个阶段主要的问题是性能和稳定性。
在初期几版灰度上线过程中,我们使用的是 v0.5.6 版本,当时还不是特别稳定,我们经常会因为 crash 以及一些诸如手势冲突相关的 bug 修改引擎并同步给 Google 的同学。另外,在中国特有环境下的适配问题,也需要我们对引擎依赖的一些三方库进行修改以适配各个厂商特有的 ROM 带来的 bug。我们还发现音视频和图片的内存占用和 CPU 消耗上有不少问题,这个过程中我们也做了针对性的改进。
第三个阶段是 Flutter 大规模的推广的问题,真正意义上让团队每个同学都可以开发 Flutter 的代码。
而这一过程中,主要的挑战在于如何让没有经验的同学在短时间内可以写出高水平的 Flutter 代码并解决代码隔离问题。这部分我们通过对 Redux 的标准进行扩展,前后经历了三个版本的迭代讨论,最终在保持 Redux 核心三原则的基础上,扩展出了 Component 机制来解决组件复用以及代码隔离的问题,这个在多人协同的复杂业务上是非常重要的。我们也将其开源并命名为 Fish-Redux,欢迎大家使用。
闲鱼在混合架构的演进的过程中,与 Flutter 相关的改动主要有两部分,第一部分是针对于引擎本身的 Bug 进行的修复工作,随着 Flutter 的日益完善,这部分目前对大家的参考意义不太大。
而另一部分则是关于性能的优化与改进。有两个比较典型的例子:
1、第一个是混合开发的开始阶段,如果每次都创建新的 FlutterView 进行渲染,会造成内存的严重消耗,但如果全局只使用一个 FlutterView 又会造成 Native 和 Flutter 页面栈管理复杂。基于这个问题我们研发出 Flutter_Boost 的方案,既保证了全局只有一个 Engine 实例共享,又通过该框架屏蔽了 Native 和 Flutter 页面栈管理复杂的问题。
2、另一个是针对图片和视频在 Flutter 页面上渲染的优化。主要的策略是通过改造 Flutter Engine 将绘制部分的 API 做扩展,允许 Flutter Engine 接受 TextureID 的直接传递,保证 Flutter 页面在使用外接纹理绘制的过程中整个调用链路足够短,使用的内存足够少。这个方案当然也有缺陷,就是需要改 Engine,通过去年的优化,我们已经找到了类似缩短链路又不改引擎的方案,后续也会分享给大家。
后续团队的 Flutter 发展规划大体有几个方面:
我们目前团队的架构师,Fish-Redux 的作者吉丰将于今年的 GMTC 上给大家分享详细的架构设计和应用场景,欢迎大家参加。
从目前的方案上来看,Flutter 是行业内跨平台方案解决的较为彻底的一个,且背后有 Google 支持。大量的头部公司都有团队在持续投入和研究,因此我认为作为一种跨平台的解决方案,未来 Flutter 有机会成为主流的开发方式之一,另外由于它是跨终端的解决方案,未来在 PC 端和 IoT 设备端也会有一定的机会。
但 Flutter 一定不是唯一的方案,而且不可能完全代替 Native 开发。从成本和效率来说,若 Flutter 后续将生态完善起来,对于绝大部分小前台的 App 或需要多个终端进行投放的 App 来说将会是一个不错的选择,这也是大厂在推进 Flutter 上都比较积极的原因。对于大厂来讲,百花齐放是非常有必要的,大厂 all in 某一种技术是非常危险的选择。
而对于跨平台开发来说,行业内一直都在不断推陈出新,所以我觉得不太可能出现大一统局面。另外,跨平台开发框架其实是非常多的,除了 Flutter,Weex 和 ReactNative,我们去看 Qt,去看 Cocos2d-x 包括浏览器技术都是跨平台技术。技术选型跟团队架构师定义的当前问题、团队同学的知识结构、上下游的技术架构都有一定的关系,没有最好的技术,只有相应场景下具有优势的技术。
除了传统的跨平台方案以外,端计算,AR/VR,5G 下的音视频技术等等都是大家经常会提到的热点,这些讲起来有点虚了,我还是更多从一线的客户端开发人员的角度去看这个事情吧。
首先,端侧开发者永远都是最直接面向用户的,关注的重点主要集中在交互体验,渲染效果,端侧的性能等。但随着技术革新,新的设备与操作系统的出现,这几点可能会略有差异,但很多原理都是相通的。随着大前端入门门槛的降低,开发者若要保持竞争力,就需要在相应的领域里深耕,比如成为性能优化领域的专家,做一些别人无法做到的事情。
跨其他技术体系借鉴衍生了很多新的机会。比如端计算,除了现在大热的推理引擎以外,还有很多原来在服务端的技术方案可以转移到客户端。闲鱼团队目前在端侧就有实现轻量级的 CEP 引擎,或许不能像服务端 CEP 那么复杂,但是对于支撑实际的用户增长业务,完成实时的用户触达有比较好的效果。传统的 App 开发跟游戏技术的融合,也会产生一些新的想象力。
拿 Flutter 来说,今天的混合开发上面遇到的问题,在 Native 和游戏框架融合的场景下也会出现,Flutter 侧我们使用的外接纹理的方案,也是常见的混合渲染会使用的方案,所以是有很多共通之处的。因此当我们接触一个新的技术时,搞清楚底层原理,举一反三,定义出它的优势场景和问题并尝试通过其他类似领域遇到过的经验做优化,这种做法远好于学了一门新技术只做上层的开发,最后变为专业的 UI 还原工程师,要好的多。
保持好奇心,对技术追根问底的精神,日常多做总结养成好习惯,同时拓宽自己的技术视野,比如经常看看 InfoQ 的技术内容,看看别人是怎么做的,很重要。
于佳,花名宗心,闲鱼技术团队客户端负责人。2012 年应届毕业加入阿里巴巴,经历集团无线化转型的重要时期,参与过集团多款重量级 App 以及移动中间件的设计与开发,多年客户端老兵。2015 年加入闲鱼客户端团队负责端架构和团队建设,工作期间完成了基于 Flutter 混合架构的闲鱼客户端的整体架构设计,在工程体系上完善了针对 Flutter 的持续集成以及高可用体系的支撑,同时推进了闲鱼主链路业务的 Flutter 化。