【Web技術】276- WebView緩存原理分析和應用

前言javascript

混合式開發,在產品體驗以及頁面加載速度的體驗上已經非比以往的。今日早讀文章由@unclechen分享。css

正文從這開始~html

1、背景

如今的App開發,或多或少都會用到Hybrid模式,到了WebView這邊,常常會加載一些js文件(例如和WebView用來Native通訊的bridge.js),而這些js文件不會常常發生變化,因此咱們但願js在WebView裏面加載一次以後,若是js沒有發生變化,下次就不用再發起網絡請求去加載,從而減小流量和資源的佔用。那麼有什麼方式能夠達到這個目的呢?先得從WebView的緩存原理入手。前端

2、WebView的緩存類型

WebView主要包括兩類緩存,一類是瀏覽器自帶的網頁數據緩存,這是全部的瀏覽器都支持的、由HTTP協議定義的緩存;另外一類是H5緩存,這是由web頁面的開發者設置的,H5緩存主要包括了App Cache、DOM Storage、Local Storage、Web SQL Database 存儲機制等,這裏咱們主要介紹App Cache來緩存js文件。java

3、瀏覽器自帶的網頁數據緩存

1.工做原理

瀏覽器緩存機制是經過HTTP協議Header裏的Cache-Control(或Expires)和Last-Modified(或 Etag)等字段來控制文件緩存的機制。關於這幾個字段的做用和瀏覽器的緩存更新機制,你們能夠看看這兩篇文章(H5 緩存機制淺析 移動端 Web 加載性能優化,Android:手把手教你構建 WebView 的緩存機制 & 資源預加載方案),裏面有詳細的介紹。下面從我實際應用的角度,介紹一下一般會在HTTP協議中遇到的Header。android

這兩個字段是接收響應時,瀏覽器決定文件是否須要被緩存;或者須要加載文件時,瀏覽器決定是否須要發出請求的字段。git

Cache-Control:max-age=315360000,這表示緩存時長爲315360000秒。若是315360000秒內須要再次請求這個文件,那麼瀏覽器不會發出請求,直接使用本地的緩存的文件。這是HTTP/1.1標準中的字段。github

Expires: Thu, 31 Dec 2037 23:55:55 GMT,這表示這個文件的過時時間是2037年12月31日晚上23點55分55秒,在這個時間以前瀏覽器都不會再次發出請求去獲取這個文件。這是HTTP/1.0中的字段,若是客戶端和服務器時間不一樣步會致使緩存出現問題,所以纔有了上面的Cache-Control,當它們同時出如今HTTP Response的Header中時,Cache-Control優先級更高。web

下面兩個字段是發起請求時,服務器決定文件是否須要更新的字段。shell

Last-Modified:Wed, 28 Sep 2016 09:24:35 GMT,這表示這個文件最後的修改時間是2016年9月28日9點24分35秒。這個字段對於瀏覽器來講,會在下次請求的時候,做爲Request Header的If-Modified-Since字段帶上。例如瀏覽器緩存的文件已經超過了Cache-Control(或者Expires),那麼須要加載這個文件時,就會發出請求,請求的Header有一個字段爲If-Modified-Since:Wed, 28 Sep 2016 09:24:35 GMT,服務器接收到請求後,會把文件的Last-Modified時間和這個時間對比,若是時間沒變,那麼瀏覽器將返回304 Not Modified給瀏覽器,且content-length確定是0個字節。若是時間有變化,那麼服務器會返回200 OK,並返回相應的內容給瀏覽器。

ETag:」57eb8c5c-129」,這是文件的特徵串。功能同上面的Last-Modified是同樣的。只是在瀏覽器下次請求時,ETag是做爲Request Header中的If-None-Match:」57eb8c5c-129」字段傳到服務器。服務器和最新的文件特徵串對比,若是相同那麼返回304 Not Modified,不一樣則返回200 OK。當ETag和Last-Modified同時出現時,任何一個字段只要生效了,就認爲文件是沒有更新的。

2.WebView如何設置才能支持上面的協議

由上面的介紹可知,只要是個主流的、合格的瀏覽器,都應該可以支持HTTP協議層面的這幾個字段。這不是咱們開發者能夠修改的,也不是咱們應該修改的配置。在Android上,咱們的WebView也支持這幾個字段。可是咱們能夠經過代碼去設置WebView的Cache Mode,而使得協議生效或者無效。WebView有下面幾個Cache Mode:

  • 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,都使用緩存中的數據。本地沒有緩存時才從網絡上獲取。

設置WebView緩存的Cache Mode示例代碼以下:

WebSettings settings = webView.getSettings();settings.setCacheMode(WebSettings.LOAD_DEFAULT);

網上不少人都說根據網絡條件去選擇Cache Mode,當有網絡時,設置爲LOAD_DEFAULT,當沒有網絡時設置爲LOAD_CACHE_ELSE_NETWORK。可是在個人業務中,js文件的更新都是非覆蓋式的更新,也就是時候每次改變js文件的時候,文件的url地址必定會發生變化,因此我但願瀏覽器可以緩存下來js,而且一直使用它,那麼我就給它只設置爲LOAD_CACHE_ELSE_NETWORK。固然若是你要是能夠改js的cdn服務器的Cache-Control字段,那也行啊,用LOAD_DEFAULT就ok了。至於文件是應該採用覆蓋式or非覆蓋式的更新,不是我今天要討論的內容,在web前端領域,這是一個能夠聊聊的topic。

關於iOS的WebView,我同事在實際測試的時候居然發現,控制文件緩存的Response Header是Expires字段。。並且iOS沒法針對整個WebView設置Cache Mode,只能針對每個URLRequest去設置。。後續有機會要學習一下iOS那塊的狀況。

3.在手機裏面的存儲路徑

瀏覽器默認緩存下來的文件是怎麼被存儲到了哪裏呢?這個問題在接觸到WebView以來,就一直是一個謎題。此次因爲工做的須要,我特地root了兩臺手機,一臺紅米1(Android 4.4)和一臺小米4c(Android 5.1),在root高系統版本(6.0和7.1)的兩臺Nexus都以失敗了結以後,我決定仍是先看看4.4和5.1系統上,WebView自帶的緩存存到了哪裏。

首先,不用思考就知道,這些文件必定是在/data/data/包名/目錄下,在我以前的一篇博客裏面提到過,這是每個應用本身的內部存儲目錄。

接着,咱們打開終端,使用adb鏈接手機,而後按照下面命令操做一下。

// 1.先進入shelladb shell// 2.開啓root帳號 su// 3.修改文件夾權限chmod 777 data/data/你的應用包名/// 4.修改子文件夾的權限,由於Android命令行不支持向Linux那樣的-R命令實現遞歸式的chmod。。。chmod 777 data/data/你的應用包名/*// 5.因此若是你對應用目錄層級更深,你就要進一步地chmod。。。chmod 777 data/data/你的應用包名/*/*// 6.直到終端裏提示你說,no such file or directory時,說明chmod完了,全部的內部存儲裏面的文件夾和文件均可以看到了,若是你們有更好的方法請必定告訴我,多謝了~

Android 4.4的目錄:/data/data/包名/app_webview/cache/,以下圖所示的第二個文件夾。

640?wx_fmt=jpeg

可能你注意到了,第一個文件夾是叫Application Cache,咱們後面再說它。

Android 5.1的目錄:/data/data/包名/cache/org.chromium.android_webview/下面,以下圖所示。

640?wx_fmt=jpeg

可是在5.1系統上,/data/data/包名/app_webview/文件夾依然存在,只是4.4系統上面存儲WebView自帶緩存的app_webview/cache文件夾再也不存在了(注意下App Cache目錄還在),以下圖所示。

640?wx_fmt=jpeg

綜上所述,WebView自帶的瀏覽器協議支持的緩存,在不一樣的系統版本上,位置是不同的。也許除了我root過的4.四、5.1之外,其餘版本系統的WebView自帶緩存還可能存在於不一樣的目錄裏面。

另一個是關於緩存文件的存儲格式和索引格式,在不一樣的手機上可能也有差異,由於以前看到網上的人都說有叫webview.db或者webviewCache.db的文件,這個文件呢,還不是在app_webview/cache或者org.chromium.android_webview下面,而是在/data/data/包名/database/裏面。可是,我這兩臺root過的手機都沒有看到這種文件,並且我把/data/data/包名/下面全部的db文件都打開看了,並無發現有存儲url記錄的table。。

實際上,以5.1系統爲例,我看到了/data/data/包名/cache/org.chromium.android_webview/下面有叫index和/index-dir/the-real-index的文件,以及一堆名稱爲md5+下劃線+數字的文件,上面的圖中也能夠看獲得,這塊的原理仍然有些疑問,也但願專業的大神能夠解答一下。

4、H5的緩存

講完了WebView自帶的緩存,下面講一下H5裏面的App Cache。這個Cache是由開發Web頁面的開發者控制的,而不是由Native去控制的,可是Native裏面的WebView也須要咱們作一下設置才能支持H5的這個特性。

1.工做原理

寫Web頁面代碼時,指定manifest屬性便可讓頁面使用App Cache。一般html頁面代碼會這麼寫:

<html manifest="xxx.appcache"></html>

xxx.appcache文件用的是相對路徑,這時appcache文件的路徑是和頁面同樣的。也可使用的絕對路徑,可是域名要保持和頁面一致。

完整的xxx.appcache文件通常包括了3個section,基本格式以下:

CACHE MANIFEST# 2017-05-13 v1.0.0/bridge.jsNETWORK:*FALLBACK:/404.html
  • CACHE MANIFEST下面文件就是要被瀏覽器緩存的文件

  • NETWORK下面的文件就是要被加載的文件

  • FALLBACK下面的文件是目標頁面加載失敗時的顯示的頁面

AppCache工做的原理:當一個設置了manifest文件的html頁面被加載時,CACHE MANIFEST指定的文件就會被緩存到瀏覽器的App Cache目錄下面。當下次加載這個頁面時,會首先應用經過manifest已經緩存過的文件,而後發起一個加載xxx.appcache文件的請求到服務器,若是xxx.appcache文件沒有被修改過,那麼服務器會返回304 Not Modified給到瀏覽器,若是xxx.appcache文件被修改過,那麼服務器會返回200 OK,並返回新的xxx.appcache文件的內容給瀏覽器,瀏覽器收到以後,再把新的xxx.appcache文件中指定的內容加載過來進行緩存。

能夠看到,AppCache緩存須要在每次加載頁面時都發出一個xxx.appcache的請求去檢查manifest文件是否是有更新(byte by byte)。根據這篇文章(H5 緩存機制淺析 移動端 Web 加載性能優化)的介紹,AppCache有一些坑的地方,且官方已經不推薦使用了,但目前主流的瀏覽器依然是支持的。文章裏主要提到下面這些坑:

  • 要更新緩存的文件,須要更新包含它的 manifest 文件,那怕只加一個空格。經常使用的方法,是修改 manifest 文件註釋中的版本號。如:# 2012-02-21 v1.0.0

  • 被緩存的文件,瀏覽器是先使用,再經過檢查 manifest 文件是否有更新來更新緩存文件。這樣緩存文件可能用的不是最新的版本。

  • 在更新緩存過程當中,若是有一個文件更新失敗,則整個更新會失敗。

  • manifest 和引用它的HTML要在相同 HOST。

  • manifest 文件中的文件列表,若是是相對路徑,則是相對 manifest 文件的相對路徑。

  • manifest 也有可能更新出錯,致使緩存文件更新失敗。

  • 沒有緩存的資源在已經緩存的 HTML 中不能加載,即便有網絡。例如:http://appcache-demo.s3-website-us-east-1.amazonaws.com/without-network/

  • manifest 文件自己不能被緩存,且 manifest 文件的更新使用的是瀏覽器緩存機制。因此 manifest 文件的 Cache-Control 緩存時間不能設置太長。

2.WebView如何設置才能支持AppCache

WebView默認是沒有開啓AppCache支持的,須要添加下面這幾行代碼來設置:

WebSettings webSettings = webView.getSettings();webSettings.setAppCacheEnabled(true);String cachePath = getApplicationContext().getCacheDir().getPath(); // 把內部私有緩存目錄'/data/data/包名/cache/'做爲WebView的AppCache的存儲路徑webSettings.setAppCachePath(cachePath);webSettings.setAppCacheMaxSize(5 * 1024 * 1024);

注意:WebSettings的setAppCacheEnabled和setAppCachePath都必需要調用才行。

3.存儲AppCache的路徑

按照Android SDK的API說明,setAppCachePath是能夠用來設置AppCache路徑的,可是我實際測試發現,無論你怎麼設置這個路徑,設置到應用本身的內部私有目錄仍是外部SD卡,都沒法生效。AppCache緩存文件最終都會存到/data/data/包名/app_webview/cache/Application Cache這個文件夾下面,在上面的Android 4.4和5.1系統目錄截圖能夠看獲得,可是若是你不調用setAppCachePath方法,WebView將不會產生這個目錄。這裏有點讓我以爲奇怪,我猜想可能從某一個系統版本開始,爲了緩存文件的完整性和安全性考慮,SDK實現的時候就吧AppCache緩存目錄設置到了內部私有存儲。

5、總結

相同點

WebView自帶的緩存和AppCache都是能夠用來作文件級別的緩存的,基本上比較好地知足對於非覆蓋式的js、css等文件更新。

不一樣點

WebView自帶的緩存是是協議層實現的(瀏覽器內核標準實現,開發者沒法改變);而AppCache是應用層實現的。

WebView的緩存目錄在不一樣系統上多是不一樣的;而對於AppCache而言,AppCache的存儲路徑雖然有方法設置,可是最終都存儲到了一個固定的內部私有目錄下。

WebView自帶的緩存能夠在緩存生效的時候不用再發HTTP請求;而AppCache必定會發出一個manifest文件的請求。

WebView自帶的緩存能夠經過設置CacheMode來改變WebView的緩存機制;而AppCache的緩存策略是由manifest文件控制的,也就是說是由web頁面開發者控制的。

最後說一下,其實不少時候,這兩類緩存是共同在工做的,當manifest文件沒有控制某些資源加載時,例如我上面寫的xxx.appcache文件裏,NETWORK section下面用的是*號,意思是全部不緩存的文件都要去網絡加載。此時,這些資源就會走到WebView自帶的緩存機制去,結合WebView的CacheMode,咱們實際上對這些文件進行了一次WebView自帶的緩存。搞清楚這兩類緩存的原理有利於咱們更好的設計本身的頁面和App,儘量減小網絡請求,提升App運行效率。

關於本文

做者:@unclechen

原文:

https://unclechen.github.io/2017/05/13/WebView緩存原理分析和應用/


640?wx_fmt=png

每個「在看」,都是對我最大的確定!640?wx_fmt=gif

相關文章
相關標籤/搜索