H5和移动端WebView缓存机制解析与实战
本⽂来⾃于腾讯Bugly(weixinBugly),未经作者同意,请勿转载,原⽂地址:
作者:叶建升
个⼈主页:
导语
在web项⽬开发中,我们可能都曾碰到过这样⼀个棘⼿的问题:
线上项⽬需要更新⼀个有问题的资源(可能是图⽚,js,css,json数据等),这个资源已经发布了很长⼀段时间,为什么页⾯在浏览器⾥打开还是没有看到更新?
有些web开发经验的同学应该马上会想到,可能是资源发布出了岔⼦导致没有实际发布成功,更⼤的可能是⽼的资源被缓存了。说到web缓存,⾸先我们要弄清它是什么。Web缓存可以理解为Web资源在Web服务器和客户端(浏览器)的副本,其作⽤体现在减少⽹络带宽消耗、降低服务器压⼒和减少⽹络延迟,加快页⾯打开速度等⽅⾯(笔者在⾹港求学期间看到港台地区将cache译为“快取”,除了读⾳相近,⼤概就是贴近这层含义)。他们通常还会告诉你:ctrl+F5强刷⼀下,但是本⽂下⾯的内容将会说明为什么强制刷新
在去除缓存上不总是能奏效的,更何况对于线上项⽬⽽⾔,总不能让所有已经访问过的⽤户撸起袖⼦岔开两个⼿指都强制刷新⼀下吧?
同时,当前原⽣ + html5的混合模式移动应⽤(hybrid APP)因可⼤幅降低移动应⽤的开发成本,并且可在⽤户桌⾯形成独⽴⼊⼝以及有接近原⽣应⽤的体验⽽⼤⾏其道,APP内嵌h5应⽤的开发也是本⼈现在⼯作内容重要的⼀部分,本⽂将从实际项⽬开发中遇到的问题出发,⼀窥html5和app内webview的缓存机制真容。
⼀、协议缓存
回到开头的那个问题,更新了⼀张图⽚,发布之后反复重新进页⾯总是看不到更新,这是为什么呢?
这⾥我们假设已经排除了资源没有发布成功过的情况,那么第⼀步,我们可能会认为是http协议缓存(也称为浏览器缓存或者⽹页缓存)。
http协议缓存机制是指通过 HTTP 协议头⾥的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段来控制⽂件缓存的机制。
Cache-Control ⽤于控制⽂件在本地缓存有效时长。最常见的,⽐如服务器回包:Cache-Control:max-age=600 表⽰⽂件在本地应该缓存,且有效时长是600秒(从发出请求算起)。在接下来600秒内,如
果有请求这个资源,浏览器不会发出 HTTP 请求,⽽是直接使⽤本地缓存的⽂件。
Last-Modified 是标识⽂件在服务器上的最新更新时间。下次请求时,如果⽂件缓存过期,浏览器通过 If-Modified-Since 字段带上这个时间,发送给服务器,由服务器⽐较时间戳来判断⽂件是否有修改。如果没有修改,服务器返回304告诉浏览器继续使⽤缓存;如果有修改,则返回200,同时返回最新的⽂件。
Cache-Control 通常与 Last-Modified ⼀起使⽤。⼀个⽤于控制缓存有效时间,⼀个在缓存失效后,向服务查询是否有更新。
Cache-Control 还有⼀个同功能的字段:Expires。Expires 的值⼀个绝对的时间点,如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表⽰在这个时间点之前,缓存都是有效的。
Expires 是 HTTP1.0 标准中的字段,Cache-Control 是 HTTP1.1 标准中新加的字段,功能⼀样,都是控制缓存的有效时间。当这两个字段同时出现时,Cache-Control 是⾼优化级的。
Etag 也是和 Last-Modified ⼀样,对⽂件进⾏标识的字段。不同的是,Etag 的取值是⼀个对⽂件进⾏标识的特征字串。在向服务器查询⽂件是否有更新时,浏览器通过 If-None-Match 字段把特征字串发送给服务器,由服务器和⽂件最新特征字串进⾏匹配,来判断⽂件是否有更新。没有更新回包304,有更新回包200。Etag 和 Last-Modified 可根据需求使⽤⼀个或两个同时使⽤。两个同时使⽤时,只要满⾜基中⼀个条件,就认为⽂件没有更新。
⼀个⽐较形象的理解:
翠花:狗蛋,你⼏岁了?
狗蛋:我18岁了。(200)
翠花记住了狗蛋18岁(200 from cache)
=================================
翠花:狗蛋,你⼏岁了?我猜你18岁。
狗蛋:靠,知道还问我!(304)
=================================
翠花:狗蛋,你⼏岁了?我猜你18岁。
狗蛋:翠花,我已经19岁了。(200)
不过有两种情况⽐较特殊:
1. ⼿动刷新页⾯(F5),浏览器会直接认为缓存已经过期(可能缓存还没有过期),在请求中加上字段:Cache-Control:max-age=0,发
包向服务器查询是否有⽂件是否有更新。
2. 强制刷新页⾯(Ctrl+F5),浏览器会直接忽略本地的缓存(有缓存也会认为本地没有缓存),在请求中加上字段:Cache-Control:no-
cache(或 Pragma:no-cache),发包向服务重新拉取⽂件。
当然,各个浏览器对于刷新和强制刷新的实现⽅式也有⼀些区别。
那么,如果线上更新了web资源,如何能让尽快更新呢?(要知道像图⽚这样⽐较少更新的资源⼀般缓存时间都设置得⽐较长,⽐如img域名下是⼀天,有问题的图⽚在⽤户侧缓存这么长时间是不可接受的)
⽅法⼀修改请求header头,⽐如php添加:
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
⽅法⼆修改html的head块:
<META HTTP-EQUIV="pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">
<META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT">
<META HTTP-EQUIV="expires" CONTENT="0">
⽅法三:添加随机参数:
对于图⽚或者css,可使⽤如下⽅式:
<img src="./data/avatar_mingpian_bak.jpg?rand=h9xqeI"  width="156" height="98">
对于js则可以直接使⽤时间戳:
<script language="javascript" src="UILib/Common/Common.js?time=new Date()">
⼆、应⽤缓存
除了http协议缓存,HTML5 提供⼀种应⽤程序缓存机制,使得基于web的应⽤程序可以离线运⾏。为了能够让⽤户在离线状态下继续访问Web 应⽤,开发者需要提供⼀个 cache manifest ⽂件。这个⽂件中列出了所有需要在离线状态下使⽤的资源,浏览器会把这些资源缓存到本地。例如以下页⾯:
<!-- calender.html -->
<!DOCTYPE HTML>
网页app<html manifest="calender.manifest">
<head>
<title>calender</title>
<script src="calender.js"></script>
<link rel="stylesheet" href="calender.css">
</head>
<body>
<p>The time is: <output id="calender"></output></p>
</body>
</html>
其对应的 calender.manifest代码
CACHE MANIFEST
calender.html
calender.css
calender.js
cache manifest 格式遵循以下原则:
1. ⾸⾏必须是 CACHE MANIFEST。
2. 其后,每⼀⾏列出⼀个需要缓存的资源⽂件名。
3. 可根据需要列出在线访问的⽩名单。⽩名单中的所有资源不会被缓存,在使⽤时将直接在线访问。声明⽩名单使⽤ NETWORK:标识
符。
4. 如果在⽩名单后还要补充需要缓存的资源,可以使⽤ CACHE:标识符。
5. 如果要声明某 URI 不能访问时的替补 URI,可以使⽤ FALLBACK:标识符。其后的每⼀⾏包含两个 URI,当第⼀个 URI 不可访问
时,浏览器将尝试使⽤第⼆个 URI。
6. 注释要另起⼀⾏,以 # 号开头。
例如以下manifest⽂件:
CACHE MANIFEST
# 上⼀⾏是必须书写
images/sound-icon.png
images/background.png
NETWORK:
# 下⾯是另⼀些需要缓存的资源,在这个⽰例中只有⼀个 css ⽂件。
CACHE:
style/default.css
FALLBACK:
/files/projects /projects
那么,如果使⽤了应⽤缓存,应该如何去更新呢?有以下两种⽅式
1、⾃动更新
浏览器除了在第⼀次访问 Web 应⽤时缓存资源外,只会在 cache manifest ⽂件本⾝发⽣变化时更新缓存。⽽ cache manifest 中的资源⽂件发⽣变化并不会触发更新。
2、⼿动更新
开发者也可以使⽤ window.applicationCache 的接⼝更新缓存。⽅法是检测 window.applicationCache.status 的值,如果是UPDATEREADY,那么可以调⽤ window.applicationCache.update() 更新缓存。⽰范代码如下。
⼿动更新缓存代码:
if(window.applicationCache.status== window.applicationCache.UPDATEREADY)
window.applicationCache.update();
然⽽,有时候虽然应⽤缓存刷新了,但是还是不能看到最新的:那么有可能是使⽤了本地存储。常⽤的本地存储有DOM Storage和webSQL 和indexDB三种
,细节可以参考这篇⽂章,这⾥就不展开了,需要注意的是,若使⽤本地存储,想要清理缓存,除了清理本地存储⽂件外,还需要重启APP,以消除内存中的备份。
⾄此,⼀个完成的流程图就出来了:
三、移动端APP如何⽀持html5缓存机制?
笔者现在常会和移动端APP内嵌html5页⾯打交道,那么移动端hybrid⽅式开发的APP,如何⽀持以上的缓存⽅式呢?
需要了解这些,我们先了解下hybrid⽅式开发的APP怎么展⽰⽹页。简单得说就是使⽤了webView,那么什么是webView呢?WebView是⼿机中内置了⼀款⾼性能webkit 内核浏览器,在SDK 中封装的⼀个组件。没有提供地址栏和导航栏,WebView只是单纯的展⽰⼀个⽹页界⾯。简单地可以理解为简略版的浏览器。
安卓端:
1、⽹页缓存:
在data/应⽤package下⽣成database与cache两个⽂件夹,请求的Url记录是保存在webviewCache.db⾥,⽽url的内容是保存在webviewCache⽂件夹下。
<1> 缓存构成
/data/data/package_name/cache/
/data/data/package_name/database/webview.db
/data/data/package_name/database/webviewCache.db
<2> 缓存模式
LOAD_CACHE_ONLY:不使⽤⽹络,只读取本地缓存数据,
LOAD_DEFAULT:根据cache-control决定是否从⽹络上取数据,
LOAD_CACHE_NORMAL:API level 17中已经废弃, 从API level 11开始作⽤同- - LOAD_DEFAULT模式,
LOAD_NO_CACHE: 不使⽤缓存,只从⽹络获取数据,
LOAD_CACHE_ELSE_NETWORK,只要本地有,⽆论是否过期,或者no-cache,都使⽤缓存中的数据。
如果⼀个页⾯的cache-control为no-cache,在模式LOAD_DEFAULT下,⽆论如何都会从⽹络上取数据,如果没有⽹络,就会出现错误页⾯;在LOAD_CACHE_ELSE_NETWORK模式下,⽆论是否有⽹络,只要本地有缓存,都使⽤缓存。本地没有缓存时才从⽹络上获取。如果⼀个页⾯的cache-control为max-age=60,在两种模式下都使⽤本地缓存数据。
2、应⽤缓存
根据setAppCachePath(String appCachePath)提供的路径,在H5使⽤缓存过程中⽣成的缓存⽂件。
⽆模式选择,通过setAppCacheEnabled(boolean flag)设置是否打开。默认关闭,即,H5的缓存⽆法使⽤。如果要⼿动清理缓存,需要到
调⽤setAppCachePath(String appCachePath)设置缓存的路径,把它下⾯的⽂件全部删除就OK了。
iOS端:
iOS的UIWebView组件不⽀持html5应⽤程序缓存的⽅式,对于协议缓存,可以使⽤sdk中的NSURLCache类。NSURLRequest需要⼀个缓存参数来说明它请求的url何如缓存数据的,我们先看下它的CachePolicy类型。
1. NSURLRequestUseProtocolCachePolicy NSURLRequest 默认的cache policy,使⽤Protocol协议定义,注意这种情况下默认缓存时
间是60s
2. NSURLRequestReloadIgnoringCacheData 忽略缓存直接从原始地址下载。
3. NSURLRequestReturnCacheDataElseLoad 只有在cache中不存在data时才从原始地址下载。
4. NSURLRequestReturnCacheDataDontLoad 只使⽤cache数据,如果不存在cache,请求失败;⽤于没有建⽴⽹络连接离线模式;
5. NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地和远程的缓存数据,直接从原始地址下载,与
NSURLRequestReloadIgnoringCacheData类似。
6. NSURLRequestReloadRevalidatingCacheData:验证本地数据与远程数据是否相同,如果不同则下载远程数据,否则使⽤本地数据。处于数据安全性的考虑,IOS的应⽤拥有⾃⼰独⽴的⽬录,⽤来写⼊应⽤的数据或者⾸选项参数。应⽤安装后,会有对应的home⽬录,基于NSURLCache来实现数据的Ca
che,NSURLCache会存放在home内的⼦⽬录Library/ Caches下,以Bundle Identifier为⽂件夹名建⽴Cache的存放路径。在xcode下可以管理对应的⽂件,具体可以参见此⽂:
四、总结
综上所述,html5缓存主要可以分为http协议缓存、应⽤缓存、DOM Storage、webSQL和indexedDB⼏种⽅式,针对不同的⽅式清理缓存的⽅式也不尽相同,上⽂中都有说明。同时,在移动端webView层,对html缓存机制做了⽀持(从笔者接触过的⼿游和相关APP来看,⽬前使⽤默认缓存机制的⽐较多),项⽬开发过程中缓存更新和清理⽅式也需要有针对性地选择使⽤。
参考⽂献:
更多精彩内容欢迎关注的公众账号:
是⼀款专为移动开发者打造的质量监控⼯具,帮助开发者快速,便捷的定位线上应⽤崩溃的情况以及解决⽅案。智能合并功能帮助开发同学把每天上报的数千条根据根因合并分类,每⽇⽇报会列出影响⽤户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码⾏,实时上报可以在发布后快速的了解应⽤的质量情况,适配最新的 iOS, Android 官⽅操作系统,鹅⼚的⼯程师都在使⽤,快来加⼊我们吧!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。