QQ空间前端工程

极牛技术实践分享活动

极牛技术实践分享系列活动是极牛联合顶级VC、技术专家,为企业、技术人提供的一种系统的线上技术分享活动。

每期不同的技术主题,和行业专家深度探讨,专注解决技术实践难点,推动技术创新,每两周的周三20点正式开课。欢迎各个机构、企业、行业专家、技术人报名参加

嘉宾介绍
崔 进 QQ空间的前端工程师

本次主题
QQ空间的hybrid页面首屏优化方案webso,因为活动页面、运营页面的需要,亦或者客户端开发周期长,需要采用H5的技术方案,越来越多的H5页面内嵌在客户端里了, 即所谓hybrid形式。
QQ空间如何优化hybrid页面
把H5页面内嵌在QQ空间客户端里面,是一个开发重点转型的问题,也是面临的新的优化课题。
hybrid页面主要体现在两个客户端:QQ空间客户端和手Q客户端

当初面临的主要体验问题是:

  1. 客户端的webview启动太慢,尤其是android上

  2. 页面加载有白屏的过程
    所以我们的精力也主要集中于来解决这两个问题。

在解决这两个问题时,主要参考的是现有成熟的技术方案,比如native的客户端是怎么保证体验的,native客户端会缓存用户上一次访问的数据,下一次进来的时候会先显示上一次的内容,再更新成新的内容。那我们是不是也可以采用类似的方案呢?

首先分析了我们当时的现状:

  1. 我们的H5页面都是通过自研的node.js server直出的,这意味着页面输出层我们是可以控制的。

  2. 我们的H5页面是内嵌在QQ空间客户端和手Q客户端的,那这两个客户端都是可以配合做相应的优化修改的。

所以,我们最终有了webso的技术方案:

  1. 客户端缓存node.js直出的H5页面,并且node.js通过自定义的响应头来控制客户端的缓存;

  2. 客户端将初始化webview和加载页面内容并行进行,并通过客户端的长连接通道来加载页面内容,来提升弱网络下的体验

图片描述

上图就是我们对初始化webview的流程进行优化后带来的好处。
这样就大大弱化了webview启动慢的问题,如果客户端长连接通过够快,甚至可以达到webview启动完成,页面就可以显示出来了,基本可以媲美客户端的体验了。
webso方案核心内容
webso方案包含的主要几个核心内容是:

  1. 离线缓存
    首先,webso借鉴的就

是客户端的缓存方案,我们也是讲H5的直出内容缓存在客户端了,但是流程和细节稍有差异。
图片描述

这是webso方案的主要流程,首先用户点击客户端上的某个hybrid页面的入口按钮后,客户端会开始初始化webview,并同时通过客户端的wns组件去加载本地缓存。(wns是QQ空间客户端的基础网络组件)wns在加载到当前页面的本地缓存后会将页面塞到webview里面去显示(如果webview还没有初始化完成,就等待webview初始化完成),同时会计算缓存页面的md5,在通过wns的长连接通道去加载新的H5页面内容时,会将md5放置在请求头的e-tag头上。
后端node.js在收到请求到,会像处理正常的http请求一样工作,只是在最后一步的时候会将要返回给客户端的html页面内容交给node.js的一个gzipHttp模块。
gzipHttp模块会根据请求头里的e-tag里的值和当前需要返回给客户端的html页面的md5进行对比,如果一致,则会在响应头上放置一个自定义的响应头cache-offline: store,并返回304,如果不一致则会在响应头上设置cache-offline: true.

cache-offline有如下几种取值,以及客户端的处理方式。
false: 响应内容不进入离线,刷新webview,并清掉本地缓存。
true: 响应内容进入离线,刷新webview.
store: 响应内容进入离线,如果weview已经有内容在显示则不刷新webview.
无: 作用与false一样。
这样就达到了我们的目的:webview启动后用缓存的页面内容展示给用户,wns组件并行加载到最新的H5页面,更新webview的展示并更新客户端本地的缓存。

2. 增量更新
上面的流程中有提到,node.js在返回html内容给客户端的时候有两种可能:304和完整的页面内容。
增量更新主要目的就是优化返回完整页面内容的体验,有的时候,有的页面体积比较大,导致加载的时候比较慢。同时我们调研发现,大部分页面都是有部分页面内容是不怎么变化的,比如head部分,内联的样式,内联的脚本等等,并且这部分内容占比并不小,占有页面体验一半左右。
所以如果我们将页面的这部分不怎么变化的部分跟经常变化的部分分开,然后对不变的部分进行增量更新,效果是不是会更好呢?
最终的效果是肯定的,我们先来看下增量更新具体是怎么实现的:
跟离线缓存一样,我们需要控制客户端的缓存,并且客户端需要向后端node.js提供本地的缓存信息。不同的是,后端node.js需要将页面拆分成动态内容和静态内容两部分,而客户端则需要将静态内容和动态内容进行合并。
图片描述

这是我们在离线缓存的基础上增加了增量更新的特性后的流程图。增量更新对比的处理仍然就交给gzipHttp模块处理。
首先客户端会通过在请求头中设置accept-diff来开始后端改客户端是否支持增量更新,通过会通过template-tag将本地缓存的静态内容的md5带给后端。
后端的gzipHttp模块在返回html内容时会先通过e-tag和整个页面的html md5对比页面是否有变更,然后对需要返回给客户端的html进行拆分,然后对拆出来的静态内容进行md5,然后对比请求头里的template-tag值,如果一致,说明静态内容没有变更,如果不一致则说明客户端的静态内容需要更新。
对静态内容需要更新时,后端server会取出与template-tag一致的缓存,然后与刚拆出来的静态内容进行二进制对比,并拿到增量内容,然后将增量内容的base64与动态内容一起返回给客户端。
客户端则完全是一个逆向的过程了,先将增量内容更新到本地的静态内容上,然后将最新的静态内容和动态内容进行合并。
这是一个典型的支持增量更新的页面 :

图片描述
中间的wnsdiff-body的一组标签就是开始后端node.js从这里来拆分静态内容和动态内容。

  1. 预加载
    预加载,是属于锦上添花的点。

我们整个webso的方案其实都是依赖于客户端本地的缓存,如果没有本地的缓存,体验还是提升不上去。
所以,我们提出了预加载的方案,这个主要是客户端提供的能力。
主要的价值就是:

  1. 可以在用户访问页面前就通过前置的页面来预加载,提高缓存覆盖率

  2. hybrid页面可以通过客户端接口来控制本地缓存,而不是完全后后端node.js来控制缓存。

图片描述

可以看出接口其实很简单,就是告诉客户端需要缓存的页面url,以及是否需要先判断本地缓存的选项。
通过这个接口,我们可以满足如下几个场景需求:
前置页面预加载其他页面或活动页
修改后动态更新缓存
清除本地缓存
以上就是webso方案的主要内容。

Q&A
Q1:缓存H5是怎么实现的,因为除了html以外还有js css 图片等资源,这些是否都需要一并缓存?
A1:webso方案主要关注的还是H5直出的html内容部分,其他静态资源还是依赖浏览器缓存,但是目前客户端也提供了基于客户端缓存的技术方案,与webso类似。
Q2: 是选用hybird方式,还是一步到位选用react native方式?
A2:其实QQ空间也面临这样的问题,但是目前来看,rn的开发效率还是没有达到期望的效率,QQ空间也在实验阶段,目前hybrid页面还是主力。
Q3: 有哪些常用的hybird框架?
A3:其实,空间的hybrid页面会尽量追求简单纯粹一点,功能会相对单一一点,作为客户端的一个补充,不会用比较重的框架,一般也是用一些基础组件或工具库就可以解决。
Q4: 选用hybird方式后,是招新同学开始hybird开发,还是从现有app开发转过去?你们是怎么做的?
A4: 我们hybrid的页面开发是有原来的PC web前端开发来承担的, app会提供基础接口,hybrid页面的开发和客户端的开发是解耦的。
*此分享由QQ空间的崔进在极牛线上技术分享群里所分享

【下期重磅预告】
<主题>
【React+Redux 性能优化实践 】
<讲师>
刘华清,GrowingIO 核心工程师,负责 GrowingIO 整个前端工程的搭建。
<大纲>
1.What is React + Redux?
2.Why do we use React + Redux?
3.How do we use React + Redux?
4.Solution for higher performance

有意加入的技术朋友,请在极牛公众号(ji-niu)里回复“技术分享”

    原文作者:极牛
    原文地址: https://segmentfault.com/a/1190000007042914
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞