隐风 阿里巴巴技术专家
本文是2018年Weex Conf 中议题《Weex技术演进》的内容文档整理,主要给大家介绍过去一年里Weex业务规模不断扩大,业务复杂度不断上升,给Weex带来了哪些技术挑战,以及Weex在技术架构和设计上做了哪些升级来应对这些挑战。
业务规模
2017年,Weex的业务规模呈现了爆发式的增长,不但在比较擅长的会场业务弥补了最后1%的页面,也就是一些富交互、富视觉的页面,让店铺承接页也全部Weex化,同时更重要的是支持了很多常态化的业务和产品。
技术挑战
业务规模的不断扩大,业务复杂度的不断上升,给Weex的技术到底带来了什么样的挑战?
- 交互体验问题,我们在2016年做的更多的还是纯展示的活动页面,而2017年要支持的这些常态化的业务和产品,很多原来就是native的,对于动画、手势等交互体验要求比较高,一些native非常常见的交互效果,在Weex上实现起来挑战非常大。
- 内存压力,越来越多的业务Weex化之后,用户整个点击路径都变成了Weex,举个例子,2017年的双11预售期,当我们打开手淘,点击狂欢城H5,再点店铺承接页Weex,点主会场Weex,点分会场还是Weex,然后分会场、分会场不断地逛,链路中每个页面都非常复杂,其中大部分页面是很长的列表页,包含了大量的楼层、视频、动效等复杂组件,整体内存占用非常高,这就导致整个APP的crash率上升非常明显。2017年我们升级了列表渲染架构,针对内存问题重点优化了一次。
- 性能稳定性,如何把V8引擎升级到JSC来提升JS执行性能,如何能让JSC在独立进程运行而不影响主进程稳定性,如何解决JS上下文全局污染问题,如何替换Yoga以解决License问题而又不影响布局性能等等
- WeexCore,如何在架构上实现跨平台和高性能,将一些通用的逻辑下沉到C++
演进过程
纵观Weex一开始诞生到现在,它的技术演进过程可以总结为从具象到抽象不断探索的过程。
- 特殊组件期:最开始Weex亮相的时候是2015年双11,当时只支持了一个会场页面。那时候我们做了非常多的特殊组件,比如tabbar组件,倒计时就做一个countdown组件,跑马灯就做一个marquee组件。当时的Weex可以理解为是一个非常具象的框架,由一大堆特殊组件拼接而成,最后达到了动态化的目的。
- 偏展示期:到了2016年双11,我们要支持整个双11所有会场,几千张页面,继续使用2015年的方式肯定是不行的。于是我们做了很多偏展示的基础样式,比如支持text的基础样式,div的基础样式,最基本的动画模块,存储等一些基础且细粒度的模块等,以及一个相对来说比较高性能的列表组件。
- 富交互期:到了2017年中,我们要支持一些固定的产品,一些核心业务,偏展示又满足不了需求了,于是我们重点去打富交互,支持富文本、手势、布局动画以及一些通用的交互组件。
- 国际化:到了11、12月时又来了一波挑战,我们要支持Lazada等国际化的APP,于是整体做了一次国际化,比如从右到左的文字排布,比如无障碍、多语言、多端差异化等。
- 标准化:到了2018年,我们要做的是标准化,尽可能抹平跨端在组件和架构上的不一致性,遵循标准并推进标准,让Weex变得更加通用。
交互体验升级
什么是好的交互体验?
我们做交互体验首先要问的一个问题,到底什么是好的交互体验? 2017年之前,我们会关注几个特性,第一,加载速度很快,希望用户从点击页面到最后整个首屏渲染完,在一秒内完成,也就是我们经常说的秒开。第二是滚动流畅,用指标描述就是60FPS。到了2017年要求就更高了,当时交互同学给了我们一些例子,看一下在他们眼中好的交互体验是什么样的。
第一个例子,iOS原生的mail app,随着左右滑动的手势,两边的view可以灵活地做动画,这个在业务上非常常见。比如左滑删除,右滑关注之类的功能。
第二个例子,有好货,它是由多个tab组成,每个tab都是很长的列表。下面可以通过手势操作列表,同时tab可以灵活的进行切换。
最后还有一个微博的例子,也就是我们经常所说的滚动视差体验,随着列表下拉,图片变大或者变模糊,随着列表向上滚动,图片以不同速率移动。一句话概括这些例子对Weex交互体验的要求,那就是随着用户的操作,Weex页面需要持续又快速的进行响应。
需要遵循哪些原则?
- 最大化利用native:我们做交互演进过程中,一直在思考Weex最大的价值是什么。我们认为,Weex 最大的优势和价值在于其native 的能力,我们要最大化利用native 的特性,而不是去模仿它。怎么去理解?举个例子,Native 的列表组件,有着非常好的滚动流畅度、内存控制能力、View复用能力,我们希望把它的所有能力都能透出给前端JS,而不是想着在 JS 侧重新再模仿一个长列表组件。
- 从下至上:首先竭力解决最底层的东西,比如通信的消耗、手势、基本的动画、文字的基本样式。如果这些问题解决起来成本太高,我们会暂时先往上,封装一些通用的组件、模块级的解决方案,来解决大部分应用场景。再往上就是封装业务组件,把native 的一些复杂的能力整块整块地丢给JS。
- 允许多端差异化:允许Android遵循MD的设计规范,iOS遵循iOS的一些设计规范,一些复杂的交互和视觉,运行Web进行体验上的降级
如何解决通信时延的问题?
在交互体验升级过程中,最大的挑战是JS和native的通信时延问题,举刚才滚动视差的例子,随着滚动,每一帧native发起滚动事件,JS监听做一些处理,计算出要发生多少translate位移,重新告诉native,native再做动画,整个循环需要在一帧也就是16毫秒内完成。现实情况下非常难做到,大部分情况下这个流程下来至少都是50毫秒。当时我们用这种方式也做了一个Demo,滚动视差的效果,大家会发现抖动非常明显,和持续快速响应的目标相去甚远。
优化思路也很简单,两个优化思路,第一是减少通信次数,希望一次通信把这些事情都搞定。这里我们用了Expression Binding模块和parallax组件,他们的原理类似,就是将手势事件和View属性变更的关系提前绑定到native, 当手势事件发生时,native根据这个关系来选择输出,不再需要发事件给JS及接受JS的指令。
第二种思路是优化单次通信耗时。我们发现通信大部分时间都花在序列化和反
序列化过程中。我们自己发明了一个通信协议wson,用的是二进制传输协议,序列化和反序列化效率提升很多。同时因为通信时间的提升,整个页面加载时间也有了一定的提升,大概在5%左右。
列表渲染架构升级
列表组件的前世今生
我们再来看一下另外一个重要的技术演进:列表渲染架构的升级,2015年双11第一次亮相,我们用的组件是Scroller,完成了从DOM树到View树的转换,缺点是必须等JS解析完所有节点才会到native,一次会创建非常多的View,对一个长列表来说了经常需要3-5秒才能出来;到了2016年年中时我们加了一个渐进式渲染的概念,node和tree模式的灵活搭配,最小粒度渲染,解析完单个DOM后可立即显示。但这个方案仍然也一些瓶颈,它渲染了不可见区域,内存占用比较高。到了2016年的双11,我们使用了List,相对于Scroller好多了,我们透传了native 的UITableView 和RecycleView, 在内存的控制上提升明显,比如只渲染可见区域,View内存回收复用等。
到了今年,list也开始满足不了需求了,刚才说了,整个链路都是Weex 长列表,内存挑战很大。而我们的list在内存占用上和native列表还是有不小的差距,另外list还有一个比较大瓶颈就是无法做view的复用,低端机滚动帧率较差。
列表渲染架构解析
我们看一下老的native设计是什么样的,这是偏底层的设计和架构解析。假设一个业务有M个模板和N条数据,我们现在做的就是在JS层做一个模板解析和数据绑定的工作,这一层是Vue做的。JSFM会生成N个Virtual DOM,Native生成N个native Component,可视区域内有限个native View。
这样一个架构问题在哪里呢?
- 为什么做不到View结构的复用? 简单来说,要做复用必须在native 进行模板数据绑定,现在在JS层已经绑定好了,native拆不开,没法做复用。
- 为什么内存占用高?因为element 和component 是重复生成的,虽然他们只是一个个对象,但当列表非常长,无限滚动时,它的内存仍然会呈上升的趋势。
- 为什么帧率差?View永远都是销毁掉重新创建,主线耗时变高,虽然中间也做了一些异步渲染的处理,但还是没有达到理想的幀率.
什么是真正的复用,其实就是类似于垃圾分类的过程,数据就是垃圾,不同种类的垃圾,可回收垃圾、有害垃圾、厨房垃圾,Cell模板就是垃圾桶,我们只要把数据分门别类,在native 侧放到不同垃圾桶里就可以了。回到刚才那张架构设计图,我们需要做的,也就是把数据绑定放到native。看上去整个设计变化非常小,但我们做了差不多三个月,也找了尤雨溪配合我们在Vue 上进行改造。
最后是性能对比。前段时间我们刚好研究了下RN 的几个列表组件,顺便做了一个性能对比,recycle-list是我们新做的列表组件。对比的案例是左边这张图,有60屏数据,在淘宝页面里不算特别长,里面有3000+节点。看一下右边的性能数据报告,总的来说,最大优势就是内存,在iOS,可以让这样一个长列表页面的内存占用控制在个位MB,同时因为通信的数据量变少了,在加载时间、CPU消耗上也有一定的提升。
原文发布时间为:2018-01-25
本文作者:隐风
本文来自云栖社区合作伙伴“淘宝技术”,了解相关信息可以关注“淘宝技术”微信公众号