Android緩存機(jī)制:如果沒有緩存社露,在大量的網(wǎng)絡(luò)請求從遠(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圖片加載庫從v3遷移到v4的改變和使用
【Glide的緩存】
在緩存這一功能上乎完,Glide將它分成了兩個(gè)模塊熏兄,一個(gè)是內(nèi)存緩存,一個(gè)是硬盤緩存囱怕。同時(shí)內(nèi)存緩存又分為兩級霍弹,一級是LruCache緩存,一級是弱引用緩存娃弓。
- 內(nèi)存緩存的作用:防止應(yīng)用重復(fù)將圖片數(shù)據(jù)讀取到內(nèi)存當(dāng)中。
- LruCache緩存:不在使用中的圖片使用LruCache來進(jìn)行緩存岛宦。
- 弱引用緩存:把正在使用中的圖片使用弱引用來進(jìn)行緩存台丛。
【這樣的目的保護(hù)正在使用的資源不會(huì)被LruCache算法回收±危】
- 硬盤緩存的作用:防止應(yīng)用重復(fù)從網(wǎng)絡(luò)或其他地方重復(fù)下載和讀取數(shù)據(jù)挽霉。
默認(rèn)情況下,Glide 會(huì)在開始一個(gè)新的圖片請求之前檢查以下多級的緩存:
- 內(nèi)存緩存 (Memory cache) - 該圖片是否最近被加載過并仍存在于內(nèi)存中变汪?即LruCache緩存侠坎。
- 活動(dòng)資源 (Active Resources) - 現(xiàn)在是否有另一個(gè) View 正在展示這張圖片?也就是弱引用緩存裙盾。
- 資源類型(Resource) - 該圖片是否之前曾被解碼实胸、轉(zhuǎn)換并寫入過磁盤緩存?
- 數(shù)據(jù)來源 (Data) - 構(gòu)建這個(gè)圖片的資源是否之前曾被寫入過文件緩存番官?
前兩步檢查圖片是否在內(nèi)存中庐完,如果是則直接返回圖片。后兩步則檢查圖片是否在磁盤上徘熔,以便快速但異步地返回圖片门躯。
如果四個(gè)步驟都未能找到圖片,則Glide會(huì)返回到原始資源以取回?cái)?shù)據(jù)(原始文件酷师,Uri, Url等)
結(jié)合源碼去分析Glide緩存
首先在[圖片加載源碼分析一]的文章中讶凉,我們在通過單例獲取Glide的實(shí)例時(shí)染乌,調(diào)用過checkAndInitializeGlide(context)這個(gè)方法,在具體的方法里有一段代碼是通過GlideBuilder.build初始化一些對象懂讯,如下
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è)對象上面域醇。你沒有猜錯(cuò)台谊,這個(gè)就是Glide實(shí)現(xiàn)內(nèi)存緩存所使用的LruCache對象了。2處InternalCacheDiskCacheFactory是磁盤緩存(內(nèi)部存儲)所使用的工廠對象譬挚。同時(shí)在其中初始化了磁盤緩存的大小和文件的路徑锅铅。
創(chuàng)建好了這些對象說明我們已經(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類來決定贼邓,這個(gè)類主要關(guān)注設(shè)備的內(nèi)存類型,設(shè)備 RAM 大小闷尿,以及屏幕分辨率塑径。
【內(nèi)存緩存的讀取】
我們先分析Glide從哪里讀取內(nèi)存緩存,以及內(nèi)存緩存的原理填具。
1. Lurcache算法
對于大多內(nèi)存緩存的實(shí)現(xiàn)统舀,我們通常會(huì)知道這樣一個(gè)算法,LruCache算法(Least Recently Used)劳景,也叫近期最少使用算法誉简。它的主要算法原理就是把最近使用的對象用強(qiáng)引用存儲在LinkedHashMap(雙向循環(huán)列表)中,并且把最近最少使用的對象在緩存值達(dá)到預(yù)設(shè)定值之前從內(nèi)存中移除盟广。淘汰最長時(shí)間未使用的對象
上面我們提到的LruResourceCache就是Glide實(shí)現(xiàn)內(nèi)存緩存所使用的LruCache對象了闷串,而這個(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算法烹吵。不過除了LruCache算法之外,Glide還結(jié)合了一種弱引用的機(jī)制毛甲,共同完成了內(nèi)存緩存功能年叮。這樣做的目的是把正在使用中的圖片使用弱引用來進(jìn)行緩存,不在使用中的圖片使用LruCache來進(jìn)行緩存的功能玻募。分工合作只损,保護(hù)正在使用的資源不會(huì)被LruCache算法回收掉。(劃重點(diǎn))
3. Glide默認(rèn)情況下,Glide自動(dòng)就是開啟內(nèi)存緩存的跃惫。也就是說叮叹,當(dāng)我們使用Glide加載了一張圖片之后,這張圖片就會(huì)被緩存到內(nèi)存當(dāng)中爆存,只要在它還沒從內(nèi)存中被清除之前蛉顽,下次使用Glide再加載這張圖片都會(huì)直接從內(nèi)存當(dāng)中讀取,而不用重新從網(wǎng)絡(luò)或硬盤上讀取了先较,這樣無疑就可以大幅度提升圖片的加載效率携冤。比方我們在使用RecyclerView、listview闲勺、viewpager這種控件時(shí)曾棕,反復(fù)上下滑動(dòng),當(dāng)移出屏幕的項(xiàng)被回收再次移入屏幕展示時(shí)菜循,那么只要是Glide加載過的圖片都可以直接從內(nèi)存當(dāng)中迅速讀取并展示出來翘地。
4. 看過了之前圖片加載的兩篇文章,我們在第三步into時(shí)癌幕,onSizeReady準(zhǔn)備圖片加載時(shí)衙耕,會(huì)調(diào)用Engine.load這個(gè)比較重要的方法,在上一篇文章分析時(shí)勺远,我們忽略了對緩存的處理橙喘,而是直接分析沒有緩存的加載過程。而這篇文章我們返回看緩存處理谚中。
重新放出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處:首先通過KeyFactory的buildKey方法創(chuàng)建了一個(gè)EngineKey對象(緩存鍵),這個(gè)對象就是我們說的緩存key宪塔,加載資源的唯一標(biāo)識∧野荩可以看到?jīng)Q定緩存Key的條件非常多某筐,即使你用override()方法改變了一下圖片的width或者h(yuǎn)eight,也會(huì)生成一個(gè)完全不同的緩存Key冠跷。
-
//b處:通過loadFromCache方法南誊,通過key查找緩存資源,此時(shí)的緩存為內(nèi)存緩存蜜托,如果獲取的到就直接調(diào)用cb.onResourceReady()方法進(jìn)行回調(diào)抄囚。
-
//c處:如果內(nèi)存緩存沒有找到對應(yīng)key的資源,則調(diào)用loadFromActiveResources方法橄务,還是通過key獲取緩存資源幔托,而此時(shí)的緩存也是內(nèi)存緩存。獲取到的話也直接進(jìn)行回調(diào)。
也就是說重挑,Glide的圖片加載過程中會(huì)調(diào)用兩個(gè)方法來獲取內(nèi)存緩存嗓化,loadFromCache()和loadFromActiveResources()。這兩個(gè)方法中前者使用的就是LruCache算法谬哀,后者使用的就是弱引用刺覆。
-
//d處:如果以上都沒有找到,那是否可能該緩存任務(wù)正在處理史煎,還沒有完成緩存谦屑,所以根據(jù)key判斷緩存的job中是否有current,如果有篇梭,就不用新創(chuàng)建任務(wù)了对室,而是給其添加回調(diào),等待完成后獲取如孝。
-
//e處:如果以上條件都不滿足岁诉,我們就需要?jiǎng)?chuàng)建新的加載任務(wù)。并把當(dāng)前任務(wù)存放在jobs這個(gè)map中喉磁。同時(shí)要開啟線程來加載新的圖片了谓苟。
5.看一下loadFromCache()和loadFromActiveResources()這兩個(gè)方法的源碼:
//Engine類
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...省略
//內(nèi)存緩存 (Memory cache) - 該圖片是否最近被加載過并仍存在于內(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)存緩存是否被開啟孕暇,Glide默認(rèn)為開啟仑撞,true。但如果想要禁用的話呢妖滔?通過向skipMemoryCache()傳入true隧哮,此時(shí)isMemoryCacheable將為false,返回值也為null座舍。Glide-v3到v4寫法的變化
GlideApp.with(fragment)
.load(url)
.skipMemoryCache(true)
.into(view);
-
//b處:接著調(diào)用了getEngineResourceFromCache(key)方法來獲取緩存沮翔。在這個(gè)方法中,會(huì)使用緩存Key來從cache當(dāng)中取值曲秉,而這里的cache對象就是在構(gòu)建Glide對象時(shí)創(chuàng)建的LruResourceCache采蚀,那么說明這里其實(shí)使用的就是LruCache算法了。當(dāng)我們從LruResourceCache中獲取到緩存圖片之后會(huì)將它從緩存中移除承二。cache.remove(key)榆鼠。這個(gè)語句既返回了對應(yīng)key的value值,也將對應(yīng)的key從cache中移除亥鸠。
-
//c處:如果cached不為null妆够,首先調(diào)用cached.acquire();EngineResource用一個(gè)acquired變量來記錄圖片被引用的次數(shù),調(diào)用acquire()方法會(huì)讓變量acquire加1,調(diào)用release()方法會(huì)讓變量減1责静。(當(dāng)acquired變量大于0的時(shí)候袁滥,說明圖片正在使用中,也就應(yīng)該放到activeResources弱引用緩存當(dāng)中,如果acquired變量等于0了灾螃,說明圖片已經(jīng)不在使用中了题翻。釋放資源,從activeResources弱引用緩存中移除腰鬼,并put到LruResourceCache當(dāng)中)
-
//d處:然后將這個(gè)緩存圖片存儲到activeResources當(dāng)中嵌赠。activeResources就是一個(gè)弱引用的HashMap,用來緩存正在使用中的圖片熄赡,我們可以看到姜挺,loadFromActiveResources()方法就是從activeResources這個(gè)HashMap當(dāng)中取值的。使用activeResources來緩存正在使用中的圖片彼硫,可以保護(hù)這些圖片不會(huì)被LruCache算法回收掉炊豪。
-
//e處:如果從內(nèi)存中沒有找到資源,那有一種可能為該資源已被LruCache算法移除拧篮,但是它正在被另一個(gè)view展示词渤,所以此時(shí)還是有此資源的緩存。所以查找串绩,存在缺虐,引用值加1,不存在礁凡,則把key從activeResources弱引用緩存中移除高氮。
【內(nèi)存緩存的寫入】
1.當(dāng)沒有讀取到緩存時(shí),我們肯定要正常開啟線程去下載資源了顷牌,具體流程可以看之前的文章剪芍,那么從服務(wù)端得到資源后是何時(shí)以及如何寫入到緩存中呢?下面來具體分析:
上一篇文章講解過窟蓝,當(dāng)從服務(wù)端得到stream然后做處理得到的最終圖片資源通過層層回調(diào)返回等最終交給EngineJob的onResourceReady的方法處理紊浩。而在這個(gè)方法中通過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è)方法里,通過EngineResourceFactory構(gòu)建出了一個(gè)包含圖片資源的EngineResource對象费彼,然后將這個(gè)對象回調(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)聽滑臊,然后回調(diào)過來的EngineResource被put到了activeResources當(dāng)中,在這里寫入到了內(nèi)存緩存的弱引用緩存箍铲。寫入到弱引用緩存的原因是這個(gè)資源是屬于正在被加載展示的資源雇卷,也就是正在被使用的資源。
那什么時(shí)候要寫入到內(nèi)存緩存的LruCache中呢?我們說過要將不在使用中的圖片使用LruCache來進(jìn)行緩存关划,那怎么判斷是否在使用中?那就是前面講到的EngineResource中的一個(gè)引用機(jī)制小染,通過acquired的值來判斷。(當(dāng)acquired變量大于0的時(shí)候贮折,說明圖片正在使用中裤翩,也就應(yīng)該放到activeResources弱引用緩存當(dāng)中,如果acquired變量等于0了,說明圖片已經(jīng)不在使用中了调榄,此時(shí)放到LruCache來進(jìn)行緩存踊赠。)acquired的增加和減少通過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。郭霖大神這篇文章對這個(gè)DiskLruCache工具進(jìn)行了比較全面的分析侣滩,感興趣的朋友可以參考一下 Android DiskLruCache完全解析口注,硬盤緩存的最佳方案
默認(rèn)情況下我們進(jìn)行初始化glide時(shí)是磁盤內(nèi)部存儲new InternalCacheDiskCacheFactory(context),假如應(yīng)用程序展示的媒體內(nèi)容是公開的(從無授權(quán)機(jī)制的網(wǎng)站上加載君珠,或搜索引擎等)寝志,那么應(yīng)用可以將這個(gè)緩存位置改到外部存儲:在自定義moudle的配置GlideBuilder.setDiskCache(new ExternalDiskCacheFactory(context));
無論使用內(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));
}
}
上面我們提到過內(nèi)存緩存可以禁止策添,同樣磁盤緩存也可以材部。
GlideApp.with(fragment)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(view);
【磁盤緩存策略】
看到diskCacheStrategy()方法,我們就必須要提一下磁盤緩存策略:DiskCacheStrategy可被diskCacheStrategy方法應(yīng)用到每一個(gè)單獨(dú)的請求唯竹。 目前支持的策略如下
-
DiskCacheStrategy.ALL : 表示既緩存原始圖片乐导,也緩存轉(zhuǎn)換過后的圖片。對于遠(yuǎn)程圖片浸颓,緩存DATA和RESOURCE物臂。對于本地圖片旺拉,只緩存RESOURCE。
-
DiskCacheStrategy.AUTOMATIC :它會(huì)嘗試對本地和遠(yuǎn)程圖片使用最佳的策略棵磷。當(dāng)你加載遠(yuǎn)程數(shù)據(jù)(比如蛾狗,從URL下載)時(shí),AUTOMATIC 策略僅會(huì)存儲未被你的加載過程修改過(比如仪媒,變換沉桌,裁剪–譯者注)的原始數(shù)據(jù)(DATA),因?yàn)橄螺d遠(yuǎn)程數(shù)據(jù)相比調(diào)整磁盤上已經(jīng)存在的數(shù)據(jù)要昂貴得多规丽。對于本地?cái)?shù)據(jù)蒲牧,AUTOMATIC 策略則會(huì)僅存儲變換過的縮略圖(RESOURCE),因?yàn)榧词鼓阈枰俅紊闪硪粋€(gè)尺寸或類型的圖片赌莺,取回原始數(shù)據(jù)也很容易冰抢。
-
DiskCacheStrategy.DATA:表示只緩存未被處理的文件。我的理解就是我們獲得的stream艘狭。它是不會(huì)被展示出來的挎扰,需要經(jīng)過裝載decode,對圖片進(jìn)行壓縮和轉(zhuǎn)換巢音,等等操作遵倦,得到最終的圖片才能被展示。
-
DiskCacheStrategy.NONE: 表示不緩存任何內(nèi)容官撼。
-
DiskCacheStrategy.RESOURCE:表示只緩存轉(zhuǎn)換過后的圖片梧躺。(也就是經(jīng)過decode,轉(zhuǎn)化裁剪的圖片)
默認(rèn)的策略為DiskCacheStrategy.AUTOMATIC傲绣,改變策略也很簡單掠哥, xxx.diskCacheStrategy(DiskCacheStrategy.ALL);
【磁盤緩存的讀取】
上面講過內(nèi)存緩存的讀取秃诵,那磁盤緩存是在哪里讀取的呢续搀?和內(nèi)存緩存一樣,我們觸發(fā)圖片的加載是在Engine的load方法中菠净,當(dāng)我們從內(nèi)存緩存以及當(dāng)前任務(wù)中都沒有找到資源時(shí)禁舷,我們要開啟線程去下載,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()方法通過調(diào)用getNextStage霜大,傳入初始化標(biāo)識INITIALIZE,得到當(dāng)前階段標(biāo)識革答,diskCacheStrategy.decodeCachedResource()返回一個(gè)boolean標(biāo)識战坤,如果我們指定磁盤緩存策略為DiskCacheStrategy.ALL或DiskCacheStrategy.RESOURCE或由DiskCacheStrategy.AUTOMATIC對遠(yuǎn)程圖片使用磁盤緩存時(shí),此時(shí)返回true残拐,標(biāo)識途茫。返回Stage.RESOURCE_CACHE。如果為false溪食,遞歸調(diào)用囊卜,判斷是否為diskCacheStrategy.decodeCachedData(),也就是指定磁盤緩存策略為DiskCacheStrategy.ALL或DiskCacheStrategy.DATA或由DiskCacheStrategy.AUTOMATIC對本地圖片使用磁盤緩存時(shí)错沃,此時(shí)返回true栅组。否則返回false,遞歸調(diào)用枢析,判斷onlyRetrieveFromCache的boolean值玉掸,這個(gè)值是初始化DecodeJob中傳進(jìn)來的,它代表是否僅從緩存加載圖片醒叁,通過onlyRetrieveFromCache(true)制定司浪,默認(rèn)為false,如果為true把沼,它意味著要從內(nèi)存或磁盤讀取啊易,如果內(nèi)存或磁盤不存在該資源,則加載直接失敗饮睬。一般情況下我們不會(huì)制定租谈,為false,也就是會(huì)返回 Stage.SOURCE捆愁。代表不使用磁盤緩存割去,也就是之前文章分析的,直接從服務(wù)器下載牙瓢。關(guān)于onlyRetrieveFromCache劫拗,再多說兩句:
某些情形下,你可能希望只要圖片不在緩存中則加載直接失敺恕(比如省流量模式页慷?–譯者注)。如果要完成這個(gè)目標(biāo)胁附,你可以在單個(gè)請求的基礎(chǔ)上使用
GlideApp.with(fragment)
.load(url)
.onlyRetrieveFromCache(true)
.into(imageView);
-
所以酒繁,如果getNextStage方法返回的標(biāo)識為Stage.RESOURCE_CACHE或Stage.DATA_CACHE就代表我們沒有禁止磁盤緩存,那么willDecodeFromCache()將返回true控妻。此時(shí)executor=diskCacheExecutor州袒,返回false,executor=getActiveSourceExecutor();而這些executor在glide初始化的GlideBuilder.build方法里已經(jīng)被實(shí)例了弓候。
-
然后執(zhí)行executor.execute(decodeJob);接著會(huì)command.run();開啟線程任務(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ù)定義的緩存策略來回去下一個(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:
// 生成使用注冊的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());
// 通過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,得到對應(yīng)的modelloader.fetcher去獲取數(shù)據(jù)睡榆,加載完畢后通過萍肆,callback.onDataReady(result);把數(shù)據(jù)回調(diào)返回 此callback就是對應(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();
}
}
}
//然后判斷線程塘揣,這里原因上篇文章具體講解過,最后還是執(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
通過decodeFromData方法將數(shù)據(jù)解碼成Resource對象后返回即可宿崭。然后通過notifyEncodeAndRelease回調(diào)UI線程顯示出來亲铡。
至此,磁盤緩存的讀取邏輯完畢
【磁盤緩存的寫入】
我們在SourceGenerator這個(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í)如果我們的磁盤緩存策略沒有禁止奖蔓,那么 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();再說一遍原因是我們數(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ù)很多很多次了,就不過多贅述了叙谨。
@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);就是這里了手负,我們在這里寫入數(shù)據(jù)涤垫。
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
// 根據(jù)不同的數(shù)據(jù)獲取注冊的不同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ì)一下吧~