Native 性能稳定性极致优化

Weex 作为阿里开源的高性能跨平台移动开发框架,开源至今倍受关注,不到一年的时间,已经Github Star数超过1w!它能够完美兼顾性能与动态性,让移动开发者通过简捷的前端语法写出Native级别的性能体验,并支持iOS、安卓、YunOS及Web等多端部署。

Weex简明架构

Native 性能稳定性极致优化_第1张图片

图中是Weex 整体的工作流程:业务开发者通过声明式的定义组件完成.we文件开发之后,经过Transformer模块将.we 文件转为 JS Bundle,JS Bundle主要描述了业务页面的模板、结构、数据逻辑;然后再将其部署到业务服务端。客户端访问Weex页面时,首先会网络请求JS Bundle,JS Bundle被加载到客户端本地后,会进入JS Framework中进行解析渲染;JS Framework解析和渲染的过程其实是根据JS Bundle的数据结构创建Virtual DOM 和数据绑定,然后传递给客户端渲染。

整个过程中,JS Framework将整个页面的渲染分拆成一个个渲染指令,然后通过JS Bridge发送给各个平台的RenderEngine进行Native渲染。因此,尽管在开发时写的是HTML/CSS/JS,但最后在各个移动端(在IOS上对应的是IOS native UI、在安卓上对应的是安卓Native UI)渲染后产生的结果是纯Native页面。

性能优化

提升用户体验是进行性能优化的目标,也是手淘内部移动端开发的基本要求。具体体现为:所有页面在用户侧达到秒开,也就是网络(JS Bundle下载)和首屏渲染(展现在用户第一屏的渲染时间)时间和小于1s。

手淘团队在对SDK进行性能优化时,遇到了很多问题和挑战:

  •  JS Bundle下载慢,压缩后60k左右大小的JS Bundle,在全网环境下,平均下载速度大于800ms(在2G/3G下甚至是2s以上)。
  •  JS和Native通信效率低,拖慢了首屏加载时间。
  •  在活动会场应用时,长页面VDom渲染时间慢,占首屏时间40%左右。
  •  JSThread过于繁忙,导致DOM线程和UI线程的堵塞(三者类似于串行的机制运行)。
  •  JS Task无法抢占执行,当旧页面未渲染完成时,新开页面时无法暂停前一个页面的渲染执行,进而导致新页面打开慢。
  •  复杂页面或交互页面层级过深,导致滚动过程中渲染帧率低。

Native 性能稳定性极致优化_第2张图片

通过对Weex通信到渲染整体链路进行Profile,得出了上图的Weex Timeline。可以看出:在秒开时间中占比最大的是网络下载时间,约占50%以上,它很大程度上取决于当前网络状态;JS&Native通信所占时间约为首屏加载时间的一半;纯渲染时间低于25%(后两者合称首屏加载时间)。

通过对Timeline分析对比,手淘针对各个链路分别进行了针对性的优化。

网络下载

网路方面的优化分为两部分,分别是网络协议和预加载/缓存方面的优化。在未经优化前,全网情况下,IOS端下载时间为280-450ms、Android端下载时间为700-900ms,基本上不可能实现秒开;同时,业务JS文件过大,gzip压缩后大小约为60~80k;此外,还存在域名没有收敛,配置Spady协议未生效、不支持HttpCache(所有请求都是200)等问题。

Native 性能稳定性极致优化_第3张图片

经过网络埋点,对Cache、Http、Https、PackageApp、Spdy等方式的网络时间进行了比较,其中Cache的网络时间为106.69ms;Http和Https的网络时间约为1s左右;Spdy协议的网络时间约为250ms;PackageApp的网络时间约为40ms,这是比较理想的方案。

Native 性能稳定性极致优化_第4张图片

PackageApp是手淘技术团队前几年开发的针对H5的加速方案。其原理图如上所示:在请求静态资源时,服务端通过Push的方式将该资源推送到客户端本地进行预缓存;在资源请求时,首先到预加载模块获取资源,如果预加载模块的命中率足够高,则在90%以上的场景都可以利用本地IO替代网络IO;实际上,在被动更新的模式下,90%的业务都是通过本地获取。

JS&Native通信机制

Native 性能稳定性极致优化_第5张图片

上图是JS&Native通信机制的简要示意图,左侧是JS Framework;中间是JS Engine(IOS是原生的JsCore、andriod是V8、H5是Browser);右侧是Native Render。

首先,Native Render请求下载JS Bundle;然后JS Bundle通过createInstance接口,以JS Engine的方式发送给JS Framework;JS Framework再对JS Bundle进行解析和渲染,解析和渲染的过程就是创建Virtual DOM和数据绑定的过程,在该过程中会产生一系列的CallNative渲染指令,如-creatBody()/-addElement()等。

增强并发

Native 性能稳定性极致优化_第6张图片

上图左侧是未经优化前JS&Native通信流程,可以看出,每当JS发送一个callNative时,Native都会有一个callJS回调,这种方式更类似于JS同Native握手的方式,这种设计方式保证了页面渲染时所需的时序性。

图中右侧的通信流程之所以比左侧少很多callJS,是因为在JS Render中进行简单的队列维护既可以满足时序要求。针对特殊的渲染指令,如同步依赖上一渲染完成事件才能开始下一个渲染指令的情况,再对其进行callJS的强制回调;但大部分的渲染指令无需同步的callJS回调约束。

Node&Tree

Native 性能稳定性极致优化_第7张图片

Weex提供了两种模式控制渲染颗粒度:Node和Tree。Append Tree是指以树状的方式去做模块渲染,它在JS 模块上会将Div的结构、样式以JSON的形式一次性回传给Native;而Append node还是按照原来的方式,需要多次JS&Native通信。

精简链路

Native 性能稳定性极致优化_第8张图片

图中右侧是JS到Native的完整通信流程。JS Bundle进入JS Framework中进行渲染后,首先生成renderTask(Args)的渲染指令,然后它会被Format成一个JSON的字符串,再通过callNative进行分发;再将format tasks送入V8 bridge层;再送入Java层,Java层再去解析JSON 格式的task,然后再执行到native 的renderTask。

最初只设计了两个通信接口。如果把renderTask所有高频渲染指令上浮到与callNative同级的层次,则原通信流程会少两个步骤:首先,会省去JSON的format 步骤;其次,在Java端会省去一次JSON的Pass。这将会大大提高了通信效率。

中断控制

Native 性能稳定性极致优化_第9张图片

当进入长页面时,如果网络状态较差,用户在进入页面会存在卡顿现象,此时用户会重复进入该页面。如果不能将之前旧Instance中断掉,新页面渲染会一直被旧页面渲染阻塞,进而导致越是反复进出,打开页面越卡顿,最后导致整个Native渲染的堵塞。

针对该问题的解决方法是增加DESTROY_CODE的控制。JS Framework在解析和渲染时会产生一系列的renderTask,当renderTask在队列中进行渲染时,通过在Native端设置DESTROY_CODE的方式来控制其是否进行下一步渲染。

渲染优化

渲染优化主要是针对文字渲染。

Native 性能稳定性极致优化_第10张图片

在未优化渲染前,文字存在被截断的现象(如图中右侧所示),这是因为Weex Measure会根据文字和文字的样式生成Layout对象A,DOM节点再根据Layout对象A计算Text节点的高度和宽度;而TextView Draw也会根据文字和文字的样式、大小等属性生成Layout对象B,然后自动生成布局信息。Layout对象A Layout对象B的不一致,导致了Measure与Draw的不一致,出现文字被截断现象。

Native 性能稳定性极致优化_第11张图片

对应的优化方式是利用StaticLayout方式自行绘制文本内容,保证了DOM线程布局计算的Layout对象和UI线程绘制时是同一个对象,进而确保了Measure与Draw的一致性,避免了额外的对象创建。

此外,对于渲染链路上高频的反射接口,进行了固化的重开发,已经固化的逻辑和样式不再用反射调用;对于Opacity属性问题,可以增加setLayerType(View.LAYER_TYPE_HARDWARE,null),可以将页面由38帧提升到55帧。

最佳实践

List最佳实践

List组件是应用范围最广、频次最高的组件。在长列表时,建议尽量使用List,而非Scroller,因为List可以进行内存复用和内存回收;其次,尽可能细粒度拆分cell;通过为了减少首屏渲染时间,需要尽可能减少首次加载内容,多用懒加载,提升用户体验;此外,在多Tab切换场景,需要尽可能复用List组件;最后,尽可能使用a链接跳转,而不是通过响应click。

层级过深导致StackOverflowError

层级过深其实是系统自我保护的行为,如果系统检测到在递归调用函数层次过深时,会主动抛出StackOverflowError异常,进而导致应用崩溃,这其实是为了保护系统的稳定性而设计的。

在业务方开发Weex页面时,很容易忽略层级的问题,进而导致页面嵌套层级过深,进而影响页面的滚动帧率和内存状况。

Native 性能稳定性极致优化_第12张图片

该问题的解决方案只能是删除冗余的嵌套。图中左侧上半部分是天猫首页层级优化前后的引起Crash次数的对比,可以看出,优化后下降幅度较为明显;下半部分是淘宝活动页面层级优化前后的对比,效果同样明显。据手淘实践经验表明,开发过程中Native层级数目不要超过15个。

动画最佳实践

在做动画时,需要尽量使用Transform动画,如Translate/rotate /scale。做Weex动画时,注意配合使用定时器,离开页面时停止定时器;回到页面时重新开启定时器,这种做法可以降低退后台或页面不可见时的耗电量和CPU占用率。

你可能感兴趣的:(Native 性能稳定性极致优化)