飞猪后台运行原理(飞猪Flutter技术演进及业务改造实践)
飞猪后台运行原理(飞猪Flutter技术演进及业务改造实践)随着业务需求迭代,App 变的越来越臃肿,技术改造带来研发效率/性能体验提升的同时,也增加了维护的复杂度。旅行业务域逻辑非常复杂,对有些大的需求,在需求评审上经常需要反复对焦,但在实际交付上还是存在两端开发理解偏差引发的线上问题,我们印象中就发生过几起机票交易链路因为漏测引发线上故障。但受限于应用渠道更新效率,在 Android 端,新版本升级覆盖慢的问题长期存在,分发效率低的问题不得不提上日程。因此我们开始投入做相关技术的改造:当时 Android 手机山寨机横行,主流厂商各种定制 ROM,兼容性、适配问题搞的焦头烂额,所以我们主要精力在解决稳定性问题,同时夯实基础功能,支撑业务稳定上线。这个阶段 Android、iOS 得到独立发展,只在页面跳转协议和事件总线协议上保持一致。随着支付宝和手淘快速成长为航母级应用,无线基础设施趋于完善,飞猪也搭上了顺风车,支撑了业务快速迭代。我们在端研发
随着 Flutter 生态逐渐完善,阿里集团在移动领域已经大刀阔斧地开始了新方向的基建和研发模式的改革。飞猪今年担任着集团技术委员会技术创新的一个重要任务,探索大前端模式下 Flutter 的效能极限。
本文整理自飞猪高级无线开发专家旅鹤在InfoQ技术公开课直播栏目所做的分享,结合飞猪近半年来在 Flutter 技术实践中的突破和探索,重点介绍跨端标准容器建设、组件库的沉淀、性能优化的经验,以及面对存量业务做 Flutter 改造的新思路。
飞猪客户端架构演进 架构 1.0:从飞猪客户端架构架构发展历程看客户端技术的发展。
2013 年,随着阿里巴巴 All in Mobile,阿里旅行也有了独立的 App。最初业务比较简单,PC 页面简单改造成 H5 运行在 App 端,以信息展示为主,Native 业务承接机票交易,整个 App 的构建发布不成体系。发渠道包都在单台 Jenkis 服务器上现拉源码编译,测试验证后直接发应用市场,没有体系化的研发构建平台,也缺乏完善的灰度机制。
当时 Android 手机山寨机横行,主流厂商各种定制 ROM,兼容性、适配问题搞的焦头烂额,所以我们主要精力在解决稳定性问题,同时夯实基础功能,支撑业务稳定上线。这个阶段 Android、iOS 得到独立发展,只在页面跳转协议和事件总线协议上保持一致。
架构 2.0:随着支付宝和手淘快速成长为航母级应用,无线基础设施趋于完善,飞猪也搭上了顺风车,支撑了业务快速迭代。
我们在端研发框架上对接了成熟的支付宝 Mpass/手淘 Atlas 框架,也接入了伙伴/MTL 研发协作平台,有力保障了多人并行开发,业务迭代最快做到按周交付。同时基本建立了完善的灰度发布体系,Crash 率从千分之 5 降低到万分之 2,其他基础功能(网路库/图片库/Push 通道)也逐步从自建方案回归到依赖中台基础能力,研发效率大幅提升。
但受限于应用渠道更新效率,在 Android 端,新版本升级覆盖慢的问题长期存在,分发效率低的问题不得不提上日程。因此我们开始投入做相关技术的改造:
- 看 Native 自身,各种动态化技术层出不穷,基本上头部的厂商都有自己的方案,基本的思路都大同小异。飞猪也积极参与了支付宝的动态更新技术共建,后面手淘也开源了 Atlas 框架,很多公司还在使用。从我自身体感看,因为是 hook 技术,对系统的稳定性挑战还是比较大的,在非航母级应用上,动态发布能力主要还是修 bug,真正推业务的情况并不多见。随着新版 Android 系统加强对隐藏方法的限制,再加上 hook 框架享受不到系统层的一些性能优化,这条路基本走到头了。为了进一步提升业务对 Native 动态性需求,飞猪也与手淘共建了 DX 动态模板容器,通过统一动态模板 DSL,建立了完善的搭建平台和模板管理仓库,现已广泛应用于业务中,但只限于简单业务逻辑。
- 看前端,H5 因为开发效率高,且具备完备的动态化能力,开始大量承接业务。我们也成立了独角兽跨栈小组,通过离线包、预加载、预渲染技术来优化 H5 性能,后面在 Android 端内置 U4 WebView/iOS 升级到 WKWebView,整体的性能和兼容性有了更大的提升。不满足于现状的技术团队,通过把 Web 与原生渲染能力结合(Weex/Rn),实现了前端性能接近原生的体验。
随着业务需求迭代,App 变的越来越臃肿,技术改造带来研发效率/性能体验提升的同时,也增加了维护的复杂度。旅行业务域逻辑非常复杂,对有些大的需求,在需求评审上经常需要反复对焦,但在实际交付上还是存在两端开发理解偏差引发的线上问题,我们印象中就发生过几起机票交易链路因为漏测引发线上故障。
技术侧复杂度增加也容易出现纰漏,一次 H5 离线包在 Android 端由于没加好保护导致线上大面积客诉,经过超过 24 小时排查才最终定位到问题。很多基础线的改造也很难评估具体影响面,给稳定性带来新的挑战。
具体到业务开发遇到的挑战,和端技术相关的包括 UI 渲染问题,系统兼容性问题,还有些动态性、研发效率问题从目前的技术方案上看,因为两端的具体实现依赖原生能力,系统平台层的差异性带来的业务表现差异问题,很大程度是需要更底层的技术方案来解决的。
自渲染和 Flutter自渲染技术并不是新概念,从很早以前的 QT 到现在的 Flutter,它的优势在于具备非常好的跨端能力,能解决长尾问题。对比原生渲染方案,具体到 WebView,在 Android 上版本碎片化问题非常多,给前端适配带来很大的难度;看 Native 原生业务,之前提到的由于沟通上理解上偏差出现交付上差异,最终由测试兜底也是不可持续的。
Flutter 在全球超过 200 万开发者,月活开发者 50 万。据 GitHub 统计,Flutter 是全球第二快增长的开源项目。Google Play 发布了超过 11 万 Flutter 应用,国内大厂基本都使用 Flutter,阿里巴巴多个 BU 也积极参与生态建设。对比 QT,同时自渲染技术,它在开发体验上和生态建设上都有很大优势,也因此收获了很多开发者。
对比其他的技术方案,Flutter 除了因为政策原因舍弃了动态性,其他方面表现都非常优秀。
飞猪客户端架构 3.02019 年,飞猪开始在商家版 EBK 改造项目上试水 Flutter,在研发效率和用户体验上都有不错的收益。随着 Flutter 混合技术栈的完善,我们决定 2020 年 6 月份在飞猪 C 端做 Flutter 专项改造,寄希望于推动飞猪技术架构升级,完成客户端技术体系向大前端技术体系演进,建立统一移动端研发模式,摸高 Flutter 的效能极限。具体到几个核心项目,包括跨端标准容器、研发工程体系、组件化建设、自动化测试领域寻求突破,并沉淀最佳实践。
通过引入 Flutter 技术,上层业务开发可以无缝对接 Android、iOS,中间的 Flutter 容器非常薄,平台差异性对接工作主要通过 Flutter SDK 实现,解决 Native 体验的一致性问题,提升可维护性。
阿里 Flutter 建设的现状正如官方描述所说,Flutter 是 UI Tool Kit,最初定位是服务全新 App 开发,在实际中很多功能满足不了存量业务开发需求。阿里 Flutter 基础建设主要围绕完善 Flutter 混合技术栈开发、高可用监控预警、研发支撑平台,以及最核心的性能优化建设。
目前阿里已有 10 BU 积极参与到 Flutter 业务开发,核心几个 BU 也是生态的积极贡献者,与 Flutter 团队保持密切的链接,因此 Flutter 官方也在积极完善对混合栈的支持,期望未来 Flutter 在这方面有更优异的表现。
2020 年伊始,飞猪 Android 端在机票航变和酒店相关简单业务做了 Flutter 试水,我们对接的首个版本是 1.12,稳定性和性能都符合预期。对于这种简单的纯展示页面,是非常适合用 Flutter 的。像机票航班详情页最近的新需求完全靠 1 人开发就可以。
经过简单业务试点,稳定性和性能都符合预期,也建立了对 Flutter 信任感,后面开始在比较复杂的业务上试点,用到了非常多的基础功能,包括视频播放功能对接,为后面全面开始 Flutter 建设打好基础。
从性能表现上看,低端机性能上加载耗时优于 iOS,帧率接近原生,CPU 负载、内存消耗接近原生,整体的稳定性非常不错。
纯 Flutter 业务试点同时,我们也在做 Web on Flutter 方面的技术攻关,目前在飞猪 9.6.9 版本上线了 3 个试点业务,性能表现优秀,H5 代码基本不用改造就能运行在 Flutter 上,后面在专项上会具体介绍技术细节。
飞猪 Flutter 技术沉淀经过半年的沉淀,我们在相关关键领域有了比较大的突破,希望和大家深入交流,分享不一样的 Flutter 实践经验。
Web on Flutter背景介绍我们通过 Android 端内置 UC WebView,基本解决了 WebView 碎片化问题,适配压力骤减。但在 2020 年双 11 会场页面开发中,还是存在一些 UI 展示低级缺陷问题,主要表现在字体显示上,这些问题虽然不严重,但比较影响用户体验,拉低我们对品质的追求。另一方面,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 不用改造就能运行起来。
渲染指令说明:对应的是 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 渲染表现对齐。
上图是在实现 Position 属性绘制的元素层次结构关系图。
结果展示 Flutter 优化实践 性能优化1. 圆角对性能影响
2. 减少重绘
3. 开启 SurfaceView
组件库建设我们自己计算 1dp 到底应该占设备屏幕上的多少像素,而不是使用系统预设好的值。将计算好的这个比例设置到项目的环境中,这样就可以以很低的成本达到按照实际分辨率适配出同样的视觉效果,在开发时也会免去很多换算单位的麻烦。最近我们还支持 Flutter 字体大小不受系统设置影响的方案。UI 一致性完美支持后,UI 自动化测试也能从中受益,只需要维护一套测试代码,就可以在多设备下运行,降低适配成本。
我们开源了 11 个 UI 组件,都是比较贴合业务场景的组件,上面列举了几个在社区受欢迎的组件。最近团队主要精力在开发 Flugy,后面会加强组件库维护更新工作。
XRay 自动化测试框架我们做自动化测试框架的初衷,是要解决端渲染技术多样性带来的可测性难题。因为原有的测试框架是基于原生方案的,满足不了未来测试要求。所以我们想设计面向未来的自动化测试方案。
我们从手动测试操作过程找到灵感,基于图像处理算法定位元素,通过驱动能力操控元素,然后再通过图像处理算法验证结果。整个链路是零侵入的,所以具备非常强的适应性。目前能非常好地支持 Native、WebView、Flutter、小程序、动态模板等技术,并且也可以支持鸿蒙,以及 FusionOS。
为了提升测试代码开发效率,我们基于 IntelliJ 开发了插件,只要熟悉 Python 就能熟练掌握,2020 年为了方便外包写 case,还提供了录制回放功能。
这是整个测试 case 编辑、case 执行情况的流程图,最核心的部分是元素定位,用到了我们自研的图像结构相识度识别算法,以及阿里达摩院的读光 OCR 能力,在元素驱动技术上 Android 使用 adb,iOS 使用自研的方案 Dwarf。
列举酒店列表和首页测试执行情况,通过我们自研的图像算法可识别出列表,测试代码编辑只需要 recognize_list,然后操作具体的 item 就可以。实际在 XRay 执行过程看是基于酒店列表生成了个类似 Dom Tree 的结构,在运行过程中操作的不是简单的坐标映射,而是对应到具体的元素模块中,提升了可维护性和执行成功率。
除了 XRay 框架的建设,在实际业务使用场景上还涉及到设备调度,我们基于手机墙调度系统做简单改造支持自动化测试设备调度,支持按系统、品牌、机型维度调配。广泛应用于持续交付的验证,为无线质量保驾护航,目前也在和无线测试团队做智能 monkey。
Can I Use:为了能更好地支持容器同学和前端同学的研发,便于一目了然地了解各标准在容器层落地支持的情况,前端提供了 CanIUse 检索平台,查看标准在各容器侧的支持情况,通过在真机上自动运行单测脚本做对比测试,确定容器的支持情况。
面临挑战与未来规划直播视频回放:https://www.infoq.cn/video/6Ftkjr2A006v8iew6ifl
延伸阅读;
火山引擎在机器写作和机器翻译方面的最新进展-InfoQ关注我并转发此篇文章,即可获得学习资料~若想了解更多,也可移步InfoQ官网,获取InfoQ最新资讯~