本文作者飞猪@旅鹤,负责飞猪安卓端 架构和大交通业务团队,在客户端工程化体系、性能优化、稳定性治理、音视频、移动安全等方向有丰富经验;
当前飞猪技术部 2022 届校园招聘开始啦,欢迎前往 掘金详细了解。
本文结合飞猪近半年来在 Flutter 技术实践中的突破和探索,重点介绍跨端标准容器建设、组件库的沉淀、性能优化的经验,以及面对存量业务做 Flutter 改造的新思路。
一、飞猪客户端架构演进
发展历程
客户端架构 1.0
2013 年,随着阿里巴巴 All in Mobile,阿里旅行也有了独立的 App。最初业务比较简单,PC 页面简单改造成 H5 运行在 App 端,以信息展示为主,Native 业务承接机票交易,整个 App 的构建发布不成体系。发渠道包都在单台 Jenkis 服务器上现拉源码编译,测试验证后直接发应用市场,没有体系化的研发构建平台,也缺乏完善的灰度机制。
当时 Android 手机山寨机横行,主流厂商各种定制 ROM,兼容性、适配问题搞的焦头烂额,所以我们主要精力在解决稳定性问题,同时夯实基础功能,支撑业务稳定上线。这个阶段 Android、iOS 得到独立发展,只在页面跳转协议和事件总线协议上保持一致。
客户端架构2.0
随着支付宝和手淘快速成长为航母级应用,无线基础设施趋于完善,飞猪也搭上了顺风车,支撑了业务快速迭代。我们在端研发框架上对接过无线研发协作平台,有力保障了多人并行开发,业务迭代最快做到按周交付。同时基本建立了完善的灰度发布体系,Crash 率从千分之 5 降低到万分之 2,其它基础功能(网路库/图片库/Push 通道)也逐步从自建方案回归到依赖中台基础能力,研发效率大幅提升。
但受限于应用渠道更新效率,在 Android 端,新版本升级覆盖慢的问题长期存在,分发效率低的问题不得不提上日程。因此我们开始投入做相关技术的改造:
- 看 Native 自身,各种动态化技术层出不穷。飞猪也积极参与了动态更新技术共建,回归头看,在非航母级应用上,动态发布能力主要还是修 bug,真正推业务的情况并不多见。为了进一步提升业务对 Native 动态性需求,飞猪也与手淘共建了 DX 动态模板容器,通过统一动态模板 DSL,建立了完善的搭建平台和模板管理仓库,现已广泛应用于业务中,但只限于简单业务逻辑。
- 看前端,H5 因为开发效率高,且具备完备的动态化能力,开始大量承接业务。我们很早就成立了跨栈虚拟小组,通过离线包、预加载、预渲染、新SSR技术来优化 H5 性能。飞猪前端同学也积极参与了Weex业务落地,当时国际机票核心主链路也由Weex承接,实现接近原生的体验。
挑战与新机会
随着业务需求迭代,App 变的越来越臃肿,技术改造带来研发效率/性能体验提升的同时,也增加了维护的复杂度。旅行业务域逻辑非常复杂,对有些大的需求,在需求评审上经常需要反复对焦,但在实际交付上还是存在两端开发理解偏差引发的线上问题,我们印象中就发生过几起机票交易链路因为漏测引发线上故障。 技术侧复杂度增加也容易出现纰漏,一次 H5 离线包在 Android 端由于没加好保护导致线上大面积客诉,经过超过 24 小时排查才最终定位到问题。很多基础线的改造也很难评估具体影响面,给稳定性带来新的挑战。从目前的技术方案上看,因为两端的具体实现依赖原生能力,系统平台层的差异性带来的业务表现差异问题,很大程度是需要更底层的技术方案来解决的。
自渲染与Flutter
自渲染技术并不是新概念,从很早以前的 QT 到现在的 Flutter,它的优势在于具备非常好的跨端能力,能解决长尾问题。对比原生渲染方案,具体到 WebView,在 Android 上版本碎片化问题非常多,给前端适配带来很大的难度,我们利用12MB包大小内置U4来解决在WebView在Android端的适配问题;看 Native 原生业务,之前提到的由于沟通上理解上偏差出现交付上差异,最终由测试兜底也是不可持续的。
Flutter 在全球超过 200 万开发者,月活开发者 50 万。据 GitHub 统计,Flutter 是全球第二快增长的开源项目。Google Play 发布了超过 11 万 Flutter 应用,国内大厂基本都使用 Flutter,集团内非常多 BU 也积极参与生态建设。对比 QT,同是自渲染技术,它在开发体验上和生态建设上都有很大优势,也因此收获了很多开发者。对比其他的技术方案,Flutter 除了因为政策原因舍弃了动态性,其他方面表现都非常优秀。
客户端架构Flutter演进方向
2019 年,飞猪开始在商家版 EBK 改造项目上试水 Flutter,在研发效率和用户体验上都有不错的收益。随着 Flutter 混合技术栈的完善,我们决定 2020 年 6 月份在飞猪 C 端做 Flutter 专项改造,寄希望于推动飞猪技术架构升级,完成客户端技术体系向大前端技术体系演进,建立统一移动端研发模式,摸高 Flutter 的效能极限。具体到几个核心项目,包括跨端标准容器、研发工程体系、组件化建设、自动化测试领域寻求突破,并沉淀最佳实践。 通过引入 Flutter 技术,上层业务开发可以无缝对接 Android、iOS,中间的 Flutter 容器非常薄,平台差异性对接工作主要通过 Flutter SDK 实现,解决 Native 体验的一致性问题,提升可维护性。 正如官方描述所说,Flutter 是 UI Tool Kit,最初定位是服务全新 App 开发,在实际中很多功能满足不了存量业务开发需求。集团基础建设主要围绕完善 Flutter 混合技术栈开发、高可用监控预警、研发支撑平台,以及最核心的性能优化建设,飞猪主要聚焦在业务层面的落地,用好Flutter新技术能力。
二、飞猪 Flutter 技术沉淀
经过半年的沉淀,我们在相关关键领域有了比较大的突破,希望和大家深入交流,分享不一样的 Flutter 实践经验。
1. Web on Flutter
背景介绍
Android 端内置 UC WebView,基本解决了 WebView 碎片化问题,适配压力骤减。但在 2020 年双 11 会场页面开发中,还是存在一些 UI 展示低级缺陷问题,主要表现在字体显示上(上面暴露的问题,近期UC WebView 4.0已经解决),这些问题虽然不严重,但比较影响用户体验,拉低我们对品质的追求。另一方面,Weex 1.0 依赖原生组件,两端渲染一致性问题非常难解决,实现原理类似的 RN 也遇到类似的挑战。 随着 Flutter 技术热度兴起,前端同学在探索 Web on Flutter 的可能,对比之前 Weex 是 Web on Native。底层渲染技术用 Flutter 替换后,会带来性能上新的机会,一致性的表现将能更完美地解决。
技术选型与落地
- 方案①:JS 封装与 Widget API 接近的接口,实现 Flutter 动态能力,该方案脱离了前端生态;
- 方案②:Web 与 Widget 对接,ROI 高,完成映射后性能基本接近 Flutter 原生,性能方面也有很高的想象空间;
- 方案③:Web 与 RenderObject 对接,需要自己写布局相关逻辑,性能上没想象的高,扩展视频、地图组件有一定改造成本,Flutter 版本升级有一定成本;
- 方案④:用 C++重写 Framework,抛弃 Dart Rumtime,开发成本非常高。
Flugy 由飞猪自研,支持前端跨端标准子集,目前在技术层面也和其他团队共建中。 从架构图看,Flugy 是一个 Widget Plugin,可以和 Flutter 生态打通,主要代码用 Dart 开发,对接的是 Flutter Widget(不需要定制引擎),从前端视角看就是个精简的 WebView, H5 不用改造就能运行起来,近期了解到高德开发了Flutter版地图,作为扩展能力接入Flugy也是非常方便的。 渲染指令说明:对应的是 DOM API 的一些操作指令,包括 createElement,AppendChildren,removeChildren,replaceChildren,insertBefore...
Flugy 标准容器关键突破口
基于之前的架构设计,容器要做的核心事情是要打通 Web 和 Flutter,虽然 Flutter 核心开发来自 Chrome 维护者,但从设计理念上更接近原生。前端开发只需要通过属性赋值设置想要的结果,不用具体到要用什么组件来实现,WebView 把这部分逻辑都消化掉,而原生开发需要关注更多的细节,组件的封装和组织关系,这也是 Native 页面性能更高,但开发效率不如前端的原因。
回到标准容器项目上看,需要 Flugy 容器把 WebView 之前处理的页面布局、样式的逻辑实现好。与 WebView 不同之处在于我们对接的是 W3C 的子集,没有历史包袱。这里列举了几个属性映射有代表性难题。比如 Overflow:display 和 Position。
- Position 属性的实现:
- 通常原生开发是从整个页面大的结构入手,然后具体到各子模块的拆分。在 Web on Flutter 上,JS 是在运行过程中创建 DOM,是以流的方式发送渲染指令到容器侧,是个实时解析的过程,我们在解析过程中构建 Widget 对象。但前端的定位属性非常灵活会有脱离常规流情况,这就需要反复修改之前构建的 Widget 对象,也是技术突破口。
- 对元素状态做了一层抽象把问题简化,首先在解析添加元素 A 时,遇到 position 为sticky/fixed/absolute 元素时,在创建元素 A 的时候会同时创建一个它的副本 A'(副本会被标记为逃逸元素,即 escape 为 true),随后 A 元素原身将会留在其父容器中占位(不做渲染展示),继续走正常的布局流,以确保其它同级元素位置的正确性。而它的副本 A'将会通过内部的通信机制向上寻找能够接纳它的更高层级的父级容器,然后在目标父级容器中按照 web 的规则做渲染展示。
- 得益于 Flutter 有状态 Widget 的设计和 Flugy 内部有效的通信机制,每一步操作都被控制到了一个最小范围内,仅进行局部更新,而不会触发整颗树的更新,使得性能得到保证。
- 其他很多属性在 Flutter 都有具体实现,实现的复杂度基本可控。对齐标准是个比较细致的工作,也容易出现理解不到位导致实现效果不符合标准要求,前端同学正在牵头做标准容器单测事项,通过 XRay 实现各标准容器与 H5 渲染表现对齐。
结果展示
Flugy纯渲染性能 | Flugy完整业务展示 | |
---|---|---|
描述 | 直接加载(把 JS Bundle执行过程的DOM/CSS指令缓存到本地)缓存的指令,图片加载走线上请求,页面展示直出 | 加载缓存到本地的JS Bundle,看不到白屏,直接显示蓝色背景图片,网络加载后更新数据,Tab切换和滑动都非常流畅 |
结果展示 | 视频点击查看>>> | 视频点击查看>>> |
2. 单工程构建优化
3. 组件库建设
多屏适配组件
我们自己计算 1dp 到底应该占设备屏幕上的多少像素,而不是使用系统预设好的值。将计算好的这个比例设置到项目的环境中,这样就可以以很低的成本达到按照实际分辨率适配出同样的视觉效果,在开发时也会免去很多换算单位的麻烦。最近我们还支持 Flutter 字体大小不受系统设置影响的方案。UI 一致性完美支持后,UI 自动化测试也能从中受益,只需要维护一套测试代码,就可以在多设备下运行,降低适配成本。
开源UI通用组件
我们开源了 11 个 UI 组件(链接地址),都是比较贴合业务场景的组件,上面列举了几个在社区受欢迎的组件。最近团队主要精力在开发 Flugy,后面会加强组件库维护更新工作。
4. XRay 自动化测试框架
我们做自动化测试框架的初衷,是要解决端渲染技术多样性带来的可测性难题。因为原有的测试框架是基于原生方案的,满足不了未来测试要求。所以我们想设计面向未来的自动化测试方案。 我们从手动测试操作过程找到灵感,基于图像处理算法定位元素,通过驱动能力操控元素,然后再通过图像处理算法验证结果。整个链路是零侵入的,所以具备非常强的适应性。目前能非常好地支持 Native、WebView、Flutter、小程序、动态模板等技术,并且也可以支持鸿蒙,以及 FusionOS。 为了提升测试代码开发效率,我们基于 IntelliJ 开发了插件,只要熟悉 Python 就能熟练掌握,2020 年为了方便外包写 case,还提供了录制回放功能。 这是整个测试 case 编辑、case 执行情况的流程图,最核心的部分是元素定位,用到了我们自研的图像结构相识度识别算法,以及阿里达摩院的读光 OCR 能力,在元素驱动技术上 Android 使用 adb,iOS 使用自研的方案 Dwarf。 列举酒店列表和首页测试执行情况,通过我们自研的图像算法可识别出列表,测试代码编辑只需要 recognize_list,然后操作具体的 item 就可以。实际在 XRay 执行过程看是基于酒店列表生成了个类似 Dom Tree 的结构,在运行过程中操作的不是简单的坐标映射,而是对应到具体的元素模块中,提升了可维护性和执行成功率。 除了 XRay 框架的建设,在实际业务使用场景上还涉及到设备调度,我们基于手机墙调度系统做简单改造支持自动化测试设备调度,支持按系统、品牌、机型维度调配。广泛应用于持续交付的验证,为无线质量保驾护航,目前也在和无线测试团队做智能 monkey(已经覆盖80+页面的自动巡检)。 Can I Use:为了能更好地支持容器同学和前端同学的研发,便于一目了然地了解各标准在容器层落地支持的情况,前端提供了 CanIUse 检索平台,查看标准在各容器侧的支持情况,通过在真机上自动运行单测脚本做对比测试,确定容器的支持情况。
三、Flutter性能优化
1. 圆角对性能影响
2. 合理减少重绘
3. 开启 SurfaceView
注:SurfaceView开启对低端手机性能提升明显,高端手机上优势不大。
四、业务落地
- 2020 年伊始,飞猪 Android 端在机票航变和酒店相关简单业务做了 Flutter 试水,我们对接的首个版本是 1.12,稳定性和性能都符合预期。对于这种简单的纯展示页面,是非常适合用 Flutter 的。像机票航班详情页最近的新需求完全靠 1 人开发就可以。
- 经过简单业务试点,稳定性和性能都符合预期,也建立了对 Flutter 信任感,后面开始在比较复杂的业务上试点,用到了非常多的基础功能,包括视频播放功能对接,为后面全面开始 Flutter 建设打好基础。
结果:从性能表现上看,低端机性能上加载耗时优于 iOS,帧率接近原生,CPU 负载、内存消耗接近原生,整体的稳定性非常不错。
- 纯 Flutter 业务试点同时,我们也在做 Web on Flutter 业务试点,H5 代码基本不用改造就能运行在 Flutter 上。3月底的版本我们有次大的性能优化,同时也会上线iOS。
五、面临挑战与未来规划
近期更新:飞猪9.7.2.105版本升级到Hummer Flutter 1.22 版本,在poi详情页,平均帧率从 53 提升到 56。 3月4号Flutter 2.0 重要发布,亮点很多,优化多引擎实例支持,对混合栈应用开发更友好。