H5 和移動端 WebView 緩存機制解析與實戰

本文來自於騰訊Bugly公眾號(weixinBugly),未經作者同意,請勿轉載,原文地址:H5 和移動端 WebView 緩存機制解析與實戰

n

作者:葉建升

個人主頁:linkedin.com/in/jianshe

n

n

導語

n

在web項目開發中,我們可能都曾碰到過這樣一個棘手的問題:

n

線上項目需要更新一個有問題的資源(可能是圖片,js,css,json數據等),這個資源已經發布了很長一段時間,為什麼頁面在瀏覽器里打開還是沒有看到更新?

n

有些web開發經驗的同學應該馬上會想到,可能是資源發布出了岔子導致沒有實際發布成功,更大的可能是老的資源被緩存了。說到web緩存,首先我們要弄清它是什麼。Web緩存可以理解為Web資源在Web伺服器和客戶端(瀏覽器)的副本,其作用體現在減少網路帶寬消耗、降低伺服器壓力和減少網路延遲,加快頁面打開速度等方面(筆者在香港求學期間看到港台地區將cache譯為「快取」,除了讀音相近,大概就是貼近這層含義)。他們通常還會告訴你:ctrl+F5強刷一下,但是本文下面的內容將會說明為什麼強制刷新在去除緩存上不總是能奏效的,更何況對於線上項目而言,總不能讓所有已經訪問過的用戶擼起袖子岔開兩個手指都強制刷新一下吧?

n

同時,當前原生 + html5的混合模式移動應用(hybrid APP)因可大幅降低移動應用的開發成本,並且可在用戶桌面形成獨立入口以及有接近原生應用的體驗而大行其道,APP內嵌h5應用的開發也是本人現在工作內容重要的一部分,本文將從實際項目開發中遇到的問題出發,一窺html5和app內webview的緩存機制真容。

n

一、協議緩存

n

回到開頭的那個問題,更新了一張圖片,發布之後反覆重新進頁面總是看不到更新,這是為什麼呢?

n

這裡我們假設已經排除了資源沒有發布成功過的情況,那麼第一步,我們可能會認為是http協議緩存(也稱為瀏覽器緩存或者網頁緩存)。

n

http協議緩存機制是指通過 HTTP 協議頭裡的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等欄位來控制文件緩存的機制。

n

  • Cache-Control 用於控制文件在本地緩存有效時長。最常見的,比如伺服器回包:Cache-Control:max-age=600 表示文件在本地應該緩存,且有效時長是600秒(從發出請求算起)。在接下來600秒內,如果有請求這個資源,瀏覽器不會發出 HTTP 請求,而是直接使用本地緩存的文件。

    n
  • n

  • Last-Modified 是標識文件在伺服器上的最新更新時間。下次請求時,如果文件緩存過期,瀏覽器通過 If-Modified-Since 欄位帶上這個時間,發送給伺服器,由伺服器比較時間戳來判斷文件是否有修改。如果沒有修改,伺服器返回304告訴瀏覽器繼續使用緩存;如果有修改,則返回200,同時返回最新的文件。

    n
  • n

Cache-Control 通常與 Last-Modified 一起使用。一個用於控制緩存有效時間,一個在緩存失效後,向服務查詢是否有更新。

n

Cache-Control 還有一個同功能的欄位:Expires。Expires 的值一個絕對的時間點,如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表示在這個時間點之前,緩存都是有效的。

n

Expires 是 HTTP1.0 標準中的欄位,Cache-Control 是 HTTP1.1 標準中新加的欄位,功能一樣,都是控制緩存的有效時間。當這兩個欄位同時出現時,Cache-Control 是高優化級的。

n

Etag 也是和 Last-Modified 一樣,對文件進行標識的欄位。不同的是,Etag 的取值是一個對文件進行標識的特徵字串。在向伺服器查詢文件是否有更新時,瀏覽器通過 If-None-Match 欄位把特徵字串發送給伺服器,由伺服器和文件最新特徵字串進行匹配,來判斷文件是否有更新。沒有更新回包304,有更新回包200。Etag 和 Last-Modified 可根據需求使用一個或兩個同時使用。兩個同時使用時,只要滿足基中一個條件,就認為文件沒有更新。

n

一個比較形象的理解:

n

翠花:狗蛋,你幾歲了?

狗蛋:我18歲了。(200)

翠花記住了狗蛋18歲(200 from cache)

n

n

=================================

n

翠花:狗蛋 ,你幾歲了?我猜你18歲。

狗蛋:靠,知道還問我!(304)

n

n

=================================

n

翠花:狗蛋 ,你幾歲了?我猜你18歲。

狗蛋:翠花 ,我已經19歲了。(200)

n

n

不過有兩種情況比較特殊:

n

  1. 手動刷新頁面(F5),瀏覽器會直接認為緩存已經過期(可能緩存還沒有過期),在請求中加上欄位:Cache-Control:max-age=0,發包向伺服器查詢是否有文件是否有更新。

    n
  2. n

  3. 強制刷新頁面(Ctrl+F5),瀏覽器會直接忽略本地的緩存(有緩存也會認為本地沒有緩存),在請求中加上欄位:Cache-Control:no-cache(或 Pragma:no-cache),發包向服務重新拉取文件。

    n
  4. n

當然,各個瀏覽器對於刷新和強制刷新的實現方式也有一些區別。

n

那麼,如果線上更新了web資源,如何能讓儘快更新呢?(要知道像圖片這樣比較少更新的資源一般緩存時間都設置得比較長,比如game.gtimg.cn域名下是一天,有問題的圖片在用戶側緩存這麼長時間是不可接受的)

n

方法一 修改請求header頭,比如php添加:

n

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");n header("Cache-Control: no-cache, must-revalidate");n header("Pragma: no-cache");n

n

方法二 修改html的head塊:

n

<META HTTP-EQUIV="pragma" CONTENT="no-cache">n <META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">n <META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT">n <META HTTP-EQUIV="expires" CONTENT="0">n

n

方法三:添加隨機參數:

n

對於圖片或者css,可使用如下方式:

n

<img src="./data/avatar_mingpian_bak.jpg?rand=h9xqeI" width_="156" height="98">n

n

對於js則可以直接使用時間戳:

n

<script language="javascript" src="UILib/Common/Common.js?time=new Date()">n

n

二、應用緩存

n

除了http協議緩存,HTML5 提供一種應用程序緩存機制,使得基於web的應用程序可以離線運行。為了能夠讓用戶在離線狀態下繼續訪問 Web 應用,開發者需要提供一個 cache manifest 文件。這個文件中列出了所有需要在離線狀態下使用的資源,瀏覽器會把這些資源緩存到本地。例如以下頁面:

n

<!-- calender.html -->n <!DOCTYPE HTML>n <html manifest="calender.manifest">n <head>n <title>calender</title>n <script src="calender.js"></script>n <link rel="stylesheet" href="calender.css">n </head>n <body>n <p>The time is: <output id="calender"></output></p>n </body>n </html>n

n

其對應的 calender.manifest代碼

n

CACHE MANIFESTn calender.htmln calender.cssn calender.jsn

n

cache manifest 格式遵循以下原則:

n

  1. 首行必須是 CACHE MANIFEST。
  2. n

  3. 其後,每一行列出一個需要緩存的資源文件名。
  4. n

  5. 可根據需要列出在線訪問的白名單。白名單中的所有資源不會被緩存,在使用時將直接在線訪問。聲明白名單使用 NETWORK:標識符。
  6. n

  7. 如果在白名單後還要補充需要緩存的資源,可以使用 CACHE:標識符。
  8. n

  9. 如果要聲明某 URI 不能訪問時的替補 URI,可以使用 FALLBACK:標識符。其後的每一行包含兩個 URI,當第一個 URI 不可訪問時,瀏覽器將嘗試使用第二個 URI。
  10. n

  11. 注釋要另起一行,以 # 號開頭。
  12. n

例如以下manifest文件:

n

CACHE MANIFESTn # 上一行是必須書寫n images/sound-icon.pngn images/background.pngn NETWORK:n comm.cgin # 下面是另一些需要緩存的資源,在這個示例中只有一個 css 文件。n CACHE:n style/default.cssn FALLBACK:n /files/projects /projectsn

n

那麼,如果使用了應用緩存,應該如何去更新呢?有以下兩種方式

n

1、自動更新

n

瀏覽器除了在第一次訪問 Web 應用時緩存資源外,只會在 cache manifest 文件本身發生變化時更新緩存。而 cache manifest 中的資源文件發生變化並不會觸發更新。

n

2、手動更新

n

開發者也可以使用 window.applicationCache 的介面更新緩存。方法是檢測 window.applicationCache.status 的值,如果是 UPDATEREADY,那麼可以調用 window.applicationCache.update() 更新緩存。示範代碼如下。

n

手動更新緩存代碼:

n

if(window.applicationCache.status== window.applicationCache.UPDATEREADY)n {n window.applicationCache.update();n }n

n

然而,有時候雖然應用緩存刷新了,但是還是不能看到最新的:那麼有可能是使用了本地存儲。常用的本地存儲有DOM Storage和webSQL和indexDB三種

n

,細節可以參考這篇文章《HTML5 Storage Wars - localStorage vs. IndexedDB vs. Web SQL》,這裡就不展開了,需要注意的是,若使用本地存儲,想要清理緩存,除了清理本地存儲文件外,還需要重啟APP,以消除內存中的備份。

n

至此,一個完成的流程圖就出來了:

n

三、移動端APP如何支持html5緩存機制?

n

筆者現在常會和移動端APP內嵌html5頁面打交道,那麼移動端hybrid方式開發的APP,如何支持以上的緩存方式呢?

n

需要了解這些,我們先了解下hybrid方式開發的APP怎麼展示網頁。簡單得說就是使用了webView,那麼什麼是webView呢?WebView是手機中內置了一款高性能webkit 內核瀏覽器,在SDK 中封裝的一個組件。 沒有提供地址欄和導航欄,WebView只是單純的展示一個網頁界面。簡單地可以理解為簡略版的瀏覽器。

n

安卓端:

n

1、網頁緩存:

n

在data/應用package下生成database與cache兩個文件夾,請求的Url記錄是保存在webviewCache.db里,而url的內容是保存在webviewCache文件夾下。

n

<1> 緩存構成

n

/data/data/package_name/cache/n /data/data/package_name/database/webview.dbn /data/data/package_name/database/webviewCache.dbn

<2> 緩存模式

n

  • LOAD_CACHE_ONLY: 不使用網路,只讀取本地緩存數據,
  • n

  • LOAD_DEFAULT:根據cache-control決定是否從網路上取數據,
  • n

  • LOAD_CACHE_NORMAL:API level 17中已經廢棄, 從API level 11開始作用同- - LOAD_DEFAULT模式,
  • n

  • LOAD_NO_CACHE: 不使用緩存,只從網路獲取數據,
  • n

  • LOAD_CACHE_ELSE_NETWORK,只要本地有,無論是否過期,或者no-cache,都使用緩存中的數據。
  • n

如果一個頁面的cache-control為no-cache,在模式LOAD_DEFAULT下,無論如何都會從網路上取數據,如果沒有網路,就會出現錯誤頁面;在LOAD_CACHE_ELSE_NETWORK模式下,無論是否有網路,只要本地有緩存,都使用緩存。本地沒有緩存時才從網路上獲取。如果一個頁面的cache-control為max-age=60,在兩種模式下都使用本地緩存數據。

n

2、應用緩存

n

根據setAppCachePath(String appCachePath)提供的路徑,在H5使用緩存過程中生成的緩存文件。

n

無模式選擇,通過setAppCacheEnabled(boolean flag)設置是否打開。默認關閉,即,H5的緩存無法使用。如果要手動清理緩存,需要找到調用setAppCachePath(String appCachePath)設置緩存的路徑,把它下面的文件全部刪除就OK了。

n

iOS端:

n

iOS的UIWebView組件不支持html5應用程序緩存的方式,對於協議緩存,可以使用sdk中的NSURLCache類。NSURLRequest需要一個緩存參數來說明它請求的url何如緩存數據的,我們先看下它的CachePolicy類型。

n

  1. NSURLRequestUseProtocolCachePolicy NSURLRequest 默認的cache policy,使用Protocol協議定義,注意這種情況下默認緩存時間是60s
  2. n

  3. NSURLRequestReloadIgnoringCacheData 忽略緩存直接從原始地址下載。
  4. n

  5. NSURLRequestReturnCacheDataElseLoad 只有在cache中不存在data時才從原始地址下載。
  6. n

  7. NSURLRequestReturnCacheDataDontLoad 只使用cache數據,如果不存在cache,請求失敗;用於沒有建立網路連接離線模式;
  8. n

  9. NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地和遠程的緩存數據,直接從原始地址下載,與NSURLRequestReloadIgnoringCacheData類似。
  10. n

  11. NSURLRequestReloadRevalidatingCacheData:驗證本地數據與遠程數據是否相同,如果不同則下載遠程數據,否則使用本地數據。
  12. n

處於數據安全性的考慮,IOS的應用擁有自己獨立的目錄,用來寫入應用的數據或者首選項參數。應用安裝後,會有對應的home目錄,基於NSURLCache來實現數據的Cache,NSURLCache會存放在home內的子目錄Library/ Caches下,以Bundle Identifier為文件夾名建立Cache的存放路徑。在xcode下可以管理對應的文件,具體可以參見此文:《關於 iOS 刪除緩存的那些事兒》

n

四、總結

n

綜上所述,html5緩存主要可以分為http協議緩存、應用緩存、DOM Storage、webSQL和indexedDB幾種方式,針對不同的方式清理緩存的方式也不盡相同,上文中都有說明。同時,在移動端webView層,對html緩存機製做了支持(從筆者接觸過的手游和相關APP來看,目前使用默認緩存機制的比較多),項目開發過程中緩存更新和清理方式也需要有針對性地選擇使用。

n

參考文獻:

n

《HTML Living Standard》

《HTML5 Storage Wars - localStorage vs. IndexedDB vs. Web SQL》

《使用 HTML5 開發離線應用》

《Android WebView緩存機制總結》

《iOS: 聊聊 UIWebView 緩存》

《NSURLRequestCachePolicy—iOS緩存策略》

《H5 緩存機制淺析 - 移動端 Web 載入性能優化》

《關於 iOS 刪除緩存的那些事兒》n

n

更多精彩內容歡迎關注騰訊 Bugly的微信公眾賬號:

nn

騰訊 Bugly是一款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智能合併功能幫助開發同學把每天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同學定位到出問題的代碼行,實時上報可以在發布後快速的了解應用的質量情況,適配最新的 iOS, Android 官方操作系統,鵝廠的工程師都在使用,快來加入我們吧!

推薦閱讀:

TAG:HTML5 | webview | Web开发 |