Glide——緩存分析整理

LruCache

概述

LruCache是Android 3.1所提供的一個緩存類,所以在Android中可以直接使用LruCache實現內存緩存。

主要算法原理是把最近使用的對象用強引用(即我們平常使用的對象引用方式)存儲在 LinkedHashMap 中。當緩存滿時,把最近最少使用的對象從內存中移除,並提供了get和put方法來完成緩存的獲取和添加操作。

簡單使用

int maxMemory = (int) (Runtime.getRuntime().totalMemory()/1024);
int cacheSize = maxMemory/8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
        @Override
        protected int sizeOf(String key, Bitmap value) {
            return value.getRowBytes()*value.getHeight()/1024;
        }
    };
  • 設置LruCache緩存大小
  • 重寫sizeOf方法計算每張圖片大小

原理

維護一個緩存對象列表,其中對象列表的排列方式是按照訪問順序實現的,即一直沒訪問的對象,將放在隊尾,即將被淘汰。而最近訪問的對象將放在隊頭,最後被淘汰。

在這裏插入圖片描述
這個隊列的維護基於LinkedHashMap。

LruCache中維護了一個集合LinkedHashMap,該LinkedHashMap是以訪問順序排序的。當調用put()方法時,就會在結合中添加元素,並調用trimToSize()判斷緩存是否已滿,如果滿了就用LinkedHashMap的迭代器刪除隊尾元素,即近期最少訪問的元素。當調用get()方法訪問緩存對象時,就會調用LinkedHashMap的get()方法獲得對應集合元素,同時會更新該元素到隊頭。

https://www.jianshu.com/p/b49a111147ee

DiskLruCache

概述

DiskLruCache 的分析分爲兩部分:分別是日誌,讀、寫緩存。

DiskLruCache 的日誌就是一個操作記錄。例如,刪除一條緩存條目,就會在日誌文件中記錄一條 REMOVE 記錄;新建一條緩存,緩存文件剛建立時會增加一條 DIRTY 記錄,緩存寫入成功後再增加一條 CLEAN 記錄;緩存被讀取,會增加 READ 記錄。日誌文件的格式如下:
在這裏插入圖片描述

記錄日誌是爲了在 DiskLruCache 初始化時建立起緩存的 LRU 結構。DiskLruCache 內部使用了 LinkHashMap 來存儲所有的緩存項,在初始化的時候,DiskLruCache 會讀日誌文件上的記錄,根據該日誌文件的記錄將所有歷史操作“重做”一遍:在執行歷史操作時,會根據日誌記錄對緩存項進行添加和刪除操作。爲什麼這樣能保證緩存項是按 LRU 的順序排序的呢?因爲構造 LinkedHashMap 的時候選擇使用 Access Order(訪問順序)來保持元素的順序,因此只需遍歷日誌根據日誌記錄向 LinkedHashMap 中放入緩存項(Entry)自然而然地就保持了 LRU 的順序。

http://liwenkun.me/2017/08/29/glide-disk-cache-strategy/

Glide緩存概述

資源分類

Glide的緩存圖片資源分爲兩類

  • 原始圖片(Source)——原始圖片大小
  • 轉換後的圖片(Result)——經過大小處理過的圖片

使用Glide加載圖片時,Glide默認會根據View對圖片進行壓縮轉換。

緩存設計

  • Glide緩存設計爲二級緩存:內存緩存和硬盤緩存
  • 緩存讀取順序:內存緩存 --> 磁盤緩存 --> 網絡
  • 內存緩存:防止應用重複將圖片數據讀取內存當中——只緩存轉換過後的圖片
  • 硬盤緩存:防止應用重複從網絡或其他地方重複下載和讀取數據——可緩存原始圖片、轉換過後的圖片

Glide內存緩存實現基於LruCache 算法 + 弱引用機制

  • LruCache算法原理:將最近使用的對象,用強引用的方式 存儲在LinkedHashMap中 ;當緩存滿時 ,將最近最少使用的對象從內存中移除
  • 弱引用:弱引用的對象具備更短生命週期,因爲 當JVM進行垃圾回收時,一旦發現弱引用對象,都會進行回收(無論內存充足否)

磁盤緩存使用Glide 自定義的DiskLruCache算法

  • 該算法基於 Lru 算法中的DiskLruCache算法,具體應用在磁盤緩存的需求場景中
  • 該算法被封裝到Glide自定義的工具類中(該工具類基於Android 提供的DiskLruCache工具類)

Glide 緩存源碼分析

1、生成key

Glide實現內存、磁盤緩存是根據圖片緩存key進行唯一標識

生成緩存 Key 的代碼發生在Engine類的 load()中

    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地址

        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());
        // Glide的緩存Key生成規則複雜:根據10多個參數生成
        // 將該id 和 signature、width、height等10個參數一起傳入到緩存Key的工廠方法裏,最終創建出一個EngineKey對象
        // 創建原理:通過重寫equals() 和 hashCode(),保證只有傳入EngineKey的所有參數都相同情況下才認爲是同一個EngineKey對象
        // 該EngineKey 即Glide中圖片的緩存Key

        ... 

概括一下

  • 獲得了一個id字符串,若圖片的來源是網絡,那麼該id = 這張圖片的url地址
  • Glide的緩存Key生成規則:根據10多個參數生成,將該id 和 signature、width、height等10個參數一起傳入到緩存Key的工廠方法裏,最終創建出一個EngineKey對象

2、創建緩存對象LruResourceCache

LruResourceCache對象是在創建 Glide 對象時創建的

public class GlideBuilder {
    ...
    Glide createGlide() {
        MemorySizeCalculator calculator = new MemorySizeCalculator(context);
        if (bitmapPool == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                int size = calculator.getBitmapPoolSize();
                bitmapPool = new LruBitmapPool(size);
            } else {
                bitmapPool = new BitmapPoolAdapter();
            }
        }

        if (memoryCache == null) {
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
            // 創建一個LruResourceCache對象 並 賦值到memoryCache對象
            // 該LruResourceCache對象 = Glide實現內存緩存的LruCache對象

        }
        
        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }
} 

這裏沒什麼多說的。

3、獲取內存緩存中的圖片

讀取內存緩存代碼實在Engine類的load()方法中

    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();

        final String id = fetcher.getId();
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());
         // 上面講解的生成圖片緩存Key


        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        // 調用loadFromCache()獲取內存緩存中的緩存圖片

        if (cached != null) {
            cb.onResourceReady(cached);
        }
        // 若獲取到,就直接調用cb.onResourceReady()進行回調

        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
            cb.onResourceReady(active);
        }
        // 若沒獲取到,就繼續調用loadFromActiveResources()獲取緩存圖片
        // 獲取到也直接回調

        // 若上述兩個方法都沒有獲取到緩存圖片,就開啓一個新的線程準備加載圖片
        // 即從上文提到的 Glide最基礎功能:圖片加載
        EngineJob current = jobs.get(key);
            return new LoadStatus(cb, current);
        }

        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);

        return new LoadStatus(cb, engineJob);
    }

    ...
} 

概括一下

  • Glide 將 內存緩存劃分爲兩塊:一塊使用了LruCache算法機制;另一塊使用了弱引用機制。
  • 當 獲取內存緩存時,會通過兩個方法分別從上述兩塊區域進行緩存獲取。
  • 若沒有找到,則新開一個線程重新獲取

<-- 方法1loadFromCache() -->
// 原理:使用了 LruCache算法
    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        // 若isMemoryCacheable = false就返回null,即內存緩存被禁用
        // 即 內存緩存是否禁用的API skipMemoryCache() - 請回看內存緩存的具體使用
        // 若設置skipMemoryCache(true),此處的isMemoryCacheable就等於false,最終返回Null,表示內存緩存已被禁用
        }

        EngineResource<?> cached = getEngineResourceFromCache(key);
        // 獲取圖片緩存 ->>分析4

        // 從分析4回來看這裏:
        if (cached != null) {
            cached.acquire();
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
            // 將獲取到的緩存圖片存儲到activeResources當中
            // activeResources = 一個弱引用的HashMap:用於緩存正在使用中的圖片
            // 好處:保護這些圖片不會被LruCache算法回收掉。 ->>方法2

        }
        return cached;
    }

<<- 分析4:getEngineResourceFromCache() ->>
// 作用:獲取圖片緩存
// 具體過程:根據緩存Key 從cache中 取值 
// 注:此處的cache對象 = 在構建Glide對象時創建的LruResourceCache對象,即說明使用的是LruCache算法
    private EngineResource<?> getEngineResourceFromCache(Key key) {
        Resource<?> cached = cache.remove(key);
        // 當從LruResourceCache中獲取到緩存圖片後,會將它從緩存中移除->>回到方法1原處
        final EngineResource result;
        if (cached == null) {
            result = null;
        } else if (cached instanceof EngineResource) {
            result = (EngineResource) cached;
        } else {
            result = new EngineResource(cached, true /*isCacheable*/);
        }
        return result;
    }

<-- 方法2loadFromActiveResources() -->
// 原理:使用了 弱引用機制
// 具體過程:當在方法1中無法獲取內存緩存中的緩存圖片時,就會從activeResources中取值
// activeResources = 一個弱引用的HashMap:用於緩存正在使用中的圖片
    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }
        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
            active = activeRef.get();
            if (active != null) {
                active.acquire();
            } else {
                activeResources.remove(key);
            }
        }
        return active;
    }

    ...
} 

概括一下

  • loadFromCache調用了getEngineResourceFromCache獲取,getEngineResourceFromCache從cache中獲取,獲取後會從cache中移除。
  • loadFromCache獲取完成後,會將此緩存放入activeResources中, 一個弱引用的HashMap:用於緩存正在使用中的圖片。
  • loadFromActiveResources方法:當在loadFromCache中無法獲取內存緩存中的緩存圖片時,就會從activeResources中取值

在這裏插入圖片描述

4、開啓加載圖片線程

當無法從內存緩存中獲得緩存的圖片時,Glide就會開啓加載圖片的線程。

但是在開啓此線程後並不會立即從網絡上加載圖片,而是使用Glide的第二級緩存——磁盤緩存。

private Resource<?> decode() throws Exception {

// 在執行 加載圖片 線程時(即加載圖片時),分兩種情況:
// 情況1:從磁盤緩存當中讀取圖片(默認情況下Glide會優先從緩存當中讀取,沒有才會去網絡源讀取圖片)
// 情況2:不從磁盤緩存中讀取圖片

// 情況1:從磁盤緩存中讀取緩存圖片
    if (isDecodingFromCache()) {
    // 取決於在使用API時是否開啓,若採用DiskCacheStrategy.NONE,即不緩存任何圖片,即禁用磁盤緩存
        return decodeFromCache();
        // 讀取磁盤緩存的入口就是這裏,此處主要講解 ->>直接看步驟4的分析9
    } else {

    // 情況2:不從磁盤緩存中讀取圖片        
    // 即上文討論的從網絡讀取圖片,此處不作過多描述
        return decodeFromSource();
    }
} 

概括一下

  • 當磁盤緩存開啓時,調用decodeFromCache()方法獲取。
  • 否則調用decodeFromSource();從網絡加載

5、獲取磁盤緩存

<--分析9decodeFromCache()  -->
private Resource<?> decodeFromCache() throws Exception {
    Resource<?> result = null;

        result = decodeJob.decodeResultFromCache();
        // 獲取磁盤緩存時,會先獲取 轉換過後圖片 的緩存
        // 即在使用磁盤緩存時設置的模式,如果設置成DiskCacheStrategy.RESULT 或DiskCacheStrategy.ALL就會有該緩存
        // 下面來分析decodeResultFromCache() ->>分析10

    }
    if (result == null) {
        result = decodeJob.decodeSourceFromCache();
        // 如果獲取不到 轉換過後圖片 的緩存,就獲取 原始圖片 的緩存
        // 即在使用磁盤緩存時設置的模式,如果設置成DiskCacheStrategy.SOURCE 或DiskCacheStrategy.ALL就會有該緩存
        // 下面來分析decodeSourceFromCache() ->>分析12
    }
    return result;
}


<--分析10decodeFromCache()  -->
public Resource<Z> decodeResultFromCache() throws Exception {
    if (!diskCacheStrategy.cacheResult()) {
        return null;
    }

    Resource<T> transformed = loadFromCache(resultKey);
    // 1. 根據完整的緩存Key(由10個參數共同組成,包括width、height等)獲取緩存圖片
    // ->>分析11

    Resource<Z> result = transcode(transformed);
    return result;
    // 2. 直接將獲取到的圖片 數據解碼 並 返回
    // 因爲圖片已經轉換過了,所以不需要再作處理
    // 回到分析9原處
}


<--分析11decodeFromCache()  -->
private Resource<T> loadFromCache(Key key) throws IOException {
    File cacheFile = diskCacheProvider.getDiskCache().get(key);

    // 1. 調用getDiskCache()獲取Glide自己編寫的DiskLruCache工具類實例
    // 2. 調用上述實例的get() 並 傳入完整的緩存Key,最終得到硬盤緩存的文件

    if (cacheFile == null) {
        return null;
        // 如果文件爲空就返回null
    }
    Resource<T> result = null;
    try {
        result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
            } finally {
        if (result == null) {
            diskCacheProvider.getDiskCache().delete(key);
        }
    }
    return result;
    // 如果文件不爲空,則將它解碼成Resource對象後返回
    // 回到分析10原處
}



<--分析12decodeFromCache()  -->
public Resource<Z> decodeSourceFromCache() throws Exception {
    if (!diskCacheStrategy.cacheSource()) {
        return null;
    }

    Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
    // 1. 根據緩存Key的OriginalKey來獲取緩存圖片
    // 相比完整的緩存Key,OriginalKey只使用了id和signature兩個參數,而忽略了大部分的參數
    // 而signature參數大多數情況下用不到,所以基本是由id(也就是圖片url)來決定的Original緩存Key
    // 關於loadFromCache()同分析11,只是傳入的緩存Key不一樣

    return transformEncodeAndTranscode(decoded);
    // 2. 先將圖片數據 轉換 再 解碼,最終返回
    
} 

概括一下整個流程

  • 首先調用decodeJob.decodeResultFromCache(),內部再調用loadFromCache(resultKey),獲得經轉換過的緩存,其中resultKey是一個完成的key,包括十多個參數,loadFromCache(resultKey)調用getDiskCache()獲得DiskLruCache實例,接着通過他得到磁盤緩存。
  • 如果轉換過的圖片獲得不到,調用decodeJob.decodeSourceFromCache(),獲得原始圖片緩存。接着又調用loadFromCache(resultKey.getOriginalKey()); 不過這時的key只使用了id和signature兩個參數,而忽略了大部分的參數,這也就意味着獲得的緩存是原始的圖片,最後調用transformEncodeAndTranscode(decoded);進行轉碼返回

在這裏插入圖片描述

6、寫入磁盤

寫入時機:獲得圖片資源後,圖片加載完成前。
寫入類型分爲:原始寫入、轉換後寫入

private Resource<?> decode() throws Exception {

// 在執行 加載圖片 線程時(即加載圖片時),分兩種情況:
// 情況1:從磁盤緩存當中讀取圖片(默認情況下Glide會優先從緩存當中讀取,沒有才會去網絡源讀取圖片)
// 情況2:不從磁盤緩存中讀取圖片

// 情況1:從磁盤緩存中讀取緩存圖片
    if (isDecodingFromCache()) {
        return decodeFromCache();
        // 讀取磁盤緩存的入口就是這裏,上面已經講解
    } else {

    // 情況2:不從磁盤緩存中讀取圖片
        // 即上文討論的從網絡讀取圖片,不採用緩存
        // 寫入磁盤緩存就是在 此處 寫入的 ->>分析13
        return decodeFromSource();
    }
}


<--分析13decodeFromSource()  -->
public Resource<Z> decodeFromSource() throws Exception {
    Resource<T> decoded = decodeSource();
    // 解析圖片
    // 寫入原始圖片 磁盤緩存的入口 ->>分析14

     // 從分析16回來看這裏
    return transformEncodeAndTranscode(decoded);
    // 對圖片進行轉碼
    // 寫入 轉換後圖片 磁盤緩存的入口 ->>分析17
}


<--分析14decodeSource()  -->
private Resource<T> decodeSource() throws Exception {
    Resource<T> decoded = null;
    try {

        final A data = fetcher.loadData(priority);
        // 讀取圖片數據
        if (isCancelled) {
            return null;
        }
        decoded = decodeFromSourceData(data);
        // 對圖片進行解碼 ->>分析15
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}

<--分析15decodeFromSourceData()  -->
private Resource<T> decodeFromSourceData(A data) throws IOException {
    final Resource<T> decoded;
    // 判斷是否允許緩存原始圖片
    // 即在使用 硬盤緩存API時,是否採用DiskCacheStrategy.ALL 或 DiskCacheStrategy.SOURCE
    if (diskCacheStrategy.cacheSource()) {
        decoded = cacheAndDecodeSourceData(data);
        // 若允許緩存原始圖片,則調用cacheAndDecodeSourceData()進行原始圖片的緩存 ->>分析16

    } else {
        long startTime = LogTime.getLogTime();
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
    }
    return decoded;
}

<--分析16:cacheAndDecodeSourceData   -->
private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
   
    ...
    diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
    // 1. 調用getDiskCache()獲取DiskLruCache實例
    // 2. 調用put()寫入硬盤緩存
    // 注:原始圖片的緩存Key是用的getOriginalKey(),即只有id & signature兩個參數
    // 請回到分析13

}

<--分析17:transformEncodeAndTranscode() -->
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
    
    Resource<T> transformed = transform(decoded);
    // 1. 對圖片進行轉換

    writeTransformedToCache(transformed);
    // 2. 將 轉換過後的圖片 寫入到硬盤緩存中 -->分析18

    Resource<Z> result = transcode(transformed);
    return result;
}

<-- 分析18:TransformedToCache() -->
private void writeTransformedToCache(Resource<T> transformed) {
    if (transformed == null || !diskCacheStrategy.cacheResult()) {
        return;
    }

    diskCacheProvider.getDiskCache().put(resultKey, writer);
    // 1. 調用getDiskCache()獲取DiskLruCache實例
    // 2. 調用put()寫入硬盤緩存
    // 注:轉換後圖片的緩存Key是用的完整的resultKey,即含10多個參數
} 

概括一下上述過程

  • 調用decodeFromSource()從網絡讀取圖片
  • 調用decodeSource();進行加載圖片,然後調用decodeFromSourceData(data);進行解碼,順便進行檢測是否允許緩存原始圖片,調用cacheAndDecodeSourceData()進行原始圖片的緩存。
  • 接着調用transformEncodeAndTranscode(decoded);
  • 首先調用transform(decoded);對圖片進行轉換
  • 接着調用writeTransformedToCache(transformed)將轉換過的圖片寫入緩存,這時的key是完整的resultKey,即含10多個參數。

在這裏插入圖片描述

7、寫入內存緩存

緩存時機:圖片加載完成後 、圖片顯示出來前

當圖片加載完成後,會在EngineJob中通過Handler發送一條消息將執行邏輯切回到主線程當中,從而執行handleResultOnMainThread()

    private void handleResultOnMainThread() {
        ... 
        // 關注1:寫入 弱引用緩存
        engineResource = engineResourceFactory.build(resource, isCacheable);
        listener.onEngineJobComplete(key, engineResource);

        // 關注2:寫入 LruCache算法的緩存
        engineResource.acquire();

        for (ResourceCallback cb : cbs) {
            if (!isInIgnoredCallbacks(cb)) {
                engineResource.acquire();
                cb.onResourceReady(engineResource);
            }
        }
        engineResource.release();
    } 

寫入 內存緩存分爲:寫入弱引用緩存、LruCache算法的緩存,內存緩存只緩存 轉換後的圖片

寫入弱引用緩存

    private void handleResultOnMainThread() {
        ...

        // 寫入 弱引用緩存
        engineResource = engineResourceFactory.build(resource, isCacheable);
        // 創建一個包含圖片資源resource的EngineResource對象

        listener.onEngineJobComplete(key, engineResource);
        // 將上述創建的EngineResource對象傳入到Engine.onEngineJobComplete() ->>分析6


        // 寫入LruCache算法的緩存(先忽略)
        engineResource.acquire();

        for (ResourceCallback cb : cbs) {
            if (!isInIgnoredCallbacks(cb)) {
                engineResource.acquire();
                cb.onResourceReady(engineResource);
            }
        }
        engineResource.release();
    }


<<- 分析6onEngineJobComplete()() ->>
public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
    ...    

    @Override
    public void onEngineJobComplete(Key key, EngineResource<?> resource) {
        Util.assertMainThread();

        if (resource != null) {
            resource.setResourceListener(key, this);
            if (resource.isCacheable()) {
                activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
                // 將 傳進來的EngineResource對象 添加到activeResources()中
                // 即寫入了弱引用 內存緩存
            }
        }
        jobs.remove(key);
    } 

概括一下

  • 首先創建一個包含圖片資源resource的EngineResource對象
  • 將傳進來的EngineResource對象添加到activeResources中

寫入LruCache

    private void handleResultOnMainThread() {
        ... 
        // 寫入 LruCache算法的緩存
        engineResource.acquire();
        // 標記1 
        for (ResourceCallback cb : cbs) {
            if (!isInIgnoredCallbacks(cb)) {
                engineResource.acquire();
                // 標記2
                cb.onResourceReady(engineResource);
            }
        }
        engineResource.release();
        // 標記3
    } 

緩存原理

  • 用 一個 acquired 變量 記錄圖片被引用的次數
  • 加載圖片時:調用 acquire() ,變量加1
<-- 分析7acquire() -->
    void acquire() {
        if (isRecycled) {
            throw new IllegalStateException("Cannot acquire a recycled resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call acquire on the main thread");
        }
        ++acquired;
        // 當調用acquire()時,acquired變量 +1
    } 

不加載圖片時,調用 release() 時,變量減1

<-- 分析8release()  -->
    void release() {
        if (acquired <= 0) {
            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call release on the main thread");
        }
        if (--acquired == 0) {
            listener.onResourceReleased(key, this);
            // 若acquired變量 = 0,即說明圖片已經不再被使用
            // 調用listener.onResourceReleased()釋放資源
            // 該listener = Engine對象,Engine.onResourceReleased()->>分析9
        }
    }
}

<-- 分析9:onResourceReleased()  -->

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

    private final MemoryCache cache;
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
    ...    

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource resource) {
        Util.assertMainThread();
        activeResources.remove(cacheKey);
        // 步驟1:將緩存圖片從activeResources弱引用緩存中移除

        if (resource.isCacheable()) {
            cache.put(cacheKey, resource);
            // 步驟2:將該圖片緩存放在LruResourceCache緩存中
        } else {
            resourceRecycler.recycle(resource);
        }
    }
    ...
} 

概括一下

  • 當 acquired 變量 >0 時,說明圖片正在使用,即該圖片緩存繼續存放到activeResources弱引用緩存中
  • 當 acquired變量 = 0,即說明圖片已經不再被使用,就將該圖片的緩存Key從 activeResources弱引用緩存中移除,並存放到LruResourceCache緩存中
  • 正在使用中的圖片 採用 弱引用 的內存緩存
  • 不在使用中的圖片 採用 LruCache算法 的內存緩存

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章