在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)
不過有兩種情況比較特殊:
手動刷新頁面(F5),瀏覽器會直接認為緩存已經過期(可能緩存還沒有過期),在請求中加上字段:Cache-Control:max-age=0,發包向服務器查詢是否有文件是否有更新。
強制刷新頁面(Ctrl+F5),瀏覽器會直接忽略本地的緩存(有緩存也會認為本地沒有緩存),在請求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),發包向服務重新拉取文件。
當然,各個瀏覽器對于刷新和強制刷新的實現方式也有一些區別。
那么,如果線上更新了web資源,如何能讓盡快更新呢?(要知道像圖片這樣比較少更新的資源一般緩存時間都設置得比較長,比如game.gtimg.cn域名下是一天,有問題的圖片在用戶側緩存這么長時間是不可接受的)
方法一 修改請求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> <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: comm.cgi # 下面是另一些需要緩存的資源,在這個示例中只有一個 css 文件。 CACHE: style/default.css FALLBACK: /files/projects /projects
那么,如果使用了應用緩存,應該如何去更新呢?有以下兩種方式
瀏覽器除了在次訪問 Web 應用時緩存資源外,只會在 cache manifest 文件本身發生變化時更新緩存。而 cache manifest 中的資源文件發生變化并不會觸發更新。
開發者也可以使用 window.applicationCache 的接口更新緩存。方法是檢測 window.applicationCache.status 的值,如果是 UPDATEREADY,那么可以調用 window.applicationCache.update() 更新緩存。示范代碼如下。
手動更新緩存代碼:
if(window.applicationCache.status== window.applicationCache.UPDATEREADY)
{
window.applicationCache.update();
}
然而,有時候雖然應用緩存刷新了,但是還是不能看到新的:那么有可能是使用了本地存儲。常用的本地存儲有DOM Storage和webSQL和indexDB三種,細節可以參考這篇文章《HTML5 Storage Wars - localStorage vs. IndexedDB vs. Web SQL》,這里就不展開了,需要注意的是,若使用本地存儲,想要清理緩存,除了清理本地存儲文件外,還需要重啟APP,以消除內存中的備份。
至此,一個完成的流程圖就出來了:
筆者現在常會和移動端APP內嵌html5頁面打交道,那么移動端hybrid方式開發的APP,如何支持以上的緩存方式呢?
需要了解這些,我們先了解下hybrid方式開發的APP怎么展示網頁。簡單得說就是使用了webView,那么什么是webView呢?WebView是手機中內置了一款高性能webkit 內核瀏覽器,在SDK 中封裝的一個組件。 沒有提供地址欄和導航欄,WebView只是單純的展示一個網頁界面。簡單地可以理解為簡略版的瀏覽器。
在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> 緩存模式
如果一個頁面的cache-control為no-cache,在模式LOAD_DEFAULT下,無論如何都會從網絡上取數據,如果沒有網絡,就會出現錯誤頁面;在LOAD_CACHE_ELSE_NETWORK模式下,無論是否有網絡,只要本地有緩存,都使用緩存。本地沒有緩存時才從網絡上獲取。如果一個頁面的cache-control為max-age=60,在兩種模式下都使用本地緩存數據。
根據setAppCachePath(String appCachePath)提供的路徑,在H5使用緩存過程中生成的緩存文件。
無模式選擇,通過setAppCacheEnabled(boolean flag)設置是否打開。默認關閉,即,H5的緩存無法使用。如果要手動清理緩存,需要找到調用setAppCachePath(String appCachePath)設置緩存的路徑,把它下面的文件全部刪除就OK了。
iOS的UIWebView組件不支持html5應用程序緩存的方式,對于協議緩存,可以使用sdk中的NSURLCache類。NSURLRequest需要一個緩存參數來說明它請求的url何如緩存數據的,我們先看下它的CachePolicy類型。
處于數據安全性的考慮,IOS的應用擁有自己獨立的目錄,用來寫入應用的數據或者首選項參數。應用安裝后,會有對應的home目錄,基于NSURLCache來實現數據的Cache,NSURLCache會存放在home內的子目錄Library/ Caches下,以Bundle Identifier為文件夾名建立Cache的存放路徑。在xcode下可以管理對應的文件,具體可以參見此文:《關于 iOS 刪除緩存的那些事兒》
綜上所述,html5緩存主要可以分為http協議緩存、應用緩存、DOM Storage、webSQL和indexedDB幾種方式,針對不同的方式清理緩存的方式也不盡相同,上文中都有說明。同時,在移動端webView層,對html緩存機制做了支持(從筆者接觸過的手游和相關APP來看,目前使用默認緩存機制的比較多),項目開發過程中緩存更新和清理方式也需要有針對性地選擇使用。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。