HybridCache:一種簡單的native與webview共享緩存的設計

HybridCache簡而言之實際上是一套native和webview共享緩存的解決方案。不過在瞭解HybridCache的實現細節以及可以解決的問題以前,先大概瞭解一下web開發中涉及到的緩存機制css

Web緩存機制

實際上,web開發當中已經具有至關完善的緩存機制,而且Android系統的WebView對這些已有的緩存機制基本上都提供了完備的支持。html

web的緩存機制有如下兩大類:前端

  • 瀏覽器緩存機制
  • web開發中的緩存機制

瀏覽器緩存機制

瀏覽器自身的緩存機制是基於http協議層的Header中的信息實現的java

  • Cache-control && Expiresandroid

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

    Cache-control常見的值包括:no-cache、no-store、max-age等。其中max-age=xxx表示緩存的內容將在 xxx 秒後失效, 這個選項只在HTTP 1.1可用, 並若是和Last-Modified一塊兒使用時, 優先級較高。github

  • Last-Modified && ETagweb

    這兩個字段的做用是:發起請求時,服務器決定文件是否須要更新。服務端響應瀏覽器的請求時會添加一個Last-Modified的頭部字段,字段內容表示請求的文件最後的更改時間。而瀏覽器會在下一次請求經過If-Modified-Since頭部字段將這個值返回給服務端,以決定是否須要更新文件api

這些技術都是協議層所定義的,在Android的webview當中咱們能夠經過配置決定是否採納這幾個協議的頭部屬性。設置以下:瀏覽器

webView.settings.cacheMode=WebSettings.LOAD_DEFAULT
// cacheMode的取值定義以下:
@IntDef({LOAD_DEFAULT, LOAD_NORMAL, LOAD_CACHE_ELSE_NETWORK, LOAD_NO_CACHE, LOAD_CACHE_ONLY})
@Retention(RetentionPolicy.SOURCE)
public @interface CacheMode {}
複製代碼

web開發中的緩存機制

  • Application Cache
  • Dom Storage 緩存機制
  • Web SQL Database 緩存機制
  • IndexedDB 緩存機制
  • File System

關於以上這幾個web開發中的緩存機制,能夠參考這篇文章Android:手把手教你構建 全面的WebView 緩存機制 & 資源加載方案

認識HybridCache

HybridCache旨在提供一種native和webview之間共享緩存的解決方案,尤爲是共享native中的圖片緩存。在native開發中,咱們普遍的使用着各類圖片加載庫,好比:

這些存在native的圖片加載框架爲咱們提供了很是良好的圖片緩存體驗。HybridCache的一種具體運用,就是把在webview中的圖片交由咱們的native的圖片加載框架(或者是咱們本身實現的文件緩存)進行緩存,這樣的好處就是:

  • 可以更持久的保存webview中的圖片(並且不須要前端開發人員所關注)
  • 可以更加統一app內的圖片緩存
  • webview和native的緩存貢獻,在某些適用的場景具有節省流量和加快加載速度的優勢

固然圖片緩存只是一個至關具體的運用,實際上HybridCache提供的是更爲普遍的webview資源加載攔截的功能,經過攔截webview中渲染網頁過程當中各類資源(包括圖片、js文件、css樣式文件、html頁面文件等)的下載,根據業務的場景考慮緩存的策略,能夠從app端提供webview的緩存技術方案(不須要前端人員感知的)。

實現原理

Android的webview在加載網頁的時候,用戶可以經過系統提供的API干預各個中間過程。而HybridCache要攔截的就是網頁資源請求的環節。這個過程,WebViewClient當中提供瞭如下兩個入口:

public class WebViewClient {

	// android5.0以上的版本加入
   public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        return shouldInterceptRequest(view, request.getUrl().toString());
    }

	  @Deprecated
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        return null;
    }
}
複製代碼

上面的兩個API是在調用了WebView#loadUrl()以後,請求網頁資源(包括html文件、js文件、css文件以及圖片文件)的時候回調。關於這兩個API有幾個點須要注意:

  • 回調不是發生在主線程,所以不能作一些處理UI的事情
  • 接口的返回值是同步的
  • WebResourceResponse這個返回值能夠自行構造,其中關鍵的屬性主要是:表明資源內容的一個輸入流InputStream以及標記這個資源內容類型的mMimeType

只要在這兩個入口構造正確的WebResourceResponse對象,就能夠替換默認的請求爲咱們提供的資源。所以,webview和native緩存共享的方案就是經過這兩個入口,在每次請求資源的時候根據請求的URL/WebResourceRequest判斷是否存在本地的緩存,並在緩存存在的狀況下將緩存的輸入流返回,示意圖以下所示:

方案設計

先放上一張方案實現的設計類圖:

ps:這張類圖是一開始設計方案的時候畫的,後續通過了屢次重構和調整,部分已經不盡一致,不過基本保持了核心的概念和結構

HybridCache的核心任務就是攔截資源請求,下載資源並緩存資源,所以整個庫的設計就分爲了下面三個核心點:

  • 請求攔截
  • 資源響應(下載/讀取緩存)
  • 緩存

資源請求攔截

參考okhttp攔截器的思想設計了WebResInterceptorChain兩個接口,定義了攔截的動做以及驅動攔截器的鏈條。實際上,這兩個接口都只是類庫內部可見。具體的實現是BaseInterceptorDefaultInterceptorChain兩個對象。

BaseInterceptor是攔截髮生和資源響應的核心對象,內部處理了包括尋找緩存資源、下載資源和寫緩存的基本邏輯。同時它是一個抽象類,子類只須要實現它並根據對應的資源請求定義是否參與攔截、以及選擇性的自定義配置下載和緩存的行爲便可。

DefaultInterceptorChain僅僅只是用於用於驅動攔截器鏈條的流轉,類庫內部可見

資源響應

資源響應有兩種狀況:

  • 緩存響應
  • 下載響應

當對應的資源緩存不存在的時候,會直接觸發資源的下載。在類庫內部,會經過HttpConnectionDownloader直接創建一個HttpURLConnection進行資源的下載,得到資源的文件流。

同時參考代理模式,設計了邊讀邊寫的動做。即下載的資源流經過被封裝爲一個WebResInputStreamWrapper對象後直接返回。WebResInputStreamWrapper繼承於InputStream,同時內部持有一個TempFileWriter的實例。在WebResInputStreamWrapper被瀏覽器讀取的同時,TempFileWriter會把對應的資源寫入到緩存當中,實現邊讀邊寫

緩存

CacheProvider定義了提供緩存的實現的規範,能夠根據實際的業務場景提供任意的緩存實現方案。同時庫內部經過LruCache提供了簡單的文件緩存的實現SimpleCacheProvider。同時爲了拓展共享圖片緩存的實現,類庫還提供了一個基於fresco的圖片緩存提供實例FrescoImageProvider

CacheKeyProvider使得業務能夠根據實際的場景提供緩存的key的生成策略。

關於方案的實現細節,能夠關注個人GitHub倉庫HybridCache

接入使用

在圖片緩存以及簡單的使用文件緩存資源這兩個場景上,方案已經提供了直接的實現,能夠簡單的一鍵接入使用,總的接入步驟以下:

  1. 根據業務須要定義你的攔截器。你只須要繼承BaseInterceptor,並實現僅有的一個抽象方法便可。若是你須要圖片攔截器,能夠直接使用類庫內部提供的ImageInterceptor
  2. 在定義攔截器的同時,你能夠實現你的緩存提供器,提供你的緩存管理策略。默認的狀況下會使用SimpleCacheProvider提供文件緩存
  3. 使用HybridCacheManager#addCacheInterceptor()將攔截器添加都管理器中。
  4. 在初始化webview的時候,設置自定義的WebViewClient對象,並在其攔截資源請求的入口方法中調用HybridCacheManager#interceptWebResRequest()方法

以上簡單的幾步便可擁有native和webview共享緩存的功能。具體的實例能夠參考GitHub倉庫中的demo。

你可能會遇到的坑

在使用webview的時候,你可能會遇到一些坑

  • 頁面當中資源包含http和https兩種請求

    若是你加載的頁面以及頁面請求的資源包好了http和https兩種請求,那麼你有可能會出現部分資源沒法加載的狀況。這是由於在Android5.0以後,webview默認禁止在一個頁面當中包含兩種協議請求。這時候你須要添加這樣的設置:setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW)

  • 加載的頁面部分的資源請求出錯(好比404)沒有回調

    正如前面說起的,在Android6.0之後,你能夠經過WebViewClient#onReceivedHttpError()接收到資源請求出錯的回調,可是在此以前是沒有這個api的,同時另一個APIWebViewClient#onReceivedError()的回調是在整個頁面不可達等狀況纔會回調,而不會由於資源請求問題而響應,具體能夠參考文檔備註。

    在使用HybridCache資源攔截以後,你能夠經過設置BaseInterceptor#setOnErrorListener(onErrorListener)感知到資源加載出錯的狀況

  • 緩存key不一樣致使緩存不能共享

目前這個方案已經在咱們的項目中實際使用。你能夠在個人GitHub倉庫HybridCache中看到簡單的實例.歡迎你們表達對這個方案的設計的見解和改進意見,謝謝。

相關文章
相關標籤/搜索