![]() |
1.4 客户端缓存 |
客户端缓存相对于其他端的缓存而言,要简单一些,而且通常是和服务端以及网络侧的应用或缓存配合使用的。对于互联网应用而言,也就是通常所说的BS架构应用,可以分为页面缓存和浏览器缓存。对于移动互联网应用而言,是指APP自身所使用的缓存。
页面缓存有两层含义:一个是页面自身对某些元素或全部元素进行缓存;另一层意思是服务端将静态页面或动态页面的元素进行缓存,然后给客户端使用。这里的页面缓存指的是页面自身的缓存或者离线应用缓存。
页面缓存是将之前渲染的页面保存为文件,当用户再次访问时可以避开网络连接,从而减少负载,提升性能和用户体验。随着单页面应用(Single Page Application,SPA)的广泛使用,加之HTML5支持了离线缓存和本地存储,大部分BS应用的页面缓存都可以举重若轻了。在HTML5中使用本地缓存的方法也很简单,示例代码如下:
localStorage.setItem("mykey","myvalue") localStorage.getItem("mykey","myvalue") localStorage.removeItem("mykey") localStorage.clear()
HTML5提供的离线应用缓存机制,使得网页应用可以离线使用,这种机制在浏览器上支持度非常广,可以放心地使用该特性来加速页面的访问。开启离线缓存的步骤如下:
1)准备用于描述页面需要缓存的资源列表清单文件(manifest text/cache-manifest)。
2)在需要离线使用的页面中添加manifest属性,指定缓存清单文件的路径。
离线缓存的工作流如图1-4所示。
图1-4 HTML5离线缓存的工作流程示意
由图1-4可知:
1)当浏览器访问了一个包含manifest属性的页面时,如果应用的缓存不存在,浏览器会加载文档,获取所有在清单文件中列出的文件,生成初始缓存。
2)当对该文档再次访问时,浏览器会直接从应用缓存中加载页面以及在清单文件中列出的资源。同时,浏览器还会向window.applicationCache对象发送一个表示检查的事件,以获取清单文件。
3)如果当前缓存的清单副本是最新的,浏览器将向window.applicationCache对象发送一个表示无须更新的事件,从而结束更新过程。如果在服务端修改了任何缓存资源,必须同时修改清单文件,这样浏览器才能知道要重新获取资源。
4)如果清单文件已经改变,那么文件中列出的所有文件会被重新获取并放到一个临时缓存中。对于每个加入到临时缓存中的文件,浏览器会向window.applicationCache对象发送一个表示进行中的事件。
5)一旦所有文件都获取成功,它们会自动移动到真正的离线缓存中,并向window.applicationCache对象发送一个表示已经缓存的事件。鉴于文档早已经从缓存加载到浏览器中,所以更新后的文档不会重新渲染,直到页面重新加载。
需要注意的是:manifest文件中列出的资源URL必须和manifest本身使用同样的网络协议,详情可参考W3C相关的标准文档。
浏览器缓存是根据一套与服务器约定的规则进行工作的,工作规则很简单:检查以确保副本是最新的,通常只要一次会话。浏览器会在硬盘上专门开辟一个空间来存储资源副本作为缓存。在用户触发“后退”操作或点击一个之前看过的链接的时候,浏览器缓存会很管用。同样,如果访问系统中的同一张图片,该图片可以从浏览器缓存中调出并几乎立即显现出来。
对浏览器而言,HTTP1.0提供了一些很基本的缓存特性,例如在服务器侧设置Expires的HTTP头来告诉客户端在重新请求文件之前缓存多久是安全的,可以通过if-modified-since的条件请求来使用缓存。其中,发送的时间是文件最初被下载的时间,而不是即将过期的时间,如果文件没有改变,服务器可以用304-Not Modified来应答。客户端收到304代码,就可以使用缓存的文件版本了。
HTTP 1.1有了较大的增强,缓存系统被形式化了,引入了实体标签e-tag。e-tag是文件或对象的唯一标识,这意味着可以请求一个资源,以及提供所持有的文件,然后询问服务器这个文件是否有变化。如果某一个文件的e-tag是有效的,那么服务器会生成304-Not Modified应答,并提供正确文件的e-tag,否则,发送200-OK应答。以Web浏览器使用e-tag为例,如图1-5所示。
图1-5 浏览器使用e-tag的流程示意
在配置了Last-Modified/ETag的情况下,浏览器再次访问统一URI的资源时,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304回给浏览器,浏览器则直接从本地缓存取数据;如果数据有变化,就将整个数据重新发给浏览器。
Last-Modified/ETag与Cache-Control/Expires的作用是不一样的,如果检测到本地的缓存还在有效的时间范围内,浏览器则直接使用本地缓存,不会发送任何请求。两者一起使用时,Cache-Control/Expires的优先级要高于Last-Modified/ETag。即当本地副本根据Cache-Control/Expires发现还在有效期内时,则不会再次发送请求去服务器询问修改时间(Last-Modified)或实体标识(e-tag)了。
Cache-Control与Expires的功能一致,都是指明当前资源的有效期,控制浏览器是直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。
一般情况下,使用Cache-Control/Expires会配合Last-Modified/ETag一起使用,因为即使服务器设置缓存时间,当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时Last-Modified/ETag将能够很好利用服务端的返回码304,从而减少响应开销。
通过在HTML页面的节点中加入meta标签,可以告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。代码如下:
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
令人遗憾的是,只有部分浏览器可以支持这一用法,而且一般缓存代理服务器都不支持,因为代理不解析HTML内容本身。
浏览器缓存能够极大地提升终端用户的体验,那么,用户在使用浏览器的时候,会有各种操作,如输入地址后回车、按F5刷新等等,这些行为对缓存的影响如表1-1所示。
表1-1 用户的浏览器操作对缓存的影响
尽管混合编程(hybrid programming)成为时尚,但整个移动互联网目前还是原生应用(以下简称APP)的天下。无论大型或小型APP,灵活的缓存不仅大大减轻了服务器的压力,而且因为更快速的用户体验而方便了用户。如何把APP缓存对于业务组件透明,以及APP缓存数据的及时更新,是APP缓存能否成功应用起来的关键。APP可以将内容缓存在内存、文件或本地数据库(例如SQLite)中,但基于内存的缓存要谨慎使用。
APP使用数据库缓存的方法:在下载完数据文件后,把文件的相关信息,如URL、路径、下载时间、过期时间等存放到数据库,下次下载的时候根据URL先从数据库中查询,如果查询到当前时间并未过期,就根据路径读取本地文件,从而实现缓存的效果。这种方法具有灵活存放文件的属性,进而提供了很大的扩展性,可以为其他的功能提供良好的支持。需要注意的是,要留心数据库缓存的清理机制。
对于APP中的某些界面,可以采用文件缓存的方法。这种方法使用文件操作的相关API得到文件的最后修改时间,与当前时间判断是否过期,从而实现缓存效果,操作简单,代价较低。需要注意的是,不同类型文件的缓存时间不一样。例如,图片文件的内容是相对不变的,直到最终被清理掉,APP可以永远读取缓存中的图片内容。而配置文件中的内容是可能更新的,需要设置一个可接受的缓存时间。同时,不同环境下的缓存时间标准也是不一样的,WiFi网络环境下,缓存时间可以设置短一点,一是网速较快,二是不产生流量费。而在移动数据流量环境下,缓存时间可以设置长一点,节省流量,而且用户体验也更好。
在iOS开发中,SDWebImage是一个很棒的图片缓存框架,主要类组成的结构如图1-6所示。
SDWebImage是个比较大的类库,提供一个UIImageView的类以支持远程加载来自网络的图片,具有缓存管理、异步下载、同一个URL下载次数控制和优化等特征。使用时,只需要在头文件中引入#import"UIImageView+WebCache.h"即可调用异步加载图片方法:
-(void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
URL是图片的地址,placeholder是网络图片在尚未加载成功时显示的图像,SDWebImageOptions是相关选项。默认情况下,SDWebImage会忽略Header中的缓存设置,将图片以URL为key进行保存,URL与图片是一一映射关系。在APP请求同一个URL时,SDWebImage会从缓存中取得图片。将第三个参数设置为SDWebImageRefreshCached就可以实现图片更新操作,例如:
NSURL *url = [NSURL URLWithString:@"http://www.abel.com/image.png"]; UIImage *defaultImage = [UIImage imageNamed:@"mydefault.png"]; [self.imageView setImageWithURL:url placeholderImage:defaultImage options:SDWebImageRefreshCached];
在SDWebImage中有两种缓存,一种是磁盘缓存,一种为内存缓存,框架都提供了相应的清理方法:
图1-6 SDWebImage中主要类组成的结构图
[[[SDWebImageManager sharedManager] imageCache] clearDisk]; [[[SDWebImageManager sharedManager] imageCache] clearMemory];
需要注意的是,在iOS7中,缓存机制做了修改,使用上述两个方法只清除了SDWebImage的缓存,没有清除系统的缓存,所以可以在清除缓存的代理中添加以下代码:
[[NSURLCache sharedURLCache] removeAllCachedResponses];