Android緩存機(jī)制:如果沒(méi)有緩存,在大量的網(wǎng)絡(luò)請(qǐng)求從遠(yuǎn)程獲取圖片時(shí)會(huì)造成網(wǎng)絡(luò)流量的浪費(fèi),加載速度較慢,用戶體驗(yàn)不好
關(guān)于學(xué)習(xí)Glide緩存原理前十分建議你先了解圖片加載的流程,在這基礎(chǔ)上再進(jìn)行學(xué)習(xí)會(huì)更加上手。然后可以看思維導(dǎo)圖,從宏觀角度理解Glide加載。本文的源碼基于V4版本
Glide系列文章
Glide源碼分析流程思維導(dǎo)圖
【兩篇就懂系列】Glide源碼分析之加載圖片流程(1/2)
【兩篇就懂系列】Glide源碼分析之加載圖片流程(2/2)
Glide圖片加載庫(kù)從v3遷移到v4的改變和使用
【Glide的緩存】
在緩存這一功能上,Glide將它分成了兩個(gè)模塊,一個(gè)是內(nèi)存緩存,一個(gè)是硬盤緩存。同時(shí)內(nèi)存緩存又分為兩級(jí),一級(jí)是LruCache緩存,一級(jí)是弱引用緩存。
- 內(nèi)存緩存的作用:防止應(yīng)用重復(fù)將圖片數(shù)據(jù)讀取到內(nèi)存當(dāng)中。
- LruCache緩存:不在使用中的圖片使用LruCache來(lái)進(jìn)行緩存。
- 弱引用緩存:把正在使用中的圖片使用弱引用來(lái)進(jìn)行緩存。
【這樣的目的保護(hù)正在使用的資源不會(huì)被LruCache算法回收。】
- 硬盤緩存的作用:防止應(yīng)用重復(fù)從網(wǎng)絡(luò)或其他地方重復(fù)下載和讀取數(shù)據(jù)。
默認(rèn)情況下,Glide 會(huì)在開(kāi)始一個(gè)新的圖片請(qǐng)求之前檢查以下多級(jí)的緩存:
- 內(nèi)存緩存 (Memory cache) - 該圖片是否最近被加載過(guò)并仍存在于內(nèi)存中?即LruCache緩存。
- 活動(dòng)資源 (Active Resources) - 現(xiàn)在是否有另一個(gè) View 正在展示這張圖片?也就是弱引用緩存。
- 資源類型(Resource) - 該圖片是否之前曾被解碼、轉(zhuǎn)換并寫入過(guò)磁盤緩存?
- 數(shù)據(jù)來(lái)源 (Data) - 構(gòu)建這個(gè)圖片的資源是否之前曾被寫入過(guò)文件緩存?
前兩步檢查圖片是否在內(nèi)存中,如果是則直接返回圖片。后兩步則檢查圖片是否在磁盤上,以便快速但異步地返回圖片。
如果四個(gè)步驟都未能找到圖片,則Glide會(huì)返回到原始資源以取回?cái)?shù)據(jù)(原始文件,Uri, Url等)
結(jié)合源碼去分析Glide緩存
首先在[圖片加載源碼分析一]的文章中,我們?cè)谕ㄟ^(guò)單例獲取Glide的實(shí)例時(shí),調(diào)用過(guò)checkAndInitializeGlide(context)這個(gè)方法,在具體的方法里有一段代碼是通過(guò)GlideBuilder.build初始化一些對(duì)象,如下
GlideBuilder類
public Glide build(Context context) {
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
if (arrayPool == null) {
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}
//1
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
//2
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor());
}
RequestManagerRetriever requestManagerRetriever = new RequestManagerRetriever(
requestManagerFactory);
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptions.lock(),
defaultTransitionOptions);
}
在我代碼中標(biāo)記1和2處,1處new出了一個(gè)LruResourceCache,并把它賦值到了memoryCache這個(gè)對(duì)象上面。你沒(méi)有猜錯(cuò),這個(gè)就是Glide實(shí)現(xiàn)內(nèi)存緩存所使用的LruCache對(duì)象了。2處InternalCacheDiskCacheFactory是磁盤緩存(內(nèi)部存儲(chǔ))所使用的工廠對(duì)象。同時(shí)在其中初始化了磁盤緩存的大小和文件的路徑。
創(chuàng)建好了這些對(duì)象說(shuō)明我們已經(jīng)把準(zhǔn)備工作做好了。
【內(nèi)存緩存】
接口為MemoryCache,Glide使用LruResourceCache作為默認(rèn)的內(nèi)存緩存,該類是接口MemoryCache的一個(gè)缺省實(shí)現(xiàn)(接口的另一個(gè)實(shí)現(xiàn)類為MemoryCacheAdapter)。使用固定大小的內(nèi)存和 LRU 算法。LruResourceCache的大小由 Glide 的MemorySizeCalculator類來(lái)決定,這個(gè)類主要關(guān)注設(shè)備的內(nèi)存類型,設(shè)備 RAM 大小,以及屏幕分辨率。
【內(nèi)存緩存的讀取】
我們先分析Glide從哪里讀取內(nèi)存緩存,以及內(nèi)存緩存的原理。
1. Lurcache算法
對(duì)于大多內(nèi)存緩存的實(shí)現(xiàn),我們通常會(huì)知道這樣一個(gè)算法,LruCache算法(Least Recently Used),也叫近期最少使用算法。它的主要算法原理就是把最近使用的對(duì)象用強(qiáng)引用存儲(chǔ)在LinkedHashMap(雙向循環(huán)列表)中,并且把最近最少使用的對(duì)象在緩存值達(dá)到預(yù)設(shè)定值之前從內(nèi)存中移除。淘汰最長(zhǎng)時(shí)間未使用的對(duì)象
上面我們提到的LruResourceCache就是Glide實(shí)現(xiàn)內(nèi)存緩存所使用的LruCache對(duì)象了,而這個(gè)類繼承LruCache。也是最近最少使用算法的具體實(shí)現(xiàn)。
public class LruCache<T, Y> {
private final LinkedHashMap<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
private final int initialMaxSize;
private int maxSize;
private int currentSize = 0;
/**
* Constructor for LruCache.
*
* @param size The maximum size of the cache, the units must match the units used in {@link
* #getSize(Object)}.
*/
public LruCache(int size) {
this.initialMaxSize = size;
this.maxSize = size;
}
/**
* Sets a size multiplier that will be applied to the size provided in the constructor to put the
* new size of the cache. If the new size is less than the current size, entries will be evicted
* until the current size is less than or equal to the new size.
*
* @param multiplier The multiplier to apply.
*/
public synchronized void setSizeMultiplier(float multiplier) {
if (multiplier < 0) {
throw new IllegalArgumentException("Multiplier must be >= 0");
}
maxSize = Math.round(initialMaxSize * multiplier);
evict();
}
/**
* Returns the size of a given item, defaulting to one. The units must match those used in the
* size passed in to the constructor. Subclasses can override this method to return sizes in
* various units, usually bytes.
*
* @param item The item to get the size of.
*/
protected int getSize(Y item) {
return 1;
}
/**
* Returns the number of entries stored in cache.
*/
protected synchronized int getCount() {
return cache.size();
}
/**
* A callback called whenever an item is evicted from the cache. Subclasses can override.
*
* @param key The key of the evicted item.
* @param item The evicted item.
*/
protected void onItemEvicted(T key, Y item) {
// optional override
}
/**
* Returns the current maximum size of the cache in bytes.
*/
public synchronized int getMaxSize() {
return maxSize;
}
/**
* Returns the sum of the sizes of all items in the cache.
*/
public synchronized int getCurrentSize() {
return currentSize;
}
/**
* Returns true if there is a value for the given key in the cache.
*
* @param key The key to check.
*/
public synchronized boolean contains(T key) {
return cache.containsKey(key);
}
/**
* Returns the item in the cache for the given key or null if no such item exists.
*
* @param key The key to check.
*/
@Nullable
public synchronized Y get(T key) {
return cache.get(key);
}
/**
* Adds the given item to the cache with the given key and returns any previous entry for the
* given key that may have already been in the cache.
*
* <p> If the size of the item is larger than the total cache size, the item will not be added to
* the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with
* the given key and item. </p>
*
* @param key The key to add the item at.
* @param item The item to add.
*/
public synchronized Y put(T key, Y item) {
final int itemSize = getSize(item);
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}
final Y result = cache.put(key, item);
if (item != null) {
currentSize += getSize(item);
}
if (result != null) {
// TODO: should we call onItemEvicted here?
currentSize -= getSize(result);
}
evict();
return result;
}
/**
* Removes the item at the given key and returns the removed item if present, and null otherwise.
*
* @param key The key to remove the item at.
*/
@Nullable
public synchronized Y remove(T key) {
final Y value = cache.remove(key);
if (value != null) {
currentSize -= getSize(value);
}
return value;
}
/**
* Clears all items in the cache.
*/
public void clearMemory() {
trimToSize(0);
}
/**
* Removes the least recently used items from the cache until the current size is less than the
* given size.
*
* @param size The size the cache should be less than.
*/
protected synchronized void trimToSize(int size) {
Map.Entry<T, Y> last;
while (currentSize > size) {
last = cache.entrySet().iterator().next();
final Y toRemove = last.getValue();
currentSize -= getSize(toRemove);
final T key = last.getKey();
cache.remove(key);
onItemEvicted(key, toRemove);
}
}
private void evict() {
trimToSize(maxSize);
}
}
2. Glide內(nèi)存緩存的實(shí)現(xiàn)自然也是使用的LruCache算法。不過(guò)除了LruCache算法之外,Glide還結(jié)合了一種弱引用的機(jī)制,共同完成了內(nèi)存緩存功能。這樣做的目的是把正在使用中的圖片使用弱引用來(lái)進(jìn)行緩存,不在使用中的圖片使用LruCache來(lái)進(jìn)行緩存的功能。分工合作,保護(hù)正在使用的資源不會(huì)被LruCache算法回收掉。(劃重點(diǎn))
3. Glide默認(rèn)情況下,Glide自動(dòng)就是開(kāi)啟內(nèi)存緩存的。也就是說(shuō),當(dāng)我們使用Glide加載了一張圖片之后,這張圖片就會(huì)被緩存到內(nèi)存當(dāng)中,只要在它還沒(méi)從內(nèi)存中被清除之前,下次使用Glide再加載這張圖片都會(huì)直接從內(nèi)存當(dāng)中讀取,而不用重新從網(wǎng)絡(luò)或硬盤上讀取了,這樣無(wú)疑就可以大幅度提升圖片的加載效率。比方我們?cè)谑褂肦ecyclerView、listview、viewpager這種控件時(shí),反復(fù)上下滑動(dòng),當(dāng)移出屏幕的項(xiàng)被回收再次移入屏幕展示時(shí),那么只要是Glide加載過(guò)的圖片都可以直接從內(nèi)存當(dāng)中迅速讀取并展示出來(lái)。
4. 看過(guò)了之前圖片加載的兩篇文章,我們?cè)诘谌絠nto時(shí),onSizeReady準(zhǔn)備圖片加載時(shí),會(huì)調(diào)用Engine.load這個(gè)比較重要的方法,在上一篇文章分析時(shí),我們忽略了對(duì)緩存的處理,而是直接分析沒(méi)有緩存的加載過(guò)程。而這篇文章我們返回看緩存處理。
重新放出Engine.load方法。
Engine類
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
//a
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//b
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
//c
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
//d
EngineJob<?> current = jobs.get(key);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
//e
EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
useUnlimitedSourceExecutorPool, useAnimationPool);
DecodeJob<R> decodeJob = decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
-
//a處:首先通過(guò)KeyFactory的buildKey方法創(chuàng)建了一個(gè)EngineKey對(duì)象(緩存鍵),這個(gè)對(duì)象就是我們說(shuō)的緩存key,加載資源的唯一標(biāo)識(shí)??梢钥吹?jīng)Q定緩存Key的條件非常多,即使你用override()方法改變了一下圖片的width或者h(yuǎn)eight,也會(huì)生成一個(gè)完全不同的緩存Key。
-
//b處:通過(guò)loadFromCache方法,通過(guò)key查找緩存資源,此時(shí)的緩存為內(nèi)存緩存,如果獲取的到就直接調(diào)用cb.onResourceReady()方法進(jìn)行回調(diào)。
-
//c處:如果內(nèi)存緩存沒(méi)有找到對(duì)應(yīng)key的資源,則調(diào)用loadFromActiveResources方法,還是通過(guò)key獲取緩存資源,而此時(shí)的緩存也是內(nèi)存緩存。獲取到的話也直接進(jìn)行回調(diào)。
也就是說(shuō),Glide的圖片加載過(guò)程中會(huì)調(diào)用兩個(gè)方法來(lái)獲取內(nèi)存緩存,loadFromCache()和loadFromActiveResources()。這兩個(gè)方法中前者使用的就是LruCache算法,后者使用的就是弱引用。
-
//d處:如果以上都沒(méi)有找到,那是否可能該緩存任務(wù)正在處理,還沒(méi)有完成緩存,所以根據(jù)key判斷緩存的job中是否有current,如果有,就不用新創(chuàng)建任務(wù)了,而是給其添加回調(diào),等待完成后獲取。
-
//e處:如果以上條件都不滿足,我們就需要?jiǎng)?chuàng)建新的加載任務(wù)。并把當(dāng)前任務(wù)存放在jobs這個(gè)map中。同時(shí)要開(kāi)啟線程來(lái)加載新的圖片了。
5.看一下loadFromCache()和loadFromActiveResources()這兩個(gè)方法的源碼:
//Engine類
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...省略
//內(nèi)存緩存 (Memory cache) - 該圖片是否最近被加載過(guò)并仍存在于內(nèi)存中?
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
//a
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
//c
cached.acquire();
//d
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
@SuppressWarnings("unchecked")
private EngineResource<?> getEngineResourceFromCache(Key key) {
//b
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/);
}
return result;
}
//活動(dòng)資源 (Active Resources) - 現(xiàn)在是否有另一個(gè) View 正在展示這張圖片?
//e
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();//得到引用的資源
//再次判斷是為了防止引用被清空,或gc回收
if (active != null) {
active.acquire();
} else {
activeResources.remove(key);
}
}
return active;
}
-
//a處:首先關(guān)于傳入?yún)?shù)isMemoryCacheable,代表內(nèi)存緩存是否被開(kāi)啟,Glide默認(rèn)為開(kāi)啟,true。但如果想要禁用的話呢?通過(guò)向skipMemoryCache()傳入true,此時(shí)isMemoryCacheable將為false,返回值也為null。Glide-v3到v4寫法的變化
GlideApp.with(fragment)
.load(url)
.skipMemoryCache(true)
.into(view);
-
//b處:接著調(diào)用了getEngineResourceFromCache(key)方法來(lái)獲取緩存。在這個(gè)方法中,會(huì)使用緩存Key來(lái)從cache當(dāng)中取值,而這里的cache對(duì)象就是在構(gòu)建Glide對(duì)象時(shí)創(chuàng)建的LruResourceCache,那么說(shuō)明這里其實(shí)使用的就是LruCache算法了。當(dāng)我們從LruResourceCache中獲取到緩存圖片之后會(huì)將它從緩存中移除。cache.remove(key)。這個(gè)語(yǔ)句既返回了對(duì)應(yīng)key的value值,也將對(duì)應(yīng)的key從cache中移除。
-
//c處:如果cached不為null,首先調(diào)用cached.acquire();EngineResource用一個(gè)acquired變量來(lái)記錄圖片被引用的次數(shù),調(diào)用acquire()方法會(huì)讓變量acquire加1,調(diào)用release()方法會(huì)讓變量減1。(當(dāng)acquired變量大于0的時(shí)候,說(shuō)明圖片正在使用中,也就應(yīng)該放到activeResources弱引用緩存當(dāng)中,如果acquired變量等于0了,說(shuō)明圖片已經(jīng)不在使用中了。釋放資源,從activeResources弱引用緩存中移除,并put到LruResourceCache當(dāng)中)
-
//d處:然后將這個(gè)緩存圖片存儲(chǔ)到activeResources當(dāng)中。activeResources就是一個(gè)弱引用的HashMap,用來(lái)緩存正在使用中的圖片,我們可以看到,loadFromActiveResources()方法就是從activeResources這個(gè)HashMap當(dāng)中取值的。使用activeResources來(lái)緩存正在使用中的圖片,可以保護(hù)這些圖片不會(huì)被LruCache算法回收掉。
-
//e處:如果從內(nèi)存中沒(méi)有找到資源,那有一種可能為該資源已被LruCache算法移除,但是它正在被另一個(gè)view展示,所以此時(shí)還是有此資源的緩存。所以查找,存在,引用值加1,不存在,則把key從activeResources弱引用緩存中移除。
【內(nèi)存緩存的寫入】
1.當(dāng)沒(méi)有讀取到緩存時(shí),我們肯定要正常開(kāi)啟線程去下載資源了,具體流程可以看之前的文章,那么從服務(wù)端得到資源后是何時(shí)以及如何寫入到緩存中呢?下面來(lái)具體分析:
上一篇文章講解過(guò),當(dāng)從服務(wù)端得到stream然后做處理得到的最終圖片資源通過(guò)層層回調(diào)返回等最終交給EngineJob的onResourceReady的方法處理。而在這個(gè)方法中通過(guò)Handler發(fā)送一條消息將執(zhí)行邏輯切回到主線程當(dāng)中,從而執(zhí)行handleResultOnMainThread()方法。
EngineJob類
...
private final EngineJobListener listener;
...
void handleResultOnMainThread() {
stateVerifier.throwIfRecycled();
if (isCancelled) {
resource.recycle();
release(false /*isRemovedFromQueue*/);
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
} else if (hasResource) {
throw new IllegalStateException("Already have resource");
}
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
// Hold on to resource for duration of request so we don't recycle it in the middle of
// notifying if it synchronously released by one of the callbacks.
engineResource.acquire();
listener.onEngineJobComplete(key, engineResource);
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource, dataSource);
}
}
// Our request is complete, so we can release the resource.
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
在這個(gè)方法里,通過(guò)EngineResourceFactory構(gòu)建出了一個(gè)包含圖片資源的EngineResource對(duì)象,然后將這個(gè)對(duì)象回調(diào)到Engine的onEngineJobComplete()方法當(dāng)中.
Engine類
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
@SuppressWarnings("unchecked")
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
// TODO: should this check that the engine job is still current?
jobs.remove(key);
}
}
重點(diǎn):先將resource添加監(jiān)聽(tīng),然后回調(diào)過(guò)來(lái)的EngineResource被put到了activeResources當(dāng)中,在這里寫入到了內(nèi)存緩存的弱引用緩存。寫入到弱引用緩存的原因是這個(gè)資源是屬于正在被加載展示的資源,也就是正在被使用的資源。
那什么時(shí)候要寫入到內(nèi)存緩存的LruCache中呢?我們說(shuō)過(guò)要將不在使用中的圖片使用LruCache來(lái)進(jìn)行緩存,那怎么判斷是否在使用中?那就是前面講到的EngineResource中的一個(gè)引用機(jī)制,通過(guò)acquired的值來(lái)判斷。(當(dāng)acquired變量大于0的時(shí)候,說(shuō)明圖片正在使用中,也就應(yīng)該放到activeResources弱引用緩存當(dāng)中,如果acquired變量等于0了,說(shuō)明圖片已經(jīng)不在使用中了,此時(shí)放到LruCache來(lái)進(jìn)行緩存。)acquired的增加和減少通過(guò)EngineResource的acquire()和release()方法。
EngineResource類
class EngineResource<Z> implements Resource<Z> {
private int acquired;
private ResourceListener listener;
...
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;
}
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);
}
}
}
在release方法可以看到,當(dāng)acquired=0時(shí),調(diào)用engine的onResourceReleased();
Engine類
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
當(dāng)不在使用中時(shí),此時(shí)就可以從 activeResources移除,同時(shí)就可以添加到Lrucache中了。此處為寫入內(nèi)存緩存的LruCache地方。
截止到此 內(nèi)存緩存分析完畢。
【磁盤緩存】
接口為DiskCache,Glide 使用DiskLruCacheWrapper作為默認(rèn)的磁盤緩存,該類是接口MemoryCache的實(shí)現(xiàn)類(該接口的另一個(gè)實(shí)現(xiàn)類為DiskCacheAdapter)。 DiskLruCacheWrapper是一個(gè)使用 LRU 算法的固定大小的磁盤緩存。默認(rèn)磁盤大小為250MB,位置是在應(yīng)用的緩存文件夾 下中的一個(gè) 特定目錄 。
Google也曾提供了一個(gè)現(xiàn)成的工具類,DiskLruCache。郭霖大神這篇文章對(duì)這個(gè)DiskLruCache工具進(jìn)行了比較全面的分析,感興趣的朋友可以參考一下 Android DiskLruCache完全解析,硬盤緩存的最佳方案
默認(rèn)情況下我們進(jìn)行初始化glide時(shí)是磁盤內(nèi)部存儲(chǔ)new InternalCacheDiskCacheFactory(context),假如應(yīng)用程序展示的媒體內(nèi)容是公開(kāi)的(從無(wú)授權(quán)機(jī)制的網(wǎng)站上加載,或搜索引擎等),那么應(yīng)用可以將這個(gè)緩存位置改到外部存儲(chǔ):在自定義moudle的配置GlideBuilder.setDiskCache(new ExternalDiskCacheFactory(context));
無(wú)論使用內(nèi)部或外部磁盤緩存,應(yīng)用程序都可以改變磁盤緩存的大小和改變緩存文件夾在外存或內(nèi)存上的名字:
官網(wǎng)示例:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
int diskCacheSizeBytes = 1024 1024 100; 100 MB
builder.setDiskCache(
new InternalDiskCacheFactory(context, cacheFolderName, diskCacheSizeBytes));
}
}
上面我們提到過(guò)內(nèi)存緩存可以禁止,同樣磁盤緩存也可以。
GlideApp.with(fragment)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(view);
【磁盤緩存策略】
看到diskCacheStrategy()方法,我們就必須要提一下磁盤緩存策略:DiskCacheStrategy可被diskCacheStrategy方法應(yīng)用到每一個(gè)單獨(dú)的請(qǐng)求。 目前支持的策略如下

-
DiskCacheStrategy.ALL : 表示既緩存原始圖片,也緩存轉(zhuǎn)換過(guò)后的圖片。對(duì)于遠(yuǎn)程圖片,緩存DATA和RESOURCE。對(duì)于本地圖片,只緩存RESOURCE。
-
DiskCacheStrategy.AUTOMATIC :它會(huì)嘗試對(duì)本地和遠(yuǎn)程圖片使用最佳的策略。當(dāng)你加載遠(yuǎn)程數(shù)據(jù)(比如,從URL下載)時(shí),AUTOMATIC 策略僅會(huì)存儲(chǔ)未被你的加載過(guò)程修改過(guò)(比如,變換,裁剪–譯者注)的原始數(shù)據(jù)(DATA),因?yàn)橄螺d遠(yuǎn)程數(shù)據(jù)相比調(diào)整磁盤上已經(jīng)存在的數(shù)據(jù)要昂貴得多。對(duì)于本地?cái)?shù)據(jù),AUTOMATIC 策略則會(huì)僅存儲(chǔ)變換過(guò)的縮略圖(RESOURCE),因?yàn)榧词鼓阈枰俅紊闪硪粋€(gè)尺寸或類型的圖片,取回原始數(shù)據(jù)也很容易。
-
DiskCacheStrategy.DATA:表示只緩存未被處理的文件。我的理解就是我們獲得的stream。它是不會(huì)被展示出來(lái)的,需要經(jīng)過(guò)裝載decode,對(duì)圖片進(jìn)行壓縮和轉(zhuǎn)換,等等操作,得到最終的圖片才能被展示。
-
DiskCacheStrategy.NONE: 表示不緩存任何內(nèi)容。
-
DiskCacheStrategy.RESOURCE:表示只緩存轉(zhuǎn)換過(guò)后的圖片。(也就是經(jīng)過(guò)decode,轉(zhuǎn)化裁剪的圖片)
默認(rèn)的策略為DiskCacheStrategy.AUTOMATIC,改變策略也很簡(jiǎn)單, xxx.diskCacheStrategy(DiskCacheStrategy.ALL);
【磁盤緩存的讀取】
上面講過(guò)內(nèi)存緩存的讀取,那磁盤緩存是在哪里讀取的呢?和內(nèi)存緩存一樣,我們觸發(fā)圖片的加載是在Engine的load方法中,當(dāng)我們從內(nèi)存緩存以及當(dāng)前任務(wù)中都沒(méi)有找到資源時(shí),我們要開(kāi)啟線程去下載,engineJob.start(decodeJob);上一篇文章因?yàn)楹雎跃彺妫紤]加載,所以當(dāng)時(shí)忽略緩存操作,而這次我們帶著緩存一起看。
EngineJob類
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
DecodeJob類
/**
* Returns true if this job will attempt to decode a resource from the disk cache, and false if it
* will always decode from source.
*/
boolean willDecodeFromCache() {
Stage firstStage = getNextStage(Stage.INITIALIZE);
return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
-
willDecodeFromCache()方法通過(guò)調(diào)用getNextStage,傳入初始化標(biāo)識(shí)INITIALIZE,得到當(dāng)前階段標(biāo)識(shí),diskCacheStrategy.decodeCachedResource()返回一個(gè)boolean標(biāo)識(shí),如果我們指定磁盤緩存策略為DiskCacheStrategy.ALL或DiskCacheStrategy.RESOURCE或由DiskCacheStrategy.AUTOMATIC對(duì)遠(yuǎn)程圖片使用磁盤緩存時(shí),此時(shí)返回true,標(biāo)識(shí)。返回Stage.RESOURCE_CACHE。如果為false,遞歸調(diào)用,判斷是否為diskCacheStrategy.decodeCachedData(),也就是指定磁盤緩存策略為DiskCacheStrategy.ALL或DiskCacheStrategy.DATA或由DiskCacheStrategy.AUTOMATIC對(duì)本地圖片使用磁盤緩存時(shí),此時(shí)返回true。否則返回false,遞歸調(diào)用,判斷onlyRetrieveFromCache的boolean值,這個(gè)值是初始化DecodeJob中傳進(jìn)來(lái)的,它代表是否僅從緩存加載圖片,通過(guò)onlyRetrieveFromCache(true)制定,默認(rèn)為false,如果為true,它意味著要從內(nèi)存或磁盤讀取,如果內(nèi)存或磁盤不存在該資源,則加載直接失敗。一般情況下我們不會(huì)制定,為false,也就是會(huì)返回 Stage.SOURCE。代表不使用磁盤緩存,也就是之前文章分析的,直接從服務(wù)器下載。關(guān)于onlyRetrieveFromCache,再多說(shuō)兩句:
某些情形下,你可能希望只要圖片不在緩存中則加載直接失?。ū热缡×髁磕J??–譯者注)。如果要完成這個(gè)目標(biāo),你可以在單個(gè)請(qǐng)求的基礎(chǔ)上使用
GlideApp.with(fragment)
.load(url)
.onlyRetrieveFromCache(true)
.into(imageView);
-
所以,如果getNextStage方法返回的標(biāo)識(shí)為Stage.RESOURCE_CACHE或Stage.DATA_CACHE就代表我們沒(méi)有禁止磁盤緩存,那么willDecodeFromCache()將返回true。此時(shí)executor=diskCacheExecutor,返回false,executor=getActiveSourceExecutor();而這些executor在glide初始化的GlideBuilder.build方法里已經(jīng)被實(shí)例了。
-
然后執(zhí)行executor.execute(decodeJob);接著會(huì)command.run();開(kāi)啟線程任務(wù),也就是執(zhí)行DecodeJob的run方法,run方法里調(diào)用runWrapped方法,runReason默認(rèn)為INITIALIZE
DecodeJob類
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
// 初始化 獲取下一個(gè)階段狀態(tài)
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
// 運(yùn)行
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
// 根據(jù)定義的緩存策略來(lái)回去下一個(gè)狀態(tài)
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
// 根據(jù)Stage找到數(shù)據(jù)抓取生成器。
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
// 產(chǎn)生含有降低采樣/轉(zhuǎn)換資源數(shù)據(jù)緩存文件的DataFetcher。
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
// 產(chǎn)生包含原始未修改的源數(shù)據(jù)緩存文件的DataFetcher。
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
// 生成使用注冊(cè)的ModelLoader和加載時(shí)提供的Model獲取源數(shù)據(jù)規(guī)定的DataFetcher。
// 根據(jù)不同的磁盤緩存策略,源數(shù)據(jù)可首先被寫入到磁盤,然后從緩存文件中加載,而不是直接返回。
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}
這里我們選擇ResourceCacheGenerator或DataCacheGenerator都好,我們就以ResourceGenerator為示例,在runGenerators()方法里,還是看currentGenerator.startNext()。
ResourceCacheGenerator類
@Override
public boolean startNext() {
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}
Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
currentKey = new ResourceCacheKey(sourceId, helper.getSignature(), helper.getWidth(),
helper.getHeight(), transformation, resourceClass, helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
// 查找ModelLoader
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
// 通過(guò)FileLoader繼續(xù)加載數(shù)據(jù)
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
FileLoad類
public void loadData(Priority priority, DataCallback<? super Data> callback) {
// 讀取文件數(shù)據(jù)
try {
data = opener.open(file);
} catch (FileNotFoundException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to open file", e);
}
//失敗
callback.onLoadFailed(e);
return;
}
// 成功
callback.onDataReady(data);
}
在這里我們就可以看到根據(jù)key讀取緩存文件cacheFile,傳入File,得到對(duì)應(yīng)的modelloader.fetcher去獲取數(shù)據(jù),加載完畢后通過(guò),callback.onDataReady(result);把數(shù)據(jù)回調(diào)返回 此callback就是對(duì)應(yīng)的Generator,我們這里是指ResourceCacheGenerator
ResourceCacheGenerator類
@Override
public void onDataReady(Object data) {
cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,
currentKey);
}
繼續(xù)回調(diào),cb為SourceGenerator
SourceGenerator類
// Called from source cache generator.
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
// This data fetcher will be loading from a File and provide the wrong data source, so override
// with the data source of the original fetcher
cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
}
繼續(xù)回調(diào)cb為DecodeJob
DecodeJob類
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;
this.currentData = data;
this.currentFetcher = fetcher;
this.currentDataSource = dataSource;
this.currentAttemptingKey = attemptedKey;
if (Thread.currentThread() != currentThread) {
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
TraceCompat.beginSection("DecodeJob.decodeFromRetrievedData");
try {
decodeFromRetrievedData();
} finally {
TraceCompat.endSection();
}
}
}
//然后判斷線程,這里原因上篇文章具體講解過(guò),最后還是執(zhí)行decodeFromRetrievedData();
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Retrieved data", startFetchTime,
"data: " + currentData
+ ", cache key: " + currentSourceKey
+ ", fetcher: " + currentFetcher);
}
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
在onDataReady方法回調(diào)給decodeJob的DataSource是DataSource.RESOURCE_DISK_CACHE
通過(guò)decodeFromData方法將數(shù)據(jù)解碼成Resource對(duì)象后返回即可。然后通過(guò)notifyEncodeAndRelease回調(diào)UI線程顯示出來(lái)。
至此,磁盤緩存的讀取邏輯完畢
【磁盤緩存的寫入】
我們?cè)赟ourceGenerator這個(gè)類的startNext方法觸發(fā)數(shù)據(jù)的加載時(shí), loadData.fetcher.loadData(helper.getPriority(), this);加載完畢會(huì)返調(diào)用SourceGenerator.onDataReady(result);將結(jié)果返回
SourceGenerator類
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
此時(shí)如果我們的磁盤緩存策略沒(méi)有禁止,那么 dataToCache = data;同時(shí)執(zhí)行 cb.reschedule();也就是DecodeJob.reschedule():
DecodeJob
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
callback.reschedule(this);
}
callback.reschedule(this);也就是Engine.reschedule();再說(shuō)一遍原因是我們數(shù)據(jù)加載完被回調(diào)至此,我們可能在其他線程里,但是我們需要切換到Glide自定義的線程。
@Override
public void reschedule(DecodeJob<?> job) {
// Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
// up.
getActiveSourceExecutor().execute(job);
}
也就是GlideEexcutor的execute,在這里調(diào)用DecodeJob的run方法,--runWrapped,因?yàn)?runReason = RunReason.SWITCH_TO_SOURCE_SERVICE,所以直接調(diào)用 runGenerators();方法。--> 繼續(xù),currentGenerator.startNext()這里的代碼已經(jīng)重復(fù)很多很多次了,就不過(guò)多贅述了。
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
此時(shí)調(diào)用currentGenerator.startNext()方法dataToCache已經(jīng)不為null了。也就是cacheData(data);就是這里了,我們?cè)谶@里寫入數(shù)據(jù)。
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
// 根據(jù)不同的數(shù)據(jù)獲取注冊(cè)的不同Encoder
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
//得到DiskCache得實(shí)現(xiàn),并存入磁盤。
helper.getDiskCache().put(originalKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished encoding source to cache"
+ ", key: " + originalKey
+ ", data: " + dataToCache
+ ", encoder: " + encoder
+ ", duration: " + LogTime.getElapsedMillis(startTime));
}
} finally {
loadData.fetcher.cleanup();
}
//
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
至此,磁盤緩存的寫入也講解完畢。
寫源碼分析真的十分頭疼 ? ? 給個(gè)小心心鼓勵(lì)一下吧~