Native 性能稳定性极致优化

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 在2017年1月12日 Weex Conf 2017下午的技术实践论坛上,来自Weex团队的饮源带来了题为《Native 性能稳定性极致优化》。分享中,他从网络下载、 JS&Native通信机制以及渲染优化三部分详解了手机淘宝在Native性能稳定性极致优化经验。

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

Weex简明架构

1b61228b33be649d0ee98f8d68a5d6dde52fbce6

图中是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无法抢占执行,当旧页面未渲染完成时,新开页面时无法暂停前一个页面的渲染执行,进而导致新页面打开慢。
  •  复杂页面或交互页面层级过深,导致滚动过程中渲染帧率低。

31210a7ea8ee6445244c994c6459c9873d54c3ce

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

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

网络下载

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

04a5bec3151bcd9c5e24ddfbb0ec6c5bafbeb636

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

d3c690f99cd285157c168dcb7b72b4bd6dabf5a0

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

JS&Native通信机制

3e5b93eeaa9e3b3d2828e9baf5437617de77a045

上图是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()等。

增强并发

42fbb15dcaaffb7d01b79e69775b2c161d02de6e

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

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

Node&Tree

c38a4ea340805d17cc6afc5695c8f30ad30a2a33

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

精简链路

9be81081430985a3396503aa65abadf2970bdaa6

图中右侧是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。这将会大大提高了通信效率。

中断控制

be37398ea09b926aa0b6718192315d5cddb0ff3a

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

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

渲染优化

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

b30f7a4c51475d4b375a50f375fe2eac14d4b40d

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

1bc206c59e59f69f803c558c54b1d82fdf695323

对应的优化方式是利用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页面时,很容易忽略层级的问题,进而导致页面嵌套层级过深,进而影响页面的滚动帧率和内存状况。

0e02d5d6c6dbb0e74e5d421de0a5f1182ee77ce1

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

动画最佳实践

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

相关文章
|
监控 物联网 云计算
优化服务配置:提升效率与用户体验的关键
随着科技的迅猛发展,服务配置已经成为企业和个人生活中不可或缺的一部分。无论是云计算、移动应用、还是物联网设备,都需要良好的服务配置来确保顺畅的运行和卓越的用户体验。本文将探讨服务配置的重要性,以及如何优化配置以提高效率和用户满意度。
|
5月前
|
缓存 前端开发 JavaScript
如何优化前端性能提升用户体验
在Web应用中,前端性能是影响用户体验和转化率的关键因素之一。本文将介绍一些优化前端性能的方法,包括减少HTTP请求、使用缓存、压缩代码等。
|
3月前
|
运维 监控 算法
为什么需要优化Java应用的性能与稳定性?
为什么需要优化Java应用的性能与稳定性?
|
12天前
|
Kubernetes Java 编译器
解锁极致性能:Quarkus如何让JVM应用调优变得前所未有的简单与高效!
Quarkus是一款专为GraalVM和OpenJDK设计的Kubernetes Native Java框架,采用AOT编译技术将Java应用转化为本地代码,大幅提升启动速度与运行效率。它简化了性能调优流程,如自动优化垃圾回收、类加载、内存管理及线程管理等,使开发者无需深入理解JVM细节即可轻松提升应用性能。与传统JVM应用相比,Quarkus显著降低了性能调优的复杂度。
47 2
|
3月前
|
SQL 缓存 Java
系统性能优化总结
系统性能优化总结
54 10
|
2月前
|
数据库 Android开发 开发者
打造高效安卓应用:从代码优化到性能提升
【8月更文挑战第2天】在移动设备的海洋中,安卓应用的效能直接关系到用户体验的好坏。本文旨在深入探讨如何通过代码层面的优化技巧和策略来提升安卓应用的性能。我们将一起探索减少内存消耗、避免不必要的CPU使用以及提高应用响应速度的方法。文章将结合具体的代码示例,为开发者提供可行的优化建议,帮助他们构建更流畅、更高效的安卓应用。
52 2
|
3月前
|
监控 Java 图形学
【性能优化篇】U3D游戏卡顿大作战:内存与渲染效率的极致提升
【7月更文第12天】在Unity3D游戏开发领域,性能优化是决定玩家体验好坏的关键一环。游戏频繁卡顿,不仅破坏了沉浸式体验,还可能造成玩家流失。本文将深入探讨如何有效解决U3D游戏卡顿问题,特别聚焦于内存管理和渲染效率两大核心领域,助力开发者打造流畅丝滑的游戏世界。
161 0
|
4月前
|
存储 JSON 数据格式
如何提升写入效率?Schemaless 写入性能优化实践分享
TDengine 是一款时序数据库,其Schemaless模式适应物联网数据动态变化。通过分析火焰图,发现parser和insert操作是性能瓶颈。优化措施包括减少标签解析、排序和子表生成的重复执行,提前判断schema变更,改进数据插入方法,减少内存分配和拷贝。通过这些优化,如在3.0版本中,line协议性能提升了2.5倍,telnet提升2倍,json提升近5倍。使用工具如火焰图和perf进行性能分析,以识别和解决瓶颈,实现性能提升。
25 0
|
缓存 算法 大数据
倚天710规模化应用 - 性能优化 - 软件预取分析与优化实践
软件预取技术是编程者结合数据结构和算法知识,将访问内存的指令提前插入到程序,以此获得内存访取的最佳性能。然而,为了获取性能收益,预取数据与load加载数据,比依据指令时延调用减小cachemiss的收益更大。
|
5月前
|
缓存 Android开发 UED
构建高效Android应用:从优化用户体验到提升性能
【5月更文挑战第15天】 在移动开发领域,构建一个高效的Android应用不仅仅意味着实现功能,还要确保流畅的用户体验和出色的性能。本文将深入探讨如何通过界面优化、代码整洁、资源管理和多线程处理等技术手段来提升Android应用的整体效率。我们将透过实际案例,揭示常见性能瓶颈的成因,并提供相应的解决方案。此外,文章还会涵盖最新的Android Studio工具和Lint检查的使用,帮助开发者早期发现潜在问题。
下一篇
无影云桌面