摘要 

为了满足移动终端:节省流量、减少请求、提高客户端性能的需求,我们设计了webapp安装更新程序,把js、css、html和图片这些资源,序列化为字符串存入客户端本地存储,并带上版本号来实现资源细粒度更新。

TAG

webapp 安装启动 性能优化

1、概述

我们认为webapp是一站式的应用,在一个页面里能完成整站的功能。所以,以前通过页面全刷的跳转,现在变成了通过底层框架来支持的局刷和切换动画。为了支持这些功能,会多出不少的代码,再加上app里的功能代码,我们统称为资源,包括底层库js(zepto、iscroll、baiduTemplate等),通用ui组件和app功能性的js、css、html和图片。

如何处理一个页面里的这么多资源,才能降低对性能的影响呢?为此,我们设计了webapp安装更新程序,可以做到减少资源请求,节省流量,提升客户端性能。

 

2、浏览器缓存方案

使用浏览器对资源缓存,这是通常的做法,能快速加载页面,只需设置资源一个较长的过期时间即可,当服务器端资源有更新时,改变资源路径就能使客户端及时获取更新。

但缺点是,在webapp应用场景中,有大量的资源需要被下载,资源请求数可能过多,目前正在开发的贴吧webapp宙斯版,已经有60个请求了,预计会过百,当然我们可以把资源文件合并,但又带来了另外一个问题,下载粒度较大。当更新了一个较小部分,会导致大粒度资源被重新下载。由于手机硬件和手机网络的限制,每多一个连接请求的时间消耗和资源下载导致的流量耗费,都是无线webapp要考虑的重要因素。

另外一个缺点就是只能缓存文件,不能缓存一些常量,比如前端模板html片段。

为此,我们设计的webapp安装更新程序,能够只在一个请求中下载所有资源文件内容。当资源有更新的时候,也是在一个请求中只获取有更新的小粒度文件,并且能在客户端缓存常量,如html片段等。

3、自定义安装更新程序

我们想把资源细粒度的存储在客户端,当资源有更新的时候,客户端能及时同步更新。要解决两个主要问题:1、如何在客户端存储资源;2、如何及时获取更新通知,并执行资源更新。

1)      客户端资源存储

我们把资源序列化为字符串,以键值对的方式存入客户端本地存储(localstorage)。比如基础库zepto.js文件,在本地存储中的key为zglobal/js/base/zepto.js,value就是zepto.js的被压缩后的字符串内容了。同理,css文件,html前端模板都是一样,适合做成base64编码的图片会以base64字符串的形式存在css文件。

在资源被引用的地方,如果是js文件,我们会从本地存储中取出js字符串内容,执行eval函数;如果是css文件,我们会动态创建style元素,包含css内容,追加到HTMLDocument的head部分;如果是html前端模板,就在用到的地方,从本地存储获取前端模板字符串,然后调用模板方法进行渲染。

资源本地存储示例片段:

图一

资源的引用方式sample code:

图二

2)      获取资源更新通知

实现资源更新要依赖于资源版本号。我们把每个资源缓存项都对应一个版本号(文件的md5值的前8位),整站所有资源缓存项对应一个总的版本号(所有资源缓存项的版本号的md5值)。

具体流程是:我们把总版本号存入cookie,在一些入口url的服务器处理程序中,判断客户端cookie是否和当前服务器资源总版本号是否相同,如果相同,则跳过资源更新处理,直接启动app,如果不同,说明有资源更新,此时,客户端发送一个ajax请求,带上当前客户端每个缓存项的版本号,向服务器获取更新项,服务器对比客户端每个缓存项的版本号,就知道客户端哪些资源是需要更新的,哪些资源是被删除了的,因此,服务端就能只返回被更新了的资源。那么,客户端是如何拿到当前客户端每个缓存项的版本号呢?其实是依赖于服务器响应客户端ajax资源更新请求时,会包含每个更新了的资源缓存项的名称、版本号和资源内容字符串,客户端拿到这个结果,会更新本地存储中专门用来记录更新后每个资源项的版本号的一个本地存储项,以一个json对象序列化后的字符串形式存在,因此客户端发送的ajax请求,就是直接带上这个json字符串即可。

程序流程:

图三

图三中的totalItemVersions就是所有细粒度资源缓存项的版本号记录。在客户端本地存储中的key为totalItemVersions,value是{“zglobal/html/frsMostTemplate.html”:”d6670285″,”zglobal/js/base/ajaxStore.js”:”68ce034f”,”zglobal/js/base/appBaseController.js”:”4083dd27″,”zglobal/js/base/appBaseModel.js”:”f1f806f1″,”zglobal/css/common/app.css”:”66153242″,”zglobal/css/common/base.css”:”2731733d”……….}

通过以上,我们能发现,当服务器端无资源更新时,启动的webapp将不会有网络资源请求,直接加载本地资源,然后启动app,后续只是app动态的json数据交互;当服务器有资源更新时,不管被更新的资源的多少,客户端都是通过一个请求获取仅更新的资源,然后加载资源,并启动app。

4、本地build

了解了前面整个客户端和服务器的交互之后,我们可能会关心,这些资源在服务器端是如何生成并存储的?这依赖于一个本地build过程。

在贴吧的前端开发中,是按不同模块来开发的,不同模块包含不同的功能,这样能减少大家同时修改带来的冲突。最后的上线,也是按修改的哪些模块,只上线相应的模块修改即可。

每个模块下都会有本地build脚本,只负责本模块的build工作。对于安装更新这块来讲,本地build输出两个文件,moduleVersion.php和cache.php。

cache.php就是当前模块下所有的资源缓存项,每个项的version就是资源文件的md5值的前8位,cache.php文件如下:

图四

moduleVersion.php表示当前模块的版本号,文件如下:

 

图五

模块版本号的计算方法是当前模块下所有缓存项的版本号的总连接字符串的md5的前八位。这样当任何一个资源文件有修改的话,资源文件的version将会改变,moduleVersion的值也会改变。

在“获取资源更新通知”一节中,提到了整站所有资源缓存项的总版本号,这个版本号的计算方法是,所有模块的版本号的总的连接字符串的md5。

图六

5、使用建议

如果大家也在做无线webapp,建议不妨试试这种资源处理方式,将会有更少的资源请求,并且让资源更快加载,给用户带来更好的体验。

by gaofei