1. 程式人生 > >Android開發——WebView輕量快取優化

Android開發——WebView輕量快取優化

0. 前言

產品被使用者投訴 APP 流量消耗厲害:

[2017-08-08 07:34:40] 嚴選 APP 流量消耗太大啦,每次啟動都更新,下面流量很大。建議優化流量的消耗,可以對載入畫質進行選擇。想比淘寶 APP,消耗流量可是大多了。[2017-06-01 21:43:36] 怎麼沒用有流量節約模式,一會用了我 200M。[2017-06-12 08:32:25] 嚴選 app 太費流量了。

於是乎考慮了流量方面的問題。暫時 APP 中涉及流量的幾個方面:

1. 普通 https 請求,wzp 請求

文字傳輸,請求已經做了 gzip,流量佔比小。

2. 配置檔案下載,應用內升級下載包

觸發的時機較少,每次升級版本才觸發。另外整個 APP 全量包才12M,並實施增量更新,因此流量佔比小。

3. 網路圖片下載

圖片下載消耗的流量較多,然而本地使用 fresco 載入圖片資源,已經實現了記憶體快取(已編碼和未編碼)和本地快取(大圖快取和小圖快取),此外 APP 中已經使用 nos 的引數設定為 webp 格式,根據實際圖片控制元件大小設定請求的圖片大小,同時設定了quality。在保證圖片清晰度的前提下,儘可能的減少了流量的消耗。

由於產品要求,並沒有做根據手機記憶體和網路環境的圖片清晰度設定。

4. h5 頁面展示

h5 頁面消耗的流量較多,由於 h5 頁面是交由前端處理顯示,客戶端開發關注的少些,而此處消耗了大量的流量

使用TrafficStats記錄 APP 幾個頁面的流量消耗:

  • 啟動頁,首頁,專題 H5 頁

專題 H5 頁的這麼多流量消耗主要哪裡?

由上表可以看出:第一次進入 APP 的專題頁消耗了大量的流量;返回首頁再一次進入,流量消耗明顯小了很多;瀏覽多個 h5 頁面後進入專題頁,還是消耗了大量的流量。從上面的流量資料中,就有幾個疑問了:

(1)如何降低 H5 頁面首次載入的流量消耗?

前端 H5 頁面根據裝置螢幕大小動態設定圖片大小,保證清晰度的前提下,降低流量消耗。這部分由前端開發保證,對於移動客戶端開發透明,因此這裡並不具體討論。

(2)第 1 次進入專題頁和第 2 次進入專題頁,為什麼會有流量差異?

很容易會想到專題頁的 WebView 使用了快取,而我們程式碼中也確實是做了相關的設定。而至於這裡的程式碼是如何影響快取策略的,後面會進一步說明。

webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
webSettings.setDatabaseEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setAppCacheEnabled(true);

(3)第 2 次進入專題頁和第 3 次進入專題頁(已經瀏覽很多其他頁面),為什麼會有流量差異?

很容易想到是該頁面的快取失效了,而至於為何失效和如何避免,後面會進一步說明。

(4)如何降低非首次載入的流量消耗,提升 H5 頁面首次載入顯示的速度?

很容易發現,在有本地快取情況下,載入 H5 頁面的速度會大幅提升,載入 33.5K 的資源相比載入 3.0M 的資源會提升更大的頁面開啟速度。

5. WebView系統快取策略

5.1 CacheMode

//WebSettings能設定的CacheMode 主要有:
LOAD_DEFAULT //預設設定,如果有本地快取,且快取有效未過期,則直接使用本地快取,否則載入網路資料
LOAD_NORMAL //已經廢棄
LOAD_CACHE_ELSE_NETWORK //如果有本地快取則直接使用本地快取,而不管快取資料是否過期失效,否則載入網路資料
LOAD_NO_CACHE //載入網路資料,不使用快取
LOAD_CACHE_ONLY //有本地快取,載入資料,否則載入失敗
這裡的快取策略比較好理解,但是仍有幾個疑問:

(1)我們的 APP 是不是使用 LOAD_CACHE_ELSE_NETWORK 就可以完成快取的使用和載入問題?

類似 Fresco、UniversalImageLoader 等第三方圖片庫,都設計了三級快取,記憶體 → 磁碟 → 網路,分級取資料,只要取到就不再訪問下一級。而 h5 頁面,載入的內容不僅僅有圖片,也有 html,js,css 等內容,而這部分內容我們認為經常會發生變化,直接使用 LOAD_CACHE_ELSE_NETWORK 是不可接受的。

(2)本地快取是儲存在哪裡?

見 5.2 本地快取目錄

(3)LOAD_DEFAULT 模式下如何判定快取有效?

見 5.3 快取有效性判斷

5.2 本地快取目錄

在手機 (nexus5,Android 6.0.1) 本地目錄

/data/data/${packagename}/cache/org.chromium.android_webview/

5.3 H5快取機制

5.3.1 Dom Storage 儲存機制

H5的DOM Storage機制提供了一種方式讓程式設計師能夠把資訊儲存到本地的計算機上,在需要時獲取。相比 cookie 的儲存讀取,DOM Storage提供了更大容量的儲存空間。H5 建議每個網站的 Storage 空間是 5M,而 cookie 的大小上限為 4K。

Dom Storage儲存方式為鍵值對儲存,K/V 的資料格式為字串型別,如果需要儲存非字串,需要在讀寫的時候進行型別轉換或使用 JSON 序列化。為此,Dom Storage不適合儲存複雜或者儲存空間要求大的資料(如圖片資料等),一般用於儲存一些伺服器或者本地的臨時資料,和 Android SharePreference 機制類似。

參見介面定義:

interface Storage { 
    readonly attribute unsigned long length; 
    // 返回列表中第 n 個鍵的名字。Index 從 0 開始
    getter DOMString key(in unsigned long index); 
    // 返回指定鍵對應的值
    getter any getItem(in DOMString key); 
    // 存入一個鍵值對
    setter creator void setItem(in DOMString key, in any data); 
    // 刪除指定的鍵值對
    deleter void removeItem(in DOMString key); 
    // 刪除 Storage 物件中的所有鍵值對
    void clear(); 
};

Dom Storage可分為SessionStorage和 LocalStorage。兩者使用方法基本相同,區別在於作用的範圍不同。SessionStorage 用來儲存與頁面相關的資料,頁面關閉後就無法使用,而 LocalStorage 則持久存在,在頁面關閉後也可以使用。在 Android 中,我們通過 WebSetting的介面來開啟或關閉 Dom Storage

WebSettings webSettings = webView.getSettings();
webSettings.setDomStorageEnabled(true);

5.3.2 Web 資料庫儲存機制

H5 也提供了基於 SQL 的資料庫儲存機制,用於儲存一些結構化資料,Web SQL Database 儲存機制提供了一組 API 供 Web App 建立、儲存、查詢資料庫。相比 Dom Storage 適合儲存結構複雜的資料。在 Android WebView 中,可以通過 WebSettings 設定是否啟用 SQL Database,和設定資料庫檔案的儲存路徑。

WebSettings webSettings = webView.getSettings();webSettings.setDatabaseEnabled(true);
webSettings.setDatabasePath(dbPath);

5.3.3 HTML 應用程式快取機制

應用程式快取為 web 應用的離線訪問提供了支援,其原理是基於 manifest 屬性和 manifest 檔案。manifest 快取會一直儲存,直到快取被清除、manifest 檔案被修改、或瀏覽器更新 AppCache。

  • manifest 屬性

每個指定了 manifest 的頁面在使用者對其訪問時都會被快取。如果未指定 manifest 屬性,則頁面不會被快取(除非在 manifest 檔案中直接指定了該頁面)。

<!DOCTYPE HTML><html manifest="demo.appcache"> <body> The content of the document...... </body></html>

manifest 檔案

manifest 檔案是簡單的文字檔案,它告知瀏覽器被快取的內容(以及不快取的內容)。

manifest 檔案可分為三個部分:

1.CACHE MANIFEST- 在此標題下列出的檔案將在首次下載後進行快取

2.NETWORK- 在此標題下列出的檔案需要與伺服器的連線,且不會被快取

3.FALLBACK- 在此標題下列出的檔案規定當頁面無法訪問時的回退頁面(比如 404 頁面)

在 Android WebView 中,可以通過 WebSettings 設定是否啟用 AppCache、快取檔案儲存路徑、快取上限。

5.3.4 IndexedDB

IndexedDB也是一種資料庫的儲存機制,但不同於Web SQL Database,歸於 NoSQL 資料庫。IndexedDB 儲存資料是 key-value的形式。Key 是必需,且要唯一;Key 可以自己定義,也可由系統自動生成。Value 也是必需的,但 Value 非常靈活,可以是任何型別的物件。一般 Value 都是通過 Key 來存取的,相比 Dom Storage 的 K/V 儲存方式,功能更強大,儲存空間更大。IndexedDB 支援 index(索引),可對 Value 物件中任何屬性生成索引,然後可以基於索引進行 Value 物件的快速查詢。Android 4.4 引入 IndexDB 支援,是否開啟只需開啟允許 JS 執行的開關。

WebSettings webSettings = webView.getSettings();
webSettings.setJavaEnabled(true);

5.3.5 File System API

Android 系統的 Webview 還不支援 File System API,這裡也不在介紹。

5.3.6 瀏覽器快取機制

瀏覽器快取機制是指通過 HTTP Header 部分的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等欄位來控制檔案快取的機制。

(1)Last-Modified

標識檔案在伺服器的最新更新修改時間。請求資源時,瀏覽器通過If-Modified-Since 欄位帶上本地快取的最新修改時間,伺服器通過比較快取檔案的修改時間是否一致,來判斷檔案是否有修改。如果沒有修改,則伺服器返回 304 告知瀏覽器繼續使用快取;否則返回 200,同時返回最新的檔案。

// 伺服器返回Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT
// 客戶端再次傳送請求If-Modified-Since: Tue, 12 Jan 2016 09:31:27 GMT

(2)Cache-Control

相對值,單位是秒。指定某個檔案從發出請求開始起的有效時長,在這個有效時長之內,瀏覽器直接使用快取,而不傳送請求。Cache-Control 不用要求伺服器與客戶端的時間同步,也不用伺服器時刻同步修改配置 Expired 中的絕對時間,從而可以避免額外的網路請求。優先順序比 Expires 更高。Cache-Control 通常與 Last-Modified 一起使用。一個用於控制快取有效時間,一個在快取失效後,向服務查詢是否有更新

(3)Expires

表示到期時間,一般用在 response 報文中,當超過此時間後響應將被認為是無效的而需要網路連線,反之而是直接使用快取。

(4)ETag

是對檔案進行標識的特徵字串。瀏覽器向伺服器請求檔案時,通過 If-None-Match 欄位把特徵字串傳送給伺服器,伺服器通過 Etag 比對來判斷檔案是否更新。Etag 一致,則返回 304;否則返回 200 和最新的檔案。

// 伺服器返回ETag: 248b11be4d6c7db6b2a699988a6603a5
// 客戶端再次傳送請求If-None-Match: 248b11be4d6c7db6b2a699988a6603a5
ETagLast-Modified一同使用,是要其中一個判斷快取有效,則瀏覽器使用快取資料

(5)Cache-Control:no-cache (Pragma:no-cache)

瀏覽器忽略本地快取,請求 HEADER 中程式碼帶上該欄位,請求伺服器獲取最新的資料。整個流程圖如下:

我們 APP 中設定的 CacheMode 為 WebSettings.LOAD_DEFAULT,即支援瀏覽器快取機制。另外,Cache-ControlLast-Modified是瀏覽器核心的機制,快取容量是 12MB,不分 HOST,過期的快取會最先被清除。如果都沒過期,應該優先清最早的快取或最快到期的或檔案大小最大的;過期快取也有可能還是有效的,清除快取會導致資原始檔的重新拉取。

6 客戶端H5快取機制考慮

和流量關係比較多的主要是瀏覽器快取機制(manifest 檔案的更新也遵守瀏覽器快取機制),如何快取 html、js、css、圖片等檔案。通過快取機制,對於移動 APP 提高資原始檔的載入速度、節省流量有著很大的意義。而具體該針對哪種資源使用哪個快取欄位,以及快取時長設定就比較重要。若時長設定的太短,則快取效果受影響,若時長設定過長,則不能及時獲取伺服器最新資料。

  • html、js、css 等資源

考慮這些檔案會隨著業務需求,經常發生變化。甚至我們的很多頁面都有推薦功能,頁面內容會隨著使用者的足跡發生變化,為此這些檔案,可以使用Last-Modified(ETag)來控制快取。

  • 圖片資源

使用 Last-Modified 需要每次都向伺服器發起查詢請求請求。考慮到圖片檔案是長時間不變的,推薦使用 Cache-Control設定一個較長的時間來快取。如果 H5 頁面中需要更新一張圖片的話,我們也是通過新增一張圖片,替換 url 去實現。如果不改變 url,直接替換服務端圖片,會由於 dns 伺服器的快取(nos 圖片快取時間大概是 1 個月),導致客戶端無法顯示最新的圖片。除此之外,也可以使用AppCache 機制,由前端控制快取檔案,客戶端設定快取路徑和大小。使用瀏覽器的快取策略或者AppCache 機制,看起來已經能很好的解決問題了,但為何我們的 APP H5 頁面流量消耗嚴重,載入還不夠快。通過抓包檢視我們的 APP H5 頁面的資源載入情況:

6.1 專題頁 H5 展示

6.2 第一次進入資料請求,可以看到圖片資料都是從伺服器拉取,累計消耗快取較大

6.3 退出馬上重新進入

圖片請求顯示的為[no content],即使用了快取資料

檢視具體請求 Request,圖片資源請求同時使用了 Last-Modified和ETag

檢視具體請求 Response,伺服器判斷客戶端快取有效,不在返回圖片資料

6.4 瀏覽多個 H5 頁面後,再次進入專題頁

圖片資料又重新從伺服器拉取

檢視具體請求 Response,可以並沒有看到 If-Modified-Since和If-None-Match欄位

檢視具體請求 Response,返回了 PNG image 資料

由上,可以總結為以下 2 點:

(1)Cache-Control Last-Modified 快取容量過小

根據流量分析,一個專題頁(H5 頁面)就已經消耗了 2MB~3MB 的流量,其他大量的商品詳情頁類似,而快取容量僅為 12MB,很明顯在使用者稍微多瀏覽幾個頁面之後,最開始的頁面快取就已經被清理了,於是重新進入還是會繼續發請求。

(2) 圖片資源使用ETagLast-Modified控制快取

抓包發現,我們 H5 頁面圖片資源依舊使用 Etag 控制快取,為此每個圖片需要請求伺服器檔案是否更新。這樣子也導致了頁面載入過慢,依舊有微量流量被消耗掉。當然 web 端開發可以直接改成 Cache-Control 方式,然而並不保證全部頁面都使用了一致的快取方式,畢竟 APP 前端還分了多個活動組,多個開發。

綜上考慮,如果有一套移動端開發能控制的快取策略,就能很好的突破快取容量過小和圖片快取策略統一的問題(流量中,圖片佔了絕大部分,其他 js、css 等資源可以前端自行控制,暫時不考慮視訊流資料)。

7 資原始檔預置

一個網頁從載入到顯示,需要下載大量的檔案,不僅消耗時間也消耗流量;如果不想依賴於瀏覽器快取,客戶端接管快取的話,挺多人想到可以將需要的資原始檔預置在 app 中,當載入 h5 頁面的時候,直接從本地載入,進而達到節省流量和提高 WebView 的效能。這裡的關鍵問題是如何讓 WebView 去載入本地檔案,而不是去網路載入?

7.1 替換HTML圖片標籤

(1) 載入 url 前,設定不載入圖片資源

WebSettings webSettings = mWebView.getSettings();
webSettings.setLoadsImagesAutomatically(false);

(2) 注入JS

mWebView.addJavaInterface(new InJavaLocalObj(), "local_obj");
private final class InJavaLocalObj { 
    @JavaInterface 
    public String getLocalSrc(String src) 
         { return "file://storage/emulated/0/YanXuan/file/a.jpeg"; 
         }
}

(3) 頁面載入完成時修改圖片標籤
private class MyWebViewClient extends WebViewClient { 
    @Override 
    public void onPageFinished(WebView view, String url) { 
        super.onPageFinished(view, url); 
        view.loadUrl("java:(function(){var objs = document.getElementsByTagName('img');nfor(var i=0;i<objs.length;i++) {n var imgUrl = objs[i].getAttribute('src'); var localUrl = window.local_obj.getLocalSrc(imgUrl); if(localUrl) {objs[i].setAttribute('src', localUrl);}n}})()"); 
    }
}

(4)重新觸發載入圖片
webSettings.setLoadsImagesAutomatically(false);

及時替換了,本地圖片也無法載入

7.2 請求攔截

除了使用 js 注入的方式之外,還可以使用 WebViewClient的方法去修改載入物件。

webView.setWebViewClient(new WebViewClient() {
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        File file = getLocalImage(url);
        if (file == null || !file.exists()) {
            return null;
        }
        ...
        return new WebResourceResponse(mimeType, "UTF-8", new FileInputStream(file);
    }
});

此外,客戶端需要預置資源,並維護網路圖片url 和本地圖片之間的關聯。若預置資源不限於圖片資源,由於 html、js、css 等資源容易發生變化,因此還需要實現一套機制實現本地資源和服務端資料及時的更新,即服務端需要支援版本控制和資源增量下發等功能。

CandyWebCache 是杭研團隊在 Web 快取方面做的一個思考和嘗試,其功能非常強大。其設計框架圖如下:

(1) 提供 Gradle 外掛,支援將 H5 資源提前打入 APP 中

(2)支援設定記憶體快取大小設定,磁碟快取路徑設定等

(3)攔截 WebView載入請求,在本地快取存在的情況下,使用本地快取,而不再發送網路請求

(4)APP 啟動時,提供多種策略更新 webapp 的靜態資源包,並支援全量更新和增量更新

(5)靜態資源更新使用機制並不侷限於 WebView,同樣支援 HotFix和 ReactNative

8 本地輕量快取策略

8.1 設計考慮

採用資源預置的方式,已經能很好的解決 H5 的流量問題和載入速度問題,然後我們的 APK 包大小翻倍。可以考慮不將資源預置到 APK 中,而是在 APP 啟動頁或其他空閒時間段且在 wifi 狀態下後臺默默下載 H5 資源。

當 html、js、css 的內容發生改變的時候(url 連結不發生變化的情況下),需要經過 APP 端資源更新後,客戶端的展示才會顯示新內容。當然,只要保證 H5 資源發生變化,採用新的資源連結的方式就能避免。

針對一款電商產品,H5 頁面會很多(包括大量的專題頁、每個商品的詳情頁等),因此使用者極可能不會瀏覽全部的頁面。如果全部資源預置或者預下載,那部分流量是沒有必要的。

我們期望能有一個輕量的快取機制:客戶端能接管瀏覽器的部分快取和使用,進而分擔瀏覽器快取的消耗,同時也不期望增大 APP 包大小,並且不依賴伺服器。考慮到我們目前的需求僅針對 H5 頁面,同時影響流量和觸發瀏覽器快取清除的主要因素是圖片資源,而圖片資源基本不會變(相同 url 對應的圖片如果發生變化就會因為 DNS 快取原因造成問題)。為此我們只需要能接管 H5 中的圖片資源,同時也不需要將資源預置,共享瀏覽器圖片快取和本地圖片快取

那麼如何實現這一功能,需要確認幾個問題:

  1. 如何攔截 H5 中的資源請求?

  2. 如何識別請求的資源是否是圖片?

  3. 如何在 H5 載入中構建自己的快取?

  4. 如何共享 H5 快取與本地圖片快取?

  5. 如何使用已經構建的快取?

8.2 攔截H5資源請求

檢視 Android Developer WebViewClient可以發現確實有介面可以攔截 H5 中的資原始檔請求。

// added in API level 21WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request);
// added in API level 11WebResourceResponse shouldInterceptRequest (WebView view, String url)
Notify the host application of a resource request and allow the application to return the data.
 If the return value is null, the WebView will continue to load the resource as usual. 
Otherwise, the return response and data will be used. 
NOTE: This method is called on a thread other than the UI thread 
so clients should exercise caution when accessing private data or the view system.

2 個方法在後臺執行緒執行,若返回值為 null,則 WebView 會繼續從網路載入資料,若不為 null,則從返回的值中獲取。

檢視原始碼可以發現確實如此:

(1)WebView 載入資源 url 時,首先交由 DefaultVideoPosterRequestHandler 嘗試攔截。否則,交由mContentsClient嘗試攔截,這裡就會呼叫 WebViewClient#shouldInterceptRequest嘗試攔截。否則,傳送訊息,交由其他元件進行網路載入。

(2)若前面攔截成功,返回結果 awWebResourceResponse 無資料,則直接回調錯誤。

8.3 識別資源中的圖片

如何識別下載的資源是圖片,很容易想到在第一次網路下載之後,去檢視本地檔案。然而這種方法並不可行,因為不同 Android 版本圖片儲存的快取目錄並不一致,同時檔案命名規則並不可見(還未查到原始碼)。另外,自行編碼根據 url 對檔案進行一次下載,而這樣子就會觸發一個 url 的 2 次下載,瀏覽器一次和自發的一次,雖然達到了目的,但是使用者的流量卻被我們浪費了,不可取。

8.3.1 根據請求 header 判斷

當 Android SDK >= 21 時,資源攔截介面為:

WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request);

我們檢查 request 的資料可以看到,Header 中顯示了 image/webp,指明瞭檔案型別。

8.3.2 根據 url 判斷

當 Android SDK < 21 時,資源攔截介面為:

WebResourceResponse shouldInterceptRequest (WebView view, String url)

此時並沒有請求 Header,無法從 Header 中獲取檔案型別,所幸從 url 的字尾還是能識別出文件型別

https://m.you.163.com/act/pub/c5AEmHDqQf.html

https://yanxuan.nosdn.127.net/14956956090752977.png

8.4 在H5載入中資料引流

為了避免相同 url 的 2 次載入(WebView 的一次載入,我們編碼實現的一次載入)而導致消耗使用者流量。我們需要一次請求載入到我們指定的磁碟快取路徑,同時還能讓 WebView 讀取到資料。檢視WebResourceResponse的定義,可以看出資源攔截之後 WebView 載入資料,是從 mInputStream中嘗試載入。、

public class WebResourceResponse {
    private boolean mImmutable;
    private String mMimeType;
    private String mEncoding;
    private int mStatusCode;
    private String mReasonPhrase;
    private Map<String, String> mResponseHeaders;
    private InputStream mInputStream;

    ....
}

那問題就好解決了,我們構建一個代理模式,將 InputStreamWrapper返回。在 WebView 從 InputStreamWrapper中讀取資料(從輸入流中獲取資料時)時,我們同時將資料導流到指定的快取路徑,進而達到引流的效果,而這個過程對於 WebView 是完全透明的。
class InputStreamWrapper extends InputStream {
    private InputStream mInnerIs;
    ...

    @Override
    public int read(byte[] buffer) throws IOException {
        int count = mInnerIs.read(buffer);
        writeOutputStream(buffer, 0, buffer.length, count);
        return count;
    }

    @Override
    public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
        int count = mInnerIs.read(buffer, byteOffset, byteCount);
        writeOutputStream(buffer, byteOffset, byteCount, count);
        return count;
    }
}

8.5 本地快取構建

前面得到了資料引流出來的資料流,我們可以將這部分位元組流輸出到本地檔案,即當圖片資源下載完成,本地檔案也就構建完成。那麼得到這個本地檔案後,要如何構建本地快取呢?這裡需要滿足以下幾個設計需求:

  1. (1)快取上限可以設定;

  2. (2)當達到上限,快取優先儲存最近使用的檔案,刪除最早;

  3. (3)使用的檔案;

  4. (4)快取路徑可以設定;

  5. (5)應用層可以刪除快取。

8.5.1 DiskLruCache

很容易想到DiskLruCache,因為上述需求均已經滿足,我們只需簡單封裝就能構建整個圖片本地快取。

8.5.2 共用圖片 SDK 本地儲存

雖然使用LruDiskCache 能實現需求,然而考慮到工程中基本上會使用相關圖片庫,這些圖片庫的都有各自的快取策略機制。

UniversalImageLoader:UnlimitedDiskCache
Fresco:DiskStorageCache
Picasso:藉助 HTTP 快取
Glide:DiskLruCache

因為我們自身的 APP 工程使用的是 Fresco,這裡就分析下如何共享Fresco 的磁碟快取。首先檢視Fresco載入網路圖片的的流程:

流程看似沒有什麼問題,然而 Fresco 的磁碟快取並不只有唯一一個,檢視 DiskCacheProducer.java可以發現有 2 個磁碟快取,分別為mSmallImageBufferedDiskCachemDefaultBufferedDiskCache。至於會使用哪個快取,和應用層ImageRequest的建立有關。

public void produceResults(
      final Consumer<EncodedImage> consumer,
      final ProducerContext producerContext) {
    ImageRequest imageRequest = producerContext.getImageRequest();

    ...

    final CacheKey cacheKey = mCacheKeyFactory.getEncodedCacheKey(imageRequest);
    final BufferedDiskCache cache =
        imageRequest.getImageType() == ImageRequest.ImageType.SMALL
            ? mSmallImageBufferedDiskCache
            : mDefaultBufferedDiskCache;
    ...
}

雖然基本上我們可以通過反射或者其他方式獲取 mDefaultBufferedDiskCache並往裡面塞 WebView 的圖片資料,但是