從WebView快取聊到Http 的快取機制

weixin_34290000發表於2017-05-09

版權宣告:

本賬號釋出文章均來自公眾號,承香墨影(cxmyDev),版權歸承香墨影所有。

未經允許,不得轉載。

一、前言

在 Android 開發中,如果用過 WebView 來載入一個網頁,總是逃不過 WebView 的快取策略的設定。WebView 本身也提供了多種快取的策略來供開發者使用,而有一些涉及到 Http 協議,所以將兩個概念集中整理一起講解,希望對大家有幫助。

二、WebView 快取策略

WebView 本身是提供了設定快取策略的 API 的,可以使用 WebSetting 物件進行設定。

而 WebSetting 中,可以設定多種快取策略,如下:

  • LOAD_CACHE_ONLY:不使用網路,只讀本地快取。
  • LOAD_NORMAL:在 API Level 17 中已經被廢棄,而在API Level 11 開始,策略如 LOAD_DEFALT。
  • LOAD_NO_CACHE:不使用快取,只從網路獲取資料。
  • LOAD_CACHE_ELSE_NETWORK:只要本地有快取,就從快取中讀取資料。
  • LOAD_DEFAULT:根據 Http 協議,決定是否從網路獲取資料。

LOAD_CACHE_ONLY 和 LOAD_NO_CACHE 都是比較極端的情況,一般我們也不會使用。LOAD_NORMAL 已經被廢棄了,也沒什麼好說的,而 LOAD_CACHE_ELSE_NETWORK 本身已經決定了策略,只要本地有,就不會重新獲取,也不會有什麼可以變化的。

LOAD_DEFAULT 才是我們專案上比較常用測策略,本文主要對 LOAD_DEFAULT 模式進行講解,本身它也是 WebView 的預設模式。但是實際開發中,最好還是顯式設定一下,很多 ROM 都可能會修改這部分程式碼的,我在樂視手機上做過測試,它的預設策略就是 LOAD_CACHE_ELSE_NETWORK 。

先來看看 LOAD_DEFAULT 的 API。

可以看到,它是預設策略,如果不設定強制策略,在資源沒有過期的時候,從快取獲取,如果資源過期了,則從網路獲取。

這樣的一個策略,就和 Http 快取相關了,由協議來確定資源載入的策略。

三、Http快取策略

既然知道 WebView 也是遵循 Http 的快取策略的,那麼我們就先來看看 Http 快取的策略是怎麼樣的。

用 Charles 抓一個包,看看一個常規的靜態檔案。

可以看到,一個請求的響應頭中,包含了很多資訊,而其中有一部分是和快取相關的,下面我們來一一講解。

1、Cache-Control

Cache-Control 是 Http 1.1 中新增加的一個用來定義快取時間的頭。如果使用了 Cache-Control 的話,會覆蓋掉 Http 1.0 中的一些,例如:Pragma、Expires等,以 Cache-Control 為準。

Cache-Control 也是一個通用的 Http 報文頭欄位,它可以分別在請求報文和響應報文中使用,而它作為不同的使用方式,存在不同的含義。

Cache-Control 的規範寫法:

Cache-Control:cache-directive

cache-directive 的可選值有很多,no-cache、no-store、only-if-cached 等,有興趣的可以自行查查 Http 協議中的定義。但是一般而言,如上圖所示,會使用 max-age 來設定一個最大的有效時間的方式來使用,max-age 設定的時間,單位為秒(s)。

Cache-Control 的 max-age 出現在請求報文頭和響應報文頭中,含義是不一樣的。

  • 請求頭:告知伺服器客戶端希望接收一個存在時間(Age)不大於 max-age 的資源。
  • 響應頭:告知客戶端,該資源在 max-age 設定的時間內是新鮮的,無需再向伺服器傳送請求了。

WebView 中,如果被設定為 LOAD_DEFAULT 的話,是遵循此規則的,也就是說,當請求資源回來之後,會根據 max-age 設定當前資源的過期時間,在這個時間範圍內,則不會重新請求,會直接從快取中讀取資源,而上面的例子中,max-age 被設定為 40000,差不多 11 個多小時。

2、資料新鮮度校驗

Cache-Control 這個報文頭,決定了客戶端是否需要向伺服器傳送請求。但是,如果已經過期(超過 max-age 設定的時間),當這個請求傳送到伺服器之後,是否需要伺服器返回一個完整的資料呢?

雖然我們設定了 max-age ,但是它只能表示一個合理的變化頻度,也就是說,可能超過這個 max-age 設定的時間,但是請求的檔案也並沒有變化。那麼伺服器只需要告知客戶端,檔案沒變化,你還是讀快取的資源就好了。

這個策略,就是使用 ETag 和 Last-Modified 來校驗的。當客戶端通過 max-age 判斷髮現請求的資原始檔已經不再新鮮了,需要從伺服器上重新獲取,在向伺服器傳送請求的時候,就會通過這些值告知伺服器本地快取資源的一個標識,伺服器就通過這個標識來判斷客戶端快取的資源是否依然新鮮。

可以看到,當 max-age 失效之後,傳送的請求,會攜帶 if-None-Match 和 if-Modified-Since 這兩個報文頭,伺服器就是根據這兩個報文頭來判定客戶端的資源是否過期,如果過期,則返回新的資源,如果未過期,則返回一個狀態碼304的一個響應,告知客戶端可以繼續讀取快取使用。

細心的應該可以看到,請求頭裡的 if-None-Match 就是之前響應頭裡的 ETag ,而 if-Modified-Since 就是之前響應頭裡的 Last-Modified。

下面看看他們的含義:

  • ETag:資源的唯一匹配標識資訊。
  • Last-Modified:資源的最後一次修改時間。
  • if-None-Match:比較 ETag 是否不一致。
  • If-Modified-Since:比較資源最後更新的時間是否一致。

當然,對於請求頭,還有其他的規則,例如:if-Match、if-Unmodified-Since 等,這個就看伺服器的和客戶端的實現了。

這裡的 ETag 和 Last-Modified 其實可以分開使用,但是如果被同時使用,則要求伺服器對這兩個值都進行校驗,都校驗通過了才會返回 304。

那麼這麼做,有什麼好處,實際是所有的快取策略,都是為了減小各種地方的壓力。對於客戶端而言,減少了網路請求的壓力,對於伺服器而言,也減小了請求和流量的壓力。

可以看到,一個完整的資源請求,需要 24kb,而當資源沒有過期的時候,只需要 1kb 左右即可,並且響應的時間也更快了。

四、例外情況

到這裡,對於 WebView 的各個快取策略的理解應該就明確了。如果使用 LOAD_DEFAULT 則依賴 Http 的快取策略,而Http 快取又是依賴 Cache-Control、ETag、Last-Modified 等值來確定的。

那麼,如果我們將 CacheMode 設定為 LOAD_DEFAULT ,並且給出了一個 max-age = 40000 資源響應頭,在不清理快取的情況下,我們的 WebView 就不會對該繼續傳送請求?這樣我們不小心設定了一個極大的 max-age 值,是否客戶端的資源很久才會被更新?

其實並不是絕對的,在 WebView 中,載入網頁,我們一般使用 loadUrl() 方法,當使用 loadUrl() 方法的時候,它會完全遵循上面給出的快取策略,在沒有過期的時候去從快取中讀取資源。

但是瀏覽器在 Http 快取策略之外,還提供了強制重新整理的策略,這樣也保證了在某些情況下,可以去伺服器是重新獲取資源。而這個策略反應在 WebView 中,就是使用 reload() 方法。當使用 reload() 方法的時候,WebView 會重新請求資源,並在報文頭中,修改 max-age 為 0 。這樣既可以保證當前重新整理了會有一個真實的網路請求,又能保證在快取資源不過期的情況下,不給伺服器造成壓力。

五、小結

Http 的快取策略,在 Android 開發中,並不是只用在 WebView 上,一些網路庫,例如 OkHttp,也是依賴 Http 快取策略來進行快取資料的。

本文參加掘金技術徵文:https://juejin.im/post/58d8e99261ff4b006cd6874d

公眾號二維碼.jpg

相關文章