作者:徐凯(鬼道),阿里高级前端开发专家。
声明:本文为阿里巴巴集团技术发展部投稿,版权归阿里巴巴所有,未经允许,请勿转载。
责编:唐小引,技术之路,共同进步。欢迎技术投稿、给文章纠错,请发送邮件至tangxy@csdn.net。
Native 开发的诸多亮点中,流畅体验和系统调用是最多被提及的。流畅体验体现在页面滚动/动画的流畅性,背后是更好的内存管理和更接近原生的性能;同时又是 Web 的痛点:资源首次下载、长页面内存溢出和滚动性能、动画性能、传统 Web 性能(如 JS 执行效率)。Native 有丰富的系统调用能力,而 Web 痛点在于:W3C 标准太慢,有限的设备访问能力,API 兼容性问题较严重,如 Geolocation 在 Android WebView 中可用性很差。
Web 开发同样有诸多亮点,其中最耀眼的当属发布能力和规模协作。Native App 商店审核周期长(尤指 iOS);应用更新周期长,iOS 稍快大概能达到一周更新率 60%-80%,Android 同样的更新率要 2 周甚至更长。而 Web 在合适的缓存机制下一分钟可达到 99%+。浏览器内核 WebKit 提供了相对一致的底层运行环境,HTML/JS/CSS 控制页面的结构/行为/样式,URI 连接不同的页面,有了这些基础设施,大规模的业务复用和人与人的分工协作变得相对轻松。
同时今天阿里诸多客户端已经面临包大小接近临界值,大促活动页面(H5)体验较差等一系列问题。结合 Native 和 Web 技术亮点,同时又能解决阿里遇到的业务问题,这就是 Weex 诞生的客观环境。
2016.11.11,在 1754 张双 11 会场页面中(统计了天猫和淘宝),Weex 页面数为 1747 占比 99.6%。手淘 iOS/Android 分别有 83.5%/78.3% 版本(UV)启用了 Weex 会场,手猫 iOS/Android 分别为 91.7%/87.9% 版本(UV)。Weex 覆盖了包括主会场、分会场、分分会场、人群会场 等在内几乎所有的双 11 会场业务。
在这样的应用规模下,工作和目标是:
2016 双 11 会场的感受可查看原始录屏文件: Wi-Fi | 4G | 3G | 2G | 无网络 。录屏时主会场已经是预加载版本,其中 Wi-Fi 和 4G 效果接近,2G 效果取决于数据的网络请求速度(录屏时数据请求约 3.9s),无网络情况下打底数据来自前一次成功请求。流畅性可查看 该视频 ,左起为 H5、iOS Weex、Android Weex。
展开 Weex 双 11 细分目标:
支撑住双 11 的业务需求,是 Weex 必须要迈过的坎。双 11 的会场结构大致为:会场框架(框架 + 主会场、全部会场、必抢、清单、我的双 11)、分会场、其他会场(分分会场、人群会场等)。
Weex 支撑双 11 业务首要解决的是会场框架及其交互形式:
Weex 会场框架很好支撑住了双 11 的复杂交互需求并提供了更好的内存管理。除了会场框架,更多的 Component 和 Module 支撑住了各色各样的双 11 需求,这里仅列出几个代表:
上节内容也可以看出,参与 Weex 双 11 会场涉及多个团队和平台系统:
js-bundle
下载到客户端,客户端访问 Weex 会场时网络 IO 被拦截到本地文件 IO,从而极大加快了网络加载速度,预加载是这次秒开实战的抓手(注:最核心的工作); 以上也仅涉及到客户端和发布端,背后还有无数的业务后台系统,就不一一列出了。
将 Weex 架构自上而下地展开:
Tool,工具层:
elements
、 network
、断点、 console
等 [weex-pack](https://www.npmjs.com/package/weex-pack)
支持一键打包成 App。 DSL
以上就是 Weex 在双 11 中的架构和业务支撑的范围了。
Weex 的首要挑战就是稳定性,或者说保障 Weex 会场最大限度不降级。
8 月初(同期双 11 启动)奥运大促时,手淘 iPhone 中反复进出会场 20+(手猫 15+),会出现 Crash。奥运大促当天,手淘 iOS 1.59% Crash 次数来自该问题(top 6),手猫 1.94%(top 8)。发现问题的当天成立了攻坚小组,从 JS 业务代码、JSFM(框架)、iOS 渲染、iOS JSCore 几个方向同时排查,一周内各方向排查到逐步收敛到:根本原因是 Weex 页面实例被全局持有(Weex Runtime 只有一份),进而导致页面退出时内存不被释放,反复进出直至内存爆掉。因而任何可能导致页面实例被全局持有的因素都会触发这个问题:
destroyInstance
时清理 commonModules
和所有 dependency target
;iOS7 下的 Set Polyfill
内存飙升问题; 除了建立攻坚团队推进解决该问题,也在造势期前就展开双 11 会场压测,反复验证该问题,自双 11 造势期会场开测之后,该问题未再出现。
在治理 JSCore 内存的过程中,逐步意识到对全局变量管控的必要性。Weex 中多个页面共用 1 个 Runtime,单个页面如果写法不规范不仅可能导致内存泄露,更有可能污染全局环境,进而导致所有 Weex 页面无法正常工作。全局污染治理的核心抓手:
use strict
,使用严格模式可以将较多常见的 JS 陷阱转化为错误,如:无法再意外创建全局变量、将拼写错转成异常、限制了 eval
的能力等; Object.freeze()
,将 Weex 核心对象和 JS 原生对象“冰冻”住。尝试修改被“冰冻”的对象会抛出错误,一旦“冰冻”无法“解冻”。 Weex 通过 Adapter 来适配不同客户端的具体实现,诸多通用库,如:网络库、图片库、API 库、 H5 容器(Web 组件)、埋点库、配置库 等在不同客户端上版本不一致,因此导致的线上问题将会成为双 11 会场的隐患。为此展开的依赖梳理和同步机制是双 11 稳定性的保障之一。这件事情可能将会长期出现在 Weex 问题清单之中,如何做到上层 Weex SDK。
压测在造势期会场测试阶段展开,在超出真实会场压力的情况下(真实会场 150 坑位上限),尽可能提前嗅探出潜在的 iOS JSCore 内存问题、iOS/Android 异常闪退等细节问题。
2016.11.11,Weex 在手淘中的 Crash 占比情况:
考虑到会场的业务量级,Weex 的稳定性仍然是不错的。
注:单独计算的 Weex Crash 率太小,参考价值不大。
Weex 秒开率 = (加载时间 + 首屏渲染时间)< 1s 的比率
其中:加载时间指 Weex js-bundle
的加载时间(从网络下载或本地加载);首屏渲染时间指 Weex 页面开始渲染到第 1 个元素 bottom
超出首屏范围的时间。下文提到的“首屏网络时间”为加载时间与首屏渲染时间的和。
从双 11 结果看预加载大幅度提升加载时间,对秒开率的贡献尤其突出;但性能优化是个长期迭代的过程,回头来看优化的抓手是:预加载和首屏渲染优化。
预加载解决了 1 个问题:
用户访问页面(H5/Weex)之前,将页面静态资源(HTML/JS/CSS/IMG…)打包提前下载到客户端;用户访问页面时,将网络 IO 拦截并替换为本地文件 IO;从而实现加载性能的大幅度提升。
启用预加载后加载时间的变化,粗算一下:手淘 iOS,走网络平均 296ms,走预加载 18ms,网络性能提升约 15 倍;手淘 Android,走网络平均是 696ms,走预加载是 54ms,网络性能提升约 12 倍,但绝对值更大,对 Android 会场秒开贡献更为突出。
2015 年预加载已经在双 11 H5 会场中有较多应用,2016 年预加载升级为一项基础服务,不仅为 WindVane 提供预加载能力,也成为 Weex 秒开的最强外援。
此次双 11 会场共启用 30 个预加载包,总容量超过 20MB,业务需求相对稳定且流量较大的几个页面(会场框架+主会场 等)是独立的包,保证了对整体秒开的贡献,其他分会场均分在剩余的包中。同时主要采用强制更新的策略,即新的资源包(服务端有新发布)未下载到本地就直接读取线上,可以保证业务的实时性。2016.11.11,双 11 会场中 Android 走预加载占比为 59.4%,iOS 为 62.5%,高于平均水平(但还可以更高)。
首屏渲染优化的目标就是尽力缩短首屏的渲染时间,为此在一系列的优化过程中,可以粗分为:DOM 解析优化、UI 渲染优化、分段懒加载。
Component append 属性定义了 node
和 tree
2 种渲染模式, node
就是逐个节点渲染, tree
就是整棵树一起渲染。直观地对比: node | tree 。
node
模式,节点逐个从 js 提交到 native 的, native 侧有个 16ms 间隔的 layout 保证渲染的正确性,这是更接近于 WebKit 的一种解析渲染模式。优势是每一个被解析完的节点都可以立刻显示,同时保证不会长时间阻塞主线程,劣势是可能会造成多次冗余 layout,拉低流畅性。 tree
模式,整棵树(以当前节点为 root 的整棵树)从 js 提交到 native。优势是只需布局一次,渲染更高效;劣势是如果 tree 过大,就可能会阻塞主线程甚至阻塞渲染。 node
和 tree
可以精细化地控制页面展示的逻辑和颗粒度,典型的实践为首屏以内按 tree 解析,首屏以外按 node 解析。 UITableView
和 Android RecyclerView
,这两种 View 构建了 App 的半壁江山,使用它们来封装 list 的好处: Build Tree
、 Compute Style
、 CSS Layout
、 Create View
、 Update Frame
、 Set View Props
),所以必须在 16ms 内完成这 6 个步骤,现实是任何一步在主线程中都可能超过 16ms,这块。 除了底层的保障,我们也坚持每天产出“性能优化建议”,推进业务性能优化,接下来会有更加方便的工具提供给业务方直接性能调优;如双 11 期间 devtool 中增加了层级检测和告警,可以帮助排查深层级导致的 Android 低端机 Stackoverflow。
由于主会场流量占据了总流量的大部分,对其秒开率单列统计。2016.11.11 数据为:
schema
唤起支持、网络类型判断、 geolocation
、 audio
、 cookie
、大图预览、通讯录等; 不一一列举了,之后会有 Weex Roadmap 的讨论并且会及时公布出来,欢迎关注。