导语 | 任何技术优化都依托于业务的发展,随着QQ会员增值业务的重心转移到手Q移动端,对H5页面不仅要求加载更快,还需承载丰富多彩的运营活动,同时由于每个页面都意味着KPI收入,任何可能导致页面功能不可用的发布行为都是不可接受的。


本文主要介绍QQ会员的前端开发团队在手Q的hybrid模式下对H5页面的性能优化、组件化和持续集成方面的实践。

 

随着移动化浪潮的来临,QQ增值业务的重心从PC端PCQQ转移到移动端手机QQ;作为前端开发,我们的工作内容也转移到基于手机QQ的hybrid模式下H5开发工作。


 

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

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


同时QQ会员还有左图中的QQ等级加速更快等特权,买电影票、定外卖还能打折。


其实手机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方案是如何实现页面在1秒左右打开的。


 

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


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


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




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

第2个优化是网络耗时的优化。电影英雄中有段对白:剑术的最高境界是心中无剑,手中亦无剑。减少网络耗时最有效的优化方法莫过于不进行网络请求,也就是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直连要少很多。


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


 

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


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


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

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



 

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


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


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


 

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


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


 

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


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


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


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


 

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


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


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


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


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


 

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


 

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


 

首先我们看一下什么是运营活动?


1、左边第一个活动新游戏即将发布,在预约页面提前预约的用户在游戏发布后下载完成后可以免费领取福利;


2、左边第三个活动,QQ会员可以免费领取一张美团的优惠券;


4、最右边的活动,QQ会员玩天天酷跑游戏可以免费抽奖获取游戏道具;


 

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


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


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


 

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


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


 

一个组件由HTML片段,CSS样式和JS逻辑构成;开发人员完成组件开发之后,运营人员像拼积木一样,拖动几个组件组合在一起,就可以生成运营活动页面。


同时ET平台实现了一整套发布回滚流程支持,自动对接页面性能测试工具,可以对运营页面的性能进行自动化测试,最后也会给大家分享下如何进行性能自动化测试的。


 

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




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

 


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


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



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



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


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


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



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


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




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


 

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