Android 圖片載入快取問題:為什麼你的Glide快取沒有起作用?

Carson_Ho發表於2019-02-28

前言

  • Glide,該功能非常強大 Android 圖片載入開源框架 相信大家並不陌生

    Github截圖

  • 正由於他的功能強大,所以它的原始碼非常複雜,這導致很多人望而卻步

  • 本人嘗試將 Glide 的功能進行分解,並單獨針對每個功能進行原始碼分析,從而降低Glide原始碼的複雜度。

接下來,我將推出一系列關於 Glide的功能原始碼分析,有興趣可以繼續關注

  • 今天,我將主要講解在使用Glide快取功能時的問題:為什麼Glide 的快取無起作用,希望你們會喜歡。

請先閱讀文章:

  1. Android原始碼分析:手把手帶你深入瞭解Glide的快取機制
  2. Android:這是一份全面 & 詳細的圖片載入庫Glide原始碼分析

1. 背景

  • Glide實現記憶體 & 磁碟快取是根據 圖片的快取Key進行唯一標識
  • 開發者為了降低成本 & 安全,往往會將圖片存放在雲伺服器上

如 七牛雲 等等。

  • 為了保護 客戶的圖片資源,圖片雲伺服器 會在圖片Url地址的基礎上再加一個token引數
http://url.com/image.jpg?token=a6cvva6b02c670b0a
複製程式碼
  • Glide載入該圖片時,會使用加了token引數的圖片Url地址 作為 id引數,從而生成 快取Key

2. 問題

  • 作為身份認證的token引數可能會發生變化,並不是一成不變
  • token引數變了,則圖片Url跟著變,則生成快取key的所需id引數發生變化,即 快取Key也會跟著變化
  • 這導致同一張圖片,但因為token引數變化,而導致快取Key發生變化,從而使得 Glide的快取功能失效

快取Key發生變化,即同一個圖片的當前快取key 和 之前寫入快取的key不相同,這意味著 在讀取快取時 無法根據當前快取key 找到之前的快取,從而使得失效


3. 解決方案

3.1 原理

在 生成快取Key 的id引數 前,將 帶有token引數的圖片Url地址 去掉 token引數,從而根據 初始的圖片Url地址 生成快取Key的id引數

實現了一個圖片的快取Key的id引數始終唯一 ,即等於 圖片Url地址

3.2 儲備知識:生成快取Key的id引數的邏輯

生成快取Keyid引數的邏輯為:直接將圖片的 URL 地址作為快取Key的id引數

請回看文章生成快取Key的程式碼:Android原始碼分析:手把手帶你深入瞭解Glide的快取機制

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {

    public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        final String id = fetcher.getId();
        // 獲得了一個id字串,即需載入圖片的唯一標識
        // 如,若圖片的來源是網路,那麼該id = 這張圖片的url地址
        // fetcher = HttpUrlFetcher的例項,即呼叫HttpUrlFetcher.getid()->>分析19

        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());
        // 將該id 和 signature、width、height等10個引數一起傳入到快取Key的工廠方法裡,最終建立出一個EngineKey物件
        // 建立原理:通過重寫equals() 和 hashCode(),保證只有傳入EngineKey的所有引數都相同情況下才認為是同一個EngineKey物件
       // 該EngineKey 即Glide中的快取Key

        ...
}


<-- 分析19:getId() -->
public class HttpUrlFetcher implements DataFetcher<InputStream> {
    ...

    private final GlideUrl glideUrl;
    // GlideUrl = 在上篇文章講解 圖片載入 第2步load()中傳入圖片url地址時,Glide在內部把圖片url地址包裝成一個GlideUrl物件

    @Override
    public String getId() {
        return glideUrl.getCacheKey();
        // ->>分析20
}

<-- 分析20:getCacheKey()  -->

public class GlideUrl {

    private final URL url;
    private final String stringUrl;
    ...

    // GlideUrl建構函式
     public GlideUrl(URL url) {
        this(url, Headers.DEFAULT);
    }

    public GlideUrl(String url) {
        this(url, Headers.DEFAULT);
    }


    public String getCacheKey() {
        return stringUrl != null ? stringUrl : url.toString();
        // 在生成GlideUrl物件時:
        // 若傳入的是URL字串(即圖片地址),就直接返回該字串(大多數是這種情況)
        // 若傳入的是URL物件,那麼就返回這個物件toString()後的結果。

    }

    ...
}

複製程式碼

3.3 實現方案

我們只需重寫getCacheKey() & 將 帶有token引數的圖片Url地址 去掉 token引數 即可。

/**
  * 程式碼實現:建立一個GlideUrl類的子類 & 重寫getCacheKey()
  **/
    // 1. 繼承GlideUrl 
    public class mGlideUrl extends GlideUrl {

        private String mUrl;

        // 建構函式裡 傳入 帶有token引數的圖片Url地址 
        public MyGlideUrl(String url) {
            super(url);
            mUrl = url;
        }

        // 2. 重寫getCacheKey()
        @Override
        public String getCacheKey() {
            return mUrl.replace(deleteToken(), "");
            // 通過 deleteToken() 從 帶有token引數的圖片Url地址中 去掉 token引數
            // 最終返回一個沒有token引數、初始的圖片URL地址
            // ->>分析1
        }

        // 分析1:deleteToken()
        private String deleteToken() {
            String tokenParam = "";
            int tokenKeyIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token=");
            if (tokenKeyIndex != -1) {
                int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1);
                if (nextAndIndex != -1) {
                    tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
                } else {
                    tokenParam = mUrl.substring(tokenKeyIndex);
                }
            }
            return tokenParam;
        }

    }

/**
  * 使用快取時:需要在load()中傳入自定義的 mGlideUrl物件
  **/

    Glide.with(this)
         .load(new mGlideUrl(url))
         .into(imageView);

    // 注:a. 若像之前直接傳入圖片的url地址,那麼在內部還是會使用原始的GlideUrl類
    //    b. 即直接將傳入傳入圖片的url地址作為快取key的Id引數,而沒有對token引數作任何處理

複製程式碼

4. 總結

  • 本文主要對Glide的圖片快取功能 的使用問題進行講解
  • 關於Glide的相關文章閱讀
  1. Android原始碼分析:手把手帶你深入瞭解Glide的快取機制
  2. Android:這是一份全面 & 詳細的圖片載入庫Glide原始碼分析

請幫頂 / 評論點贊!因為你的鼓勵是我寫作的最大動力!

相關文章