昨天測(cè)試iOS的收入頁(yè),切換賬號(hào)后點(diǎn)擊提現(xiàn)button直接跳轉(zhuǎn)到了上次用戶進(jìn)入的收入頁(yè)面,問(wèn)題來(lái)了,切換了賬號(hào),又進(jìn)入了之前的頁(yè)面,是緩存影響的么?和開(kāi)發(fā)溝通了下的確是這樣,web view對(duì)h5資源頁(yè)面有緩存,所以沒(méi)有直接請(qǐng)求服務(wù)端,首次進(jìn)來(lái)拿的本地緩存,處理方式為強(qiáng)制刷新,那么h5有怎么樣的緩存機(jī)制呢?
以下是網(wǎng)上看到的h5機(jī)制,還是蠻全面的,轉(zhuǎn)載分享下:
https://zhuanlan.zhihu.com/p/27456323
本文來(lái)自于騰訊Bugly公眾號(hào)(weixinBugly),未經(jīng)作者同意,請(qǐng)勿轉(zhuǎn)載,原文地址:H5 和移動(dòng)端 WebView 緩存機(jī)制解析與實(shí)戰(zhàn)
作者:葉建升
個(gè)人主頁(yè):http://www.linkedin.com/in/jiansheng-ye-b3319778/
導(dǎo)語(yǔ)
在web項(xiàng)目開(kāi)發(fā)中,我們可能都曾碰到過(guò)這樣一個(gè)棘手的問(wèn)題:
線上項(xiàng)目需要更新一個(gè)有問(wèn)題的資源(可能是圖片,js,css,json數(shù)據(jù)等),這個(gè)資源已經(jīng)發(fā)布了很長(zhǎng)一段時(shí)間,為什么頁(yè)面在瀏覽器里打開(kāi)還是沒(méi)有看到更新?
有些web開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)應(yīng)該馬上會(huì)想到,可能是資源發(fā)布出了岔子導(dǎo)致沒(méi)有實(shí)際發(fā)布成功,更大的可能是老的資源被緩存了。說(shuō)到web緩存,首先我們要弄清它是什么。Web緩存可以理解為Web資源在Web服務(wù)器和客戶端(瀏覽器)的副本,其作用體現(xiàn)在減少網(wǎng)絡(luò)帶寬消耗、降低服務(wù)器壓力和減少網(wǎng)絡(luò)延遲,加快頁(yè)面打開(kāi)速度等方面(筆者在香港求學(xué)期間看到港臺(tái)地區(qū)將cache譯為“快取”,除了讀音相近,大概就是貼近這層含義)。他們通常還會(huì)告訴你:ctrl+F5強(qiáng)刷一下,但是本文下面的內(nèi)容將會(huì)說(shuō)明為什么強(qiáng)制刷新在去除緩存上不總是能奏效的,更何況對(duì)于線上項(xiàng)目而言,總不能讓所有已經(jīng)訪問(wèn)過(guò)的用戶擼起袖子岔開(kāi)兩個(gè)手指都強(qiáng)制刷新一下吧?
同時(shí),當(dāng)前原生 + html5的混合模式移動(dòng)應(yīng)用(hybrid APP)因可大幅降低移動(dòng)應(yīng)用的開(kāi)發(fā)成本,并且可在用戶桌面形成獨(dú)立入口以及有接近原生應(yīng)用的體驗(yàn)而大行其道,APP內(nèi)嵌h5應(yīng)用的開(kāi)發(fā)也是本人現(xiàn)在工作內(nèi)容重要的一部分,本文將從實(shí)際項(xiàng)目開(kāi)發(fā)中遇到的問(wèn)題出發(fā),一窺html5和app內(nèi)webview的緩存機(jī)制真容。
一、協(xié)議緩存
回到開(kāi)頭的那個(gè)問(wèn)題,更新了一張圖片,發(fā)布之后反復(fù)重新進(jìn)頁(yè)面總是看不到更新,這是為什么呢?
這里我們假設(shè)已經(jīng)排除了資源沒(méi)有發(fā)布成功過(guò)的情況,那么第一步,我們可能會(huì)認(rèn)為是http協(xié)議緩存(也稱為瀏覽器緩存或者網(wǎng)頁(yè)緩存)。
http協(xié)議緩存機(jī)制是指通過(guò) HTTP 協(xié)議頭里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段來(lái)控制文件緩存的機(jī)制。
Cache-Control用于控制文件在本地緩存有效時(shí)長(zhǎng)。最常見(jiàn)的,比如服務(wù)器回包:Cache-Control:max-age=600 表示文件在本地應(yīng)該緩存,且有效時(shí)長(zhǎng)是600秒(從發(fā)出請(qǐng)求算起)。在接下來(lái)600秒內(nèi),如果有請(qǐng)求這個(gè)資源,瀏覽器不會(huì)發(fā)出 HTTP 請(qǐng)求,而是直接使用本地緩存的文件。
Last-Modified是標(biāo)識(shí)文件在服務(wù)器上的最新更新時(shí)間。下次請(qǐng)求時(shí),如果文件緩存過(guò)期,瀏覽器通過(guò) If-Modified-Since 字段帶上這個(gè)時(shí)間,發(fā)送給服務(wù)器,由服務(wù)器比較時(shí)間戳來(lái)判斷文件是否有修改。如果沒(méi)有修改,服務(wù)器返回304告訴瀏覽器繼續(xù)使用緩存;如果有修改,則返回200,同時(shí)返回最新的文件。
Cache-Control 通常與 Last-Modified 一起使用。一個(gè)用于控制緩存有效時(shí)間,一個(gè)在緩存失效后,向服務(wù)查詢是否有更新。
Cache-Control 還有一個(gè)同功能的字段:Expires。Expires 的值一個(gè)絕對(duì)的時(shí)間點(diǎn),如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表示在這個(gè)時(shí)間點(diǎn)之前,緩存都是有效的。
Expires 是 HTTP1.0 標(biāo)準(zhǔn)中的字段,Cache-Control 是 HTTP1.1 標(biāo)準(zhǔn)中新加的字段,功能一樣,都是控制緩存的有效時(shí)間。當(dāng)這兩個(gè)字段同時(shí)出現(xiàn)時(shí),Cache-Control 是高優(yōu)化級(jí)的。
Etag 也是和 Last-Modified 一樣,對(duì)文件進(jìn)行標(biāo)識(shí)的字段。不同的是,Etag 的取值是一個(gè)對(duì)文件進(jìn)行標(biāo)識(shí)的特征字串。在向服務(wù)器查詢文件是否有更新時(shí),瀏覽器通過(guò) If-None-Match 字段把特征字串發(fā)送給服務(wù)器,由服務(wù)器和文件最新特征字串進(jìn)行匹配,來(lái)判斷文件是否有更新。沒(méi)有更新回包304,有更新回包200。Etag 和 Last-Modified 可根據(jù)需求使用一個(gè)或兩個(gè)同時(shí)使用。兩個(gè)同時(shí)使用時(shí),只要滿足基中一個(gè)條件,就認(rèn)為文件沒(méi)有更新。

一個(gè)比較形象的理解:
翠花:狗蛋,你幾歲了?
狗蛋:我18歲了。(200)
翠花記住了狗蛋18歲(200 from cache)
=================================
翠花:狗蛋 ,你幾歲了?我猜你18歲。
狗蛋:靠,知道還問(wèn)我!(304)
=================================
翠花:狗蛋 ,你幾歲了?我猜你18歲。
狗蛋:翠花 ,我已經(jīng)19歲了。(200)
不過(guò)有兩種情況比較特殊:
手動(dòng)刷新頁(yè)面(F5),瀏覽器會(huì)直接認(rèn)為緩存已經(jīng)過(guò)期(可能緩存還沒(méi)有過(guò)期),在請(qǐng)求中加上字段:Cache-Control:max-age=0,發(fā)包向服務(wù)器查詢是否有文件是否有更新。
強(qiáng)制刷新頁(yè)面(Ctrl+F5),瀏覽器會(huì)直接忽略本地的緩存(有緩存也會(huì)認(rèn)為本地沒(méi)有緩存),在請(qǐng)求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),發(fā)包向服務(wù)重新拉取文件。
當(dāng)然,各個(gè)瀏覽器對(duì)于刷新和強(qiáng)制刷新的實(shí)現(xiàn)方式也有一些區(qū)別。
那么,如果線上更新了web資源,如何能讓盡快更新呢?(要知道像圖片這樣比較少更新的資源一般緩存時(shí)間都設(shè)置得比較長(zhǎng),比如http://game.gtimg.cn域名下是一天,有問(wèn)題的圖片在用戶側(cè)緩存這么長(zhǎng)時(shí)間是不可接受的)
方法一 修改請(qǐng)求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">
方法三:添加隨機(jī)參數(shù):
對(duì)于圖片或者css,可使用如下方式:
<img src="./data/avatar_mingpian_bak.jpg?rand=h9xqeI" width="156" height="98">
對(duì)于js則可以直接使用時(shí)間戳:
<img src="./data/avatar_mingpian_bak.jpg?rand=h9xqeI" width="156" height="98">
二、應(yīng)用緩存
除了http協(xié)議緩存,HTML5 提供一種應(yīng)用程序緩存機(jī)制,使得基于web的應(yīng)用程序可以離線運(yùn)行。為了能夠讓用戶在離線狀態(tài)下繼續(xù)訪問(wèn) Web 應(yīng)用,開(kāi)發(fā)者需要提供一個(gè) cache manifest 文件。這個(gè)文件中列出了所有需要在離線狀態(tài)下使用的資源,瀏覽器會(huì)把這些資源緩存到本地。例如以下頁(yè)面:
<!-- calender.html -->
<!DOCTYPE HTML>
<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>
其對(duì)應(yīng)的 calender.manifest代碼
CACHE MANIFEST?
calender.html?
calender.css?
calender.js
cache manifest 格式遵循以下原則:
首行必須是 CACHE MANIFEST。
其后,每一行列出一個(gè)需要緩存的資源文件名。
可根據(jù)需要列出在線訪問(wèn)的白名單。白名單中的所有資源不會(huì)被緩存,在使用時(shí)將直接在線訪問(wèn)。聲明白名單使用 NETWORK:標(biāo)識(shí)符。
如果在白名單后還要補(bǔ)充需要緩存的資源,可以使用 CACHE:標(biāo)識(shí)符。
如果要聲明某 URI 不能訪問(wèn)時(shí)的替補(bǔ) URI,可以使用 FALLBACK:標(biāo)識(shí)符。其后的每一行包含兩個(gè) URI,當(dāng)?shù)谝粋€(gè) URI 不可訪問(wèn)時(shí),瀏覽器將嘗試使用第二個(gè) URI。
注釋要另起一行,以 # 號(hào)開(kāi)頭。
例如以下manifest文件:
CACHE MANIFEST?
# 上一行是必須書(shū)寫?
images/sound-icon.png images/background.png NETWORK: comm.cgi?
# 下面是另一些需要緩存的資源,在這個(gè)示例中只有一個(gè) css 文件。?
CACHE:?
style/default.css?
FALLBACK:?
/files/projects /projects
那么,如果使用了應(yīng)用緩存,應(yīng)該如何去更新呢?有以下兩種方式
1、自動(dòng)更新
瀏覽器除了在第一次訪問(wèn) Web 應(yīng)用時(shí)緩存資源外,只會(huì)在 cache manifest 文件本身發(fā)生變化時(shí)更新緩存。而 cache manifest 中的資源文件發(fā)生變化并不會(huì)觸發(fā)更新。
2、手動(dòng)更新
開(kāi)發(fā)者也可以使用 window.applicationCache 的接口更新緩存。方法是檢測(cè) window.applicationCache.status 的值,如果是 UPDATEREADY,那么可以調(diào)用 window.applicationCache.update() 更新緩存。示范代碼如下。
手動(dòng)更新緩存代碼:
if(window.applicationCache.status== window.applicationCache.UPDATEREADY) {? window.applicationCache.update(); }
然而,有時(shí)候雖然應(yīng)用緩存刷新了,但是還是不能看到最新的:那么有可能是使用了本地存儲(chǔ)。常用的本地存儲(chǔ)有DOM Storage和webSQL和indexDB三種
,細(xì)節(jié)可以參考這篇文章《HTML5 Storage Wars - localStorage vs. IndexedDB vs. Web SQL》,這里就不展開(kāi)了,需要注意的是,若使用本地存儲(chǔ),想要清理緩存,除了清理本地存儲(chǔ)文件外,還需要重啟APP,以消除內(nèi)存中的備份。
至此,一個(gè)完成的流程圖就出來(lái)了:

三、移動(dòng)端APP如何支持html5緩存機(jī)制?
筆者現(xiàn)在常會(huì)和移動(dòng)端APP內(nèi)嵌html5頁(yè)面打交道,那么移動(dòng)端hybrid方式開(kāi)發(fā)的APP,如何支持以上的緩存方式呢?
需要了解這些,我們先了解下hybrid方式開(kāi)發(fā)的APP怎么展示網(wǎng)頁(yè)。簡(jiǎn)單得說(shuō)就是使用了webView,那么什么是webView呢?WebView是手機(jī)中內(nèi)置了一款高性能webkit 內(nèi)核瀏覽器,在SDK 中封裝的一個(gè)組件。 沒(méi)有提供地址欄和導(dǎo)航欄,WebView只是單純的展示一個(gè)網(wǎng)頁(yè)界面。簡(jiǎn)單地可以理解為簡(jiǎn)略版的瀏覽器。
安卓端:
1、網(wǎng)頁(yè)緩存:
在data/應(yīng)用package下生成database與cache兩個(gè)文件夾,請(qǐng)求的Url記錄是保存在webviewCache.db里,而url的內(nèi)容是保存在webviewCache文件夾下。
<1> 緩存構(gòu)成
/data/data/package_name/cache/ /data/data/package_name/database/webview.db /data/data/package_name/database/webviewCache.db
<2> 緩存模式
LOAD_CACHE_ONLY: 不使用網(wǎng)絡(luò),只讀取本地緩存數(shù)據(jù),
LOAD_DEFAULT:根據(jù)cache-control決定是否從網(wǎng)絡(luò)上取數(shù)據(jù),
LOAD_CACHE_NORMAL:API level 17中已經(jīng)廢棄, 從API level 11開(kāi)始作用同- - LOAD_DEFAULT模式,
LOAD_NO_CACHE: 不使用緩存,只從網(wǎng)絡(luò)獲取數(shù)據(jù),
LOAD_CACHE_ELSE_NETWORK,只要本地有,無(wú)論是否過(guò)期,或者no-cache,都使用緩存中的數(shù)據(jù)。
如果一個(gè)頁(yè)面的cache-control為no-cache,在模式LOAD_DEFAULT下,無(wú)論如何都會(huì)從網(wǎng)絡(luò)上取數(shù)據(jù),如果沒(méi)有網(wǎng)絡(luò),就會(huì)出現(xiàn)錯(cuò)誤頁(yè)面;在LOAD_CACHE_ELSE_NETWORK模式下,無(wú)論是否有網(wǎng)絡(luò),只要本地有緩存,都使用緩存。本地沒(méi)有緩存時(shí)才從網(wǎng)絡(luò)上獲取。如果一個(gè)頁(yè)面的cache-control為max-age=60,在兩種模式下都使用本地緩存數(shù)據(jù)。
2、應(yīng)用緩存
根據(jù)setAppCachePath(String appCachePath)提供的路徑,在H5使用緩存過(guò)程中生成的緩存文件。
無(wú)模式選擇,通過(guò)setAppCacheEnabled(boolean flag)設(shè)置是否打開(kāi)。默認(rèn)關(guān)閉,即,H5的緩存無(wú)法使用。如果要手動(dòng)清理緩存,需要找到調(diào)用setAppCachePath(String appCachePath)設(shè)置緩存的路徑,把它下面的文件全部刪除就OK了。
iOS端:
iOS的UIWebView組件不支持html5應(yīng)用程序緩存的方式,對(duì)于協(xié)議緩存,可以使用sdk中的NSURLCache類。NSURLRequest需要一個(gè)緩存參數(shù)來(lái)說(shuō)明它請(qǐng)求的url何如緩存數(shù)據(jù)的,我們先看下它的CachePolicy類型。
NSURLRequestUseProtocolCachePolicy NSURLRequest 默認(rèn)的cache policy,使用Protocol協(xié)議定義,注意這種情況下默認(rèn)緩存時(shí)間是60s
NSURLRequestReloadIgnoringCacheData 忽略緩存直接從原始地址下載。
NSURLRequestReturnCacheDataElseLoad 只有在cache中不存在data時(shí)才從原始地址下載。
NSURLRequestReturnCacheDataDontLoad 只使用cache數(shù)據(jù),如果不存在cache,請(qǐng)求失??;用于沒(méi)有建立網(wǎng)絡(luò)連接離線模式;
NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地和遠(yuǎn)程的緩存數(shù)據(jù),直接從原始地址下載,與NSURLRequestReloadIgnoringCacheData類似。
NSURLRequestReloadRevalidatingCacheData:驗(yàn)證本地?cái)?shù)據(jù)與遠(yuǎn)程數(shù)據(jù)是否相同,如果不同則下載遠(yuǎn)程數(shù)據(jù),否則使用本地?cái)?shù)據(jù)。
處于數(shù)據(jù)安全性的考慮,IOS的應(yīng)用擁有自己獨(dú)立的目錄,用來(lái)寫入應(yīng)用的數(shù)據(jù)或者首選項(xiàng)參數(shù)。應(yīng)用安裝后,會(huì)有對(duì)應(yīng)的home目錄,基于NSURLCache來(lái)實(shí)現(xiàn)數(shù)據(jù)的Cache,NSURLCache會(huì)存放在home內(nèi)的子目錄Library/ Caches下,以Bundle Identifier為文件夾名建立Cache的存放路徑。在xcode下可以管理對(duì)應(yīng)的文件,具體可以參見(jiàn)此文:《關(guān)于 iOS 刪除緩存的那些事兒》

四、總結(jié)
綜上所述,html5緩存主要可以分為http協(xié)議緩存、應(yīng)用緩存、DOM Storage、webSQL和indexedDB幾種方式,針對(duì)不同的方式清理緩存的方式也不盡相同,上文中都有說(shuō)明。同時(shí),在移動(dòng)端webView層,對(duì)html緩存機(jī)制做了支持(從筆者接觸過(guò)的手游和相關(guān)APP來(lái)看,目前使用默認(rèn)緩存機(jī)制的比較多),項(xiàng)目開(kāi)發(fā)過(guò)程中緩存更新和清理方式也需要有針對(duì)性地選擇使用。
參考文獻(xiàn):
《HTML5 Storage Wars - localStorage vs. IndexedDB vs. Web SQL》
《使用 HTML5 開(kāi)發(fā)離線應(yīng)用》
《Android WebView緩存機(jī)制總結(jié)》
《NSURLRequestCachePolicy—iOS緩存策略》