潍坊市论坛

注册

 

发新话题 回复该主题

阿里跨端技术演进中的实践与思考QCo [复制链接]

1#
北京哪治疗白癜风不复发 http://m.39.net/pf/a_4688833.html
编辑

马红伟嘉宾

张舒迪年12月,在QCon全球软件开发大会(上海站)上阿里巴巴张舒迪(圣司)分享了《阿里跨端技术演进中的实践与思考》,他从跨端技术背景及演进历程、阿里跨端业务现状及思考、跨端技术方向思路演进以及对跨端技术未来展望这四个方面进行了深入的分析,从实践出发为跨端技术开发者带来更多思考方向。本文根据此次分享整理。

一般来说,跨端技术有4类场景,分别是跨设备平台(跨Web端和手机端)、跨操作系统(如跨安卓和iOS)、跨App以及跨渲染容器。本篇文章将重点围绕移动领域的跨端技术进行深入探讨。

移动跨端的重点在四类问题的解决上。首先在性能上,如何通过前端和客户端的结合,实现更优的渲染性能以及交互性能;其次在动态性上,客户端怎样能够实现更低成本的发版、甚至不发版直接动态更新代码;第三是研发效率,如何提升客户端动态调试等研发效率;最后就是一致性,如何实现一份代码的多端部署,如何保证代码在多个客户端内展示形态的一致性、杜绝兼容性问题。

1为什么说移动跨端开发需要“子集规范”?

从年苹果发布第一部iPhone手机到现在,移动互联网已经发展了十几年,国内移动端领域也经历了诸多技术演进的阶段。在-年,国内最早的移动互联网出现,这个阶段重点解决了通过一份PC代码如何在H5的浏览器里实现渲染。

年,随着移动互联网的网民数量以及设备数量的爆发式增长,移动开发步入到Hybrid技术阶段,重点解决了“互联互通”、“H5如何与Native能力齐平”以及“加载性能差”这三大问题,这一阶段的真正价值是在Hybrid加持下,前端技术成为移动互联网时代技术选型之一。随后出现的同层渲染以及Native容器化解决的核心问题都是渲染性能差。

再往下小程序开始出现,比起一个从技术角度出发的解决方案,它更像是一个通过技术来实现的产品或者说商业思路。这个阶段在跨端技术发展上是一个很核心的关键点,小程序的出现让业界意识到,我们需要一个能够帮助自己抹平不同容器技术方案的框架,阿里、腾讯、字节跳动纷纷开始布局小程序开发,由此诞生了大量的像Taro、Uni-App等一系列跨端解决方案。

再往下一个阶段走,就是现在我们聊跨端技术聊得最火热的一个话题,也是跨端技术绝对的新贵——Flutter。Flutter给前端带来的最核心思路即提供了自绘渲染技术,可以实现两端完全抹平,它弥补了像ReactNative或者Weex通过原生渲染带来的双端不一致以及兼容的成本。

以上是一个通用的跨端技术演进历程,从阿里本身来讲,以飞猪为例,在Hybrid的阶段做了Claims,在Native容器化阶段引入了Weex,在小程序化阶段引入了Rax,目前在做Web技术跟Flutter底层引擎渲染能力的对接。

通过调研阿里内部三四十个业务单元当前的业务现状,我们发现了四个主要的跨端技术问题:

技术演进诉求vs存量业务支持:端稳定性治理高风险,业务开发人员高门槛,历史包袱逐步锁死技术栈;

大前端协同需要vs差异化视角与节奏:框架与容器演进都会对上层业务研发带来影响,节奏不一致导致反复重构,业务开发与架构地位不对等;

多端多场景投放vs跨端方案割裂:跨端技术的碎片化演进反而带来了跨端场景兼容适配的高成本问题;

业务迭代效率vs技术协同成本:沟通成本、研发成本、调试成本、招聘成本、管理成本

展开来讲,第一个问题,通过调研我们发现其中有91.7%的业务单元需要把业务投放到多个客户端里去,比如说同时投放到手淘、支付宝以及飞猪团队,这种情况下会导致这些团队的95.8%的业务需要适配多个渲染容器,这会带来什么问题呢?

以一组数据来说明,阿里内部各业务单元App平均接入和维护3.15套跨端?方案,对应2.75套研发框架,这些数据产生的直接原因,就是因为我们没有办法把上一代产出的业务代码进行快速迁移,然而那部分代码也需要在线上进行持续维护,随着时间的发展以及跨端技术的演进,就导致一个App里接入的跨端技术方案越来越多、越来越冗余,继而带来“历史包袱锁死技术栈”的严重问题。

在讲第二个问题之前,前端同学需要思考一下什么时候会重构代码?一般来说是架构升级时,比如说需要用casey实现MVC的架构、React出现了单项数据流、Vue碰到了MVVM,这种时候往往会选择重构代码,在移动互联网时代却不一定是这样,容器技术演进也可能会导致前端的重构代码。举个例子,当Weex1.0版本强绑了Vue的框架,如果业务单元是React的技术栈,也需要切到Vue上,换言之Flutter也是如此,Flutter引擎上实际是强绑了Dartframework,如果要迁移就需要把代码都迁移到Dart上去,这就导致了不管是做小程序还是客户端进行同期升级,都需要进行重构。从飞猪的历程来说,发展的6年间平均一年重构一次代码。

再来看第三和第四个问题,在没有Native端的情况下,阿里各个业务单元的业务平均需要投放到3.77个App、适配2.54个渲染容器、编写2.45套代码,这反映出来一个现状,随着跨端技术的演进,不同的技术方案散列在不同的容器里,技术选型的不一致带来了大量的跨端问题,最后即使能够用资源冗余的方式去开发这么多套代码,从今天业务研发的视角,每一个问题都需要找到对应的容器协作团队,对接N个架构组,沟通协调成本之高可想而知。

那么是不是存在一个简单的解决方案,比如说提供一套统一的跨端解决方案,不再通过升级来解决这个问题?答案当然是否定的。

实际上,跨端技术解决的是H5代表的研发效率和动态性以及App代表的性能体验之间找平衡点的问题,每一个解决方案它能覆盖的宽度是有限的,如果这两个端点之间的距离太长,就没有办法提供一套方案来解决所有的问题。

那么两个端点之间是否可以拉近?从前端角度来看,性能体验会不会变得更好?在、年阿里刚开始做H5的时候,我们还相信随着硬件技术的提升手机性能问题将迎刃而解,但随着时间和技术的发展,新的问题滋生出新的性能优化。实际上,在摩尔定律下拿到的性能红利会被优先用于解决App的续航问题,此外,在流量见顶的情况下,每个业务都会倾向于以更多的功能来获取更多的用户,业务的复杂度将会对冲掉硬件升级带来的性能红利。

那从客户端的角度来讲,未来是不是有更好的研发效率和动态性来解决这个问题?答案也是否定的。研发效率问题可能能够解决,但是客户端的动态性问题却解决不了,因为想要解决这个问题就要动到OS厂商最核心的利益,苹果不会接受所有应用脱离iPhone手机市场进行自由更新。

为了解决这个问题,我们先来重新审视下整个跨端技术的研发。一个页面的开发由三个核心部分组成——研发框架、渲染容器和配套设施。研发框架更多是前端主导,意味着设计模式、MVC、MVVM单项数据流,渲染容器一般由客户端主导,代表了性能和体验,最后是代表着研发效率的配套设施。在当前的跨端技术演进中,这三者一直都是强绑的关系,一套新的跨端解决方案一定会强绑一套研发框架、一个渲染容器和基础设施。思考一下,通过一些方法能否将三者进行解耦,未来客户端的容器和框架得以实现自由更新与演进,业务也可以同时并行跑在更多的容器中。

从Web思路来想这个问题其实很好解,我们把解决方案想象成浏览器的渲染引擎,把业务想象成前端页面,实际上就是当年浏览器大战时的问题解决思路——标准化组织(W3C/WHATWG)。在标准化模式下,所有的前端业务和容器端都只需要对应一套统一标准,这样就把一个O(M*N)的复杂度问题降低到了O(M+N)。

以类似的思路去思考,阿里内部所有的渲染容器是不是都可以以统一一套子集规范的方式去做协同,跟此前Weex、ReactNative建立Web标准的借鉴思路不同,这是提前制定一套能够让各个容器去做适配的标准。在这个标准之上,我们能够去统一研发框架,支撑上层更多的业务,那这个思路能否解决前文中的几大问题?

第一个是存量业务如何迁移的问题,如果说所有的业务都是基于一套统一的标准来实现,底层容器更新后业务可以平滑迁移,就不存在历史包袱了。第二个问题是当一份代码装在多个App里时如何降低复杂度,如果说每个App的容器都提供了一套标准化的接口,一份代码自然可以无缝的装到各个容器里去。第三个是协同导致的成本问题,m个容器跟n个业务的同学需要穿插着去沟通交流,试想如果有一个标准化小组作为中间方,m个业务只需要对标准化小组提交兼容性问题即可,所有的容器也可以根据标准化小组给的排期去做统一的处理,各方都能有一个比较好的结果。

2如何实现一个高性能的标准子集?

如上图所示,基于阿里跨端技术的发展,下文将会从Web子集标准建设、WebonFlutter建设思路、统一跨端研发框架与跨端配套基础设施这四个部分入手探讨跨端技术方向演进思路。

Web子集标准建设

为什么是建Web子集标准而非全集,因为全集太复杂了。通过CanIUse和MDN,我们拉取了目前前端所有的标准规范,包括HTML的元素属性、WebAPI规范/接口、CSS属性/伪类/伪元素、标准事件等,加起来有超过个的规范要适配,并且每个规范都有非常复杂的业务逻辑,所以没有办法去实现全量。选择一个标准化子集能够解决什么问题呢?

首先是能够控制实现的成本,容器同学优先去实现关键属性,ROI一定是最高的。其次是能够优化渲染管线,经过20年的优化,Web(或浏览器内核)的渲染其实并不慢,但前后标准的无缝兼容带来的复杂度成本却很高。Flutter创始人Eric在一次采访时就曾提到,Flutter诞生的主要灵感就是在做Chromium的性能优化时,他发现把Chromium的性能渲染管线上无关紧要的渲染步骤都去掉之后,浏览器的渲染性能提升了20倍,这让他意识到一个精简的渲染管线能够铸造更好的渲染性能。

第三是兼容Web标准。既然是一个Web标准的严格子集,那也就是说业务可以无缝迁移到以WebView作为承载,这有两点好处:首先,如果渲染容器、客户端不再进行维护,业务可以随时下线,前端可以无缝降级到WebView;其次,代码将可以在端外通过浏览器或者说WebView来承载,以此实现多投。

第四是复用最佳实践。最佳实践既包含狭义的前端研发框架、前端NPM生态,也包含广义的前端人才、前端培训教程等知识储备,我们需要提供给每一个容器进行内部扩展的实例方法,比如优酷App有强管控、处理视频的需求,它可以在自有容器内做渲染规范,以此实现自有业务在端内更好的体验。

那如何实现一个高性能的标准子集?标准要如何来定制?

从阿里的实践来看,首先拉通了集团提供渲染容器和前端框架比较大的几个业务单元,如淘宝、钉钉、支付宝等,从中选择架构师同学组成一个标准化的工作小组。接下来观察整个阿里经济体内的前端业务代码仓库,将其使用到的WebAPI、CSS标签制成频度列表,根据频度高低进行选取,每个属性都会带来性能开销及研发支持成本,最后由工作小组内的资深架构师同学进行评估,以此来决定子集标准。

在标准的制定流程上阿里参考了W3C的标准规范,包括发起流程、评审流程、草稿标准、实现标准等。目前CSS已覆盖98条标准,WebAPI实现了条标准,HTML仍在梳理过程中。

WebonFlutter建设思路

制定完标准之后就要去看底层的渲染容器了。在标准容器实践上,下文将会以Web标准如何对接Flutter渲染容器这个思路进行相关阐述。

Flutter的底层实际上是一个C/C++写的引擎层,基于Skia实现了自绘渲染。上层是一个DartFramework,里面既包含了响应式能力,也包含了万物皆Widget的一个实现。

那怎么来对接?先来看一下Flutter在大前端中可能的位置,上图中第2列是FlutterNative的一个渲染链路,上层走Dart的业务代码,然后过DartFramework,最后承接在Flutter引擎上。左侧的第1列实际上是Flutter官方提供的FlutterforWeb,虽然也是对接Web,但思路不同,关键点是如何让Dart代码装在我的浏览器里。这里提供了HTMLmode跟WebGLmode这两类解决方案,前者更多使用HTML标签、CSS来进行模拟,存在很大的性能问题,已基本废弃;后者是基于WebGL加上Simbody来实现的一套训练能力,也是目前正在尝试的方案,缺点是无法做业务存在,关键的能力是当客户端实现以Dart为业务代码的页面出现无法渲染的情况时,使用这套方案可以实现无缝、快速做兜底。

第3列是前端视角,打通前端跟Flutter引擎的对接。最上层的JSFramework是我们常用的React/Vue/Rax,最底层是我们想要复用的Flutter引擎,实际上中间可能还需要一层Framework,而标准子集就在这一层的Framework跟上层前端的Framework之间来实现,以此实现一套统一的WebAPI。最右边的这一列是传统的Web渲染链路,这一块就不再赘述了。

整体的技术布局了解之后,下面讲一下具体要如何对接。

Flutter渲染与前端渲染有很多相似之处,核心就是WidgetTree、ElementTree、RenderObjectTree,其中WidgetTree和ElementTree可以理解成一个基本等同的数据结构,差异点是WidgetTree更多是配置信息,面向的是Dart业务的开发同学,是一个比较精简的数据结构,ElementTree则是框架内部真实实现的实力节点,它里面既包含了Widget跟RenderObject的实力,同时能够在WidgetTree到ElementTree之间实现diff算法。

RenderObjectTree类似前端的render区,作用是实现具体的渲染。在RenderObjectTree这层会进行Layout(布局)与Paint(绘制)的动作,形成一个LayerTree扔给Skia库,即底层Flutter引擎,最终渲染出页面。

试想一下,前端代码对接到这三棵“树”的任意一棵,就能够实现前端跟Flutter引擎的对接,从这个思路出发业界已经有很多解决方案,下面简单列举四个:

思路1:遵循Flutter思路,对接WidgetTree

遵循Flutter思路,不同的是用JS来实现整体WidgetTree,可以理解为原本由Dart写的业务代码在JS里进行了1:1的实现。这一思路有三点优势,首先是依托JSC/V8使Flutter具备了动态化能力,其次其改造成本相对可控,第三是可复用部分JS生态(工具库)。

同时,这个思路也存在三点需要注意的问题,首先,在JS里去写WidgetTree不是在写前端,无法对接前端的标准,因此也丧失了Web视图的开发特性;其次,JS部分逻辑较重,性能相对也会较差,第三,实际在JSCall对接过程中,会出现JS到C++再到Dart再到C++的一个通信流程,通信开销成本也会相对较高。在具体实践中,腾讯的MXFlutter就是沿用了这一思路,更多目的是为了解决Native的动态性问题。

思路2:遵循Web思路,对接WidgetTree

这一思路同样是对接WidgetTree这一层,不同的是在DartFramework里去实现Dormitory跟CSSOM,然后再把Dormitory跟CSSOM解析成一个WidgetTree。以一个商品的背景图片为例,它可能由Container、Decorationimage、Networkimage三个Widget组成,通过Widget去匹配CSS和HTML标签的方式,把Dormitory映射成WidgetTree。

这一方案存在三点优势,它既能够实现跟Web生态的对接,改造成本也相对可控,同时因为WidgetTree本来就是Flutter暴露给上层开发者的数据结构,不会轻易去改动API,稳定性比较好,未来Flutter升级也可以做平滑的迁移。

但是它也有两点问题,首先是会出现两次diff消耗,前端的研发框架基本上都会有一层diff,但在WidgetTree到ElementTree的转化过程中还会产生一层diff,这两层diff实际上是冗余的,将会导致一定的性能开销。其次,Widgets的组合完全没有CSS灵活,众所周知,前端标准里并不限制CSS组合,怎样都不会影响渲染,但在Flutter设计里有一些Widget是明确不能组合的,这就导致一些CSS属性的组合不可能通过拼装Widgets的方式实现,存在一个完备性问题。

这套解决方案在业界使用相对比较广泛,阿里内部有飞猪的Flugy方案,外部有字节的ORYX方案,还有一套是

分享 转发
TOP
发新话题 回复该主题