如何保证H5页面高质量低成本快速生成?

任何技术优化都依托于业务的发展,而QQ 会员增值业务的重心转移到手 Q 移动端,同时由于每个页面都意味着 KPI 收入,任何可能导致页面功能不可用的发布行为都是不可接受的。那么如何保证H5页面高质量快速生成呢?
一、业务背景

什么是 QQ 会员?QQ 会员是宇宙第一大包月业务,大部分的会员用户都很年轻。大家可以猜一下哪个年龄段的 QQ 会员用户最多?小学、初中、高中、大学还是白领?(如果你还不是 QQ 会员,说明你已经老了 :))

个性张扬是年轻的代名词,QQ 会员用户在好友列表中的名字是红色,而且排名靠前,这些都达成了用户的炫耀心理,就连发红包时都拥有右图中的专属的皮肤。

同时,QQ 会员还有下图中所示的 QQ 等级加速更快、购买电影票、定外卖享受折扣等权限。

640wx_fmtpngamptpwebpampwxfrom5ampwx_lazy1

其实,手机 QQ 在承担即时通讯等社交功能的同时,还承载着 QQ 会员数十亿的营收重任,而这数十亿中,大部分的营收都来自于内嵌在手 Q 中的 H5 页面,因此保证 H5 页面的高质量,是我们的工作重点。

二、技术挑战

保证 H5 页面的高质量,我们有以下三个挑战:

如何让 H5 打开更快?

雅虎的一项研究表明,页面打开每慢 400 毫秒,将带来 5%~9% 的用户流失;让页面更快呈现给用户是前端工程师们的不懈追求,在 Hybrid 模式下借助于终端的能力我们有了更大的想象空间!

如何让 H5 开发更快?

好的产品是运营出来的,沃尔玛每周都有打折,电商有 6.18 和双 11 双 12,同样 QQ 会员也需要有持续的 H5 运营活动以保持用户的活跃和留存,而 H5 组件化是我们提高开发效率的手段。

如何保证 H5 页面持续高质量?

手机 QQ 一两个月发布一个版本,但是 H5 页面每天都有发布,随着 H5 逻辑越来越复杂,比如不同身份用户(非会员、会员)在不同时间点(到期前和到期后)进入页面时看到的内容都不一样;如何不依赖成本很高的人工测试来保证 H5 页面的功能持续可用?

三、打开更快?基于Hybrid的Sonic方案

首先介绍下我们基于 Hybrid 的 Sonic 方案是如何实现页面在 1 秒左右打开的。

要打开页面,在 PC 端需要先打开一个浏览器(Chrome 或者火狐),在 Android 或者 ios 应用中必须先有一个 WebView(图中橙色部分);出于性能考虑手 Q 并未在后台常驻一个 WebView 进程,所以要打开页面需要先初始化 WebView

在之前版本的手 Q 中我们时常可以看到类似下图中左边的白屏,虽然加上了卖萌的文案“别闹,加载是件正经事”让用户感觉萌萌哒,但这掩盖不了曾经 WebView 初始化慢的事实。虽然经过几个版本迭代优化,客户端耗时已经大大降低,但是还需要近 900 毫秒。好像距离一秒的目标很近了。

640wx_fmtpngamptpwebpampwxfrom5ampwx_lazy3

但是 WebView 初始化完成后,再调用 loadUrl 接口获取目标 URL 的 HTML 内容并进行渲染(上图中蓝色部分);由于我们的 web 层基于 PHP 语言来实现,一个 Web 请求需要新建一个子进程去查询若干个后台服务,这里的耗时至少需要 200 毫秒。算一下终端加后台的耗时加起来已经超过一秒了。虽然没有人能跑的比博尔特更快,但是我们还是有方法来让我们的页面打开更快。

优化方法

串行改为并行。我们把终端 WebView 初始化工作并行为两个线程(下图中两个橙色块):WebView 主线程处理主要的初始化工作,而登录态获取、业务插件初始化等工作放在 WebView 子线程,这样终端的耗时就从之前的两部分的耗时之和变成了两部分耗时的最大值。同样在后台我们也新建了一个 proxy 来代理后台所有服务的查询工作(右侧绿色块),由 proxy 来并行发起对其他后台服务的查询,proxy 的耗时取决于最慢的那个后台服务接口的耗时。

640wx_fmtpngamptpwebpampwxfrom5ampwx_lazy

网络耗时的优化。电影英雄中有段对白:剑术的最高境界是心中无剑,手中亦无剑。减少网络耗时最有效的优化方法莫过于不进行网络请求,也就是 Cache。

(1)虽然浏览器本身有缓存功能,可以通过设置静态文件的缓存时间来减少请求数,但是我们经过数据验证,发现移动端浏览器缓存有时候并不可靠,缓存还未过期也有可能被清掉重新请求。

(2)H5 标准中也有一个 localstorage 特性,我们通过扩展 seajs 的缓存插件实现在 localstorage 中缓存 JS 文件,加快了 HTML 依赖的 JS 的加载速度。但是 HTML 本身仍然需要走网络请求。

(3)其实手 Q 也实现了一套离线包机制,用来缓存 HTML 和图片、CSS、JS 等文件,但是只能缓存静态不变的内容,比如刚开始介绍 QQ 会员时的会员个性化红包页面就利用了离线包的能力。然而我们的页面有很多用户数据(比如会员身份、会员成长值、QQ 等级成长速度等)需要实时查询,再加上终端复杂的离线包校验机制耗时很多,我们新建了 HTML Cache 机制,在终端缓存了整个 HTML。

(4)有了缓存之后,WebView 主线程先发起 1.1 的 loadUrl 操作展示本地 HTML 缓存给用户,同时发起 1.2 的 HTTP 请求去获取最新的数据内容,如果有变更则通过第 3 步的 jsbridge 回调进行页面刷新,同时终端会异步进行第 4 步的更新本地的 HTML Cache。

(5)如果页面没有变化,网络耗时仅为加载本地 HTML 文件的 IO 时间,这个时间几乎为 0;如果页面有变化,由于这里提前并行发起了 http 请求,网络耗时也比上一页中串行的 HTTP 直连要少很多。

这里还有一个问题,就是如果缓存的 HTML 内容和最新的内容不一致,我们需要刷新整个页面吗?答案是否定的。大家注意下这里第(2)步返回内容可能是 HTML,也有可能是 JSON,下面会介绍为什么。

我们将 HTML 拆分为两部分:模板和数据块。一个数据块对应一段 HTML 片段(下图中蓝色字部分),用注释语句包裹起来;而数据块以外的部分为模板,一般情况模板的内容比较固定,dom 结构、内联的样式等很少变动。

640wx_fmtpngamptpwebpampwxfrom5ampwx_lazy2

比如图中有三个数据块:key1,key2 和 key3,分别对应这个页面从上到下三个红框框住的部分。

刚才有讲到并行 HTTP 请求回来的内容可能是 HTML,也可能是 JSON;我们的策略是如果是首次访问本地没有缓存或者缓存被清理则返回完整的 HTML;如果模板未变化只是数据块有变化,比如总成长值加了 2 点,从 76660 加到 76662,或者生活福利模块更换了 2 个广告位,只需要返回 JSON 即可,由 jsbridge 触发页面回调来替换 DOM 节点实现页面的局部刷新。

以上两个优化点需要终端和页面按照统一规则紧密配合,我们通过扩展 HTTP 协议来实现。

640wx_fmtpngamptpwebpampwxfrom5ampwx_lazy4

我们扩展了 4 个 HTTP 协议头,2 个请求头和 2 个返回头。

accept-diff 表明终端是否支持增量更新的能力,一般传 true,对于老版本的手 Q,无法携带该头部,后台将会始终返回完整的 HTML;template-tag 代表终端本地缓存的 HTML 的 SHA1 摘要值;

template-change 代表服务端模板是否有变更,模板和数据块均无变更返回 304,模板无变更仅部分数据块有变更时为 false,首次和模板变更时都是 true;cache-offline 是后台告诉终端如何进行页面刷新和本地 HTML 缓存更新,如果为 true 代表刷新页面并更新缓存,如果为 store,代表仅更新缓存不刷新页面。

下面我们从整个流程上来看一下。

650wx_fmtpngamptpwebpampwxfrom5ampwx_lazy5

第一种场景是用户首次或者缓存失效时加载页面,用户点击终端入口后,在初始化 WebView 的同时并行发起 http 链接,在 WebView 初始化好之后会在内核和 http 流之间建立桥接。内核在读取完毕之后终端根据模板数据拆分规则对 html 进行内容分割,并记录模板和数据的 tags 信息,异步 HTML 为模板和数据用于下次与服务器通信实时更新。

第二种场景是用户二次进入页面,这种情况的占比七成以上。WebView 优先加载 HTML 缓存,并且根据 http(s) 返回码的同步状态,进行不同的处理。

如果 status 为 200,且返回的是 JSON,说明只有数据变更,终端会对数据进行 diff 处理,和页面通过 js 通信进行局部刷新。

如果发生模板变更,处理逻辑会有点复杂,终端根据在不同机型和网络环境下做智能切换处理,速度较快时会拉取完 HTML 流交给内核渲染,速度不快时仍然会建立桥接流,并且也会对 HTML 进行拆分;

如果 status 为 304 说明完全命中缓存,则不作任何处理;

880wx_fmtpngamptpwebpampwxfrom8ampwx_lazy8

1、左边的效果是最初页面局部刷新时的表现,我们可以看到加载本地缓存的 HTML 后很快看到了整个页面,然后成长值发生了变动,然后又更新了两个广告运营位。但是这里的体验还是有点问题的,加载图片需要时间,导致页面的闪动很明显。

2、我们又改进了下,先将图片下载完,再去局部更新这两个广告运营位,最终实现了右边比较平滑的效果。

另外一个图片的优化是图片自适应。

网页中的流量大头是图片,图片加载消耗了很多时间。我们实现了对于同一张图片,终端看一根据用户不同的手机分辨率返回不同规格的图片,而这一切不需要做任何代码修改,完全透明接入。

比如如果你是 iphone 7S,CDN 返回 750 像素的高清大图,如果你还在用 iPhone 4S,CDN 返回 480 像素的一般清晰度的小图,这样在保证体验的同时减少了加载的图片大小,页面更快展现给用户。

这个项目内部代号 Sonic,意思是希望页面加载速度可以像音速一样快。最终我们也实现了占比 70% 右侧 2 个场景,局部刷新和完全 cache 时总耗时 1 秒左右,而且首次访问时的总耗时也低于之前最左边的 HTTP 直连。

770wx_fmtpngamptpwebpampwxfrom7ampwx_lazy7

四、开发更快?组件化开发

我们除了让 H5 页面加载更快,还需要让 H5 页面开发更快以满足活动运营的需求。

1. 什么是运营活动?

如下图所示,左边第一个活动新游戏即将发布,在预约页面提前预约的用户在游戏发布后下载完成后可以免费领取福利;左边第三个活动,QQ 会员可以免费领取一张美团的优惠券;最右边的活动,QQ 会员玩天天酷跑游戏可以免费抽奖获取游戏道具;

10100wx_fmtpngamptpwebpampwxfrom10ampwx_lazy101

2. 运营活动的要求

运营活动有四个要求:一般 1~2 天需要完成开发测试和上线、不同活动可能有相同的功能逻辑,一般会投入大量推广资源所以对页面的质量要求比较高,大量资源推广时并发访问用户多对性能要求比较高。

3. 解决思路

我们的思路是必须尽可能减少开发环节和开发人力,最小化功能逻辑实现颗粒化可复用,对前端代码和后端服务要求稳定可靠,必须持续的前端性能优化。

我们的解决方案是构建一个组件化的活动开发平台,内部代号 ET

11110wx_fmtpngamptpwebpampwxfrom11ampwx_lazy111

第一:减少一切可以减少的环节。一般 H5 页面的开发流程是交互 - 设计 - 重构 - 开发,我们和交互、设计人员制定好运营活动的交互设计规范,比如统一弹窗样式,从而减少了交互环节;利用 H5 的新特性 Canvas 自动对设计稿进行切图,又省掉了重构环节。

第二:组件化开发。开发人员只需要开发组件,组件可以在不同活动中复用。运营人员只需要拖拽组件、配置资源,最后由执行引擎生成包含活动逻辑的 HTML 页面,自动发布外网即可。

660wx_fmtpngamptpwebpampwxfrom6ampwx_lazy6

一个组件由 HTML 片段,CSS 样式和 JS 逻辑构成;开发人员完成组件开发之后,运营人员像拼积木一样,拖动几个组件组合在一起,就可以生成运营活动页面。同时 ET 平台实现了一整套发布回滚流程支持,自动对接页面性能测试工具,可以对运营页面的性能进行自动化测试,最后也会给大家分享下如何进行性能自动化测试的。

990wx_fmtpngamptpwebpampwxfrom9ampwx_lazy9

该平台上线后,月均上线活动达到 300 个以上,但全职开发人员投入仅 1 人。

16160wx_fmtpngamptpwebpampwxfrom16ampwx_lazy161

五、高质量?自动化解决方案

保证 H5 页面功能正常,并且让 H5 页面打开更快,不是一锤子买卖,需要可持续。H5 页面的质量不能仅仅靠测试人员的手工测试来保证,我们需要一套自动化解决方案。

12120wx_fmtpngamptpwebpampwxfrom12ampwx_lazy121

说到质量标准,iOS9001 是我们耳熟能详的国际质量标准,但是 H5 页面的质量标准是什么?

PC 时代,我们知道 performance api 就能比较全面的透视整个页面请求过程的耗时,在 Hybrid 模式下,我们对 H5 页面高质量的定义是页面功能的高可用和页面加载速度更快。

功能高可用需要 WebView 不会 crash,页面能够正常打开并且业务逻辑符合预期;页面加载速度更细化,终端耗时、网络耗时、页面耗时,同时需要关注总耗时大于 5 秒以上的慢用户占比。

14140wx_fmtpngamptpwebpampwxfrom14ampwx_lazy141

页面功能可用性的自动化测试,我们构建于腾讯内部自研的自动化测试工具 QTA。该工具不仅可以识别 Android 和 iOS 终端的控件,也可以识别 Web 的 dom 控件,通过对点击事件进行模拟,将实际的返回值同期望值比较以确认用例是否通过。

测试人员使用 Python 语言编写自动化测试脚本上传到 SVN,由分布式任务管理系统分配可供测试的手机模拟器或真实的手机,测试人员可以手工或者设置定时任务自动执行测试计划。

同时我们将 Web 发布系统和任务管理系统进行打通,每次发布前自动进行功能自动化测试,只有在预发布环境的通过率达标才能继续发布,这样就保证了频繁变更时 H5 页面的功能依然正常。

13130wx_fmtpngamptpwebpampwxfrom13ampwx_lazy131

页面性能自动化测试我们参考了很多现有的工具,比如 yslow,雅虎前端优化军规以及谷歌的 pagespeed,但是发现这些对 Hybrid 模式支持的都不是很好,尤其是我们基于手 Q 环境下有更多的个性化的东西。

我们选择了自研 H5 页面性能自动化测试工具,简称为 WPT,Web performance test。参考了 yahoo 军规,结合终端环境特性和 H5 业务特性,对 H5 页面加载的全流程进行发布前测试和发布后回归。

17170wx_fmtpngamptpwebpampwxfrom17ampwx_lazy171

15150wx_fmtpngamptpwebpampwxfrom15ampwx_lazy151

简单回顾下,我们通过 H5 页面和终端的深度融合实现了 H5 页面的快速加载,同时通过组件化实现了 H5 页面的快速开发,使用自动化工具实现了 H5 页面变更时的持续的高可用和高性能,最终实现了高质量的 H5 的架构实践。

六、作者简介

翟伟,QQ 个性化业务前端团队 Leader,曾参与过超级 QQ 的和 QQ 会员的前端开发工作,目前负责手机 QQ 个性装扮的开发工作,拥有近 10 年的项目架构和实践经验,专注于手机 QQ 的 Hybrid 模式下 H5 优化和持续集成方向。

本文首发于微信公众号 小时光茶社(Tech Teahouse)

「五年博客,如果觉得我的文章对您有用,请帮助本站成长」

订阅周报 热门文章 关注微博