
?
前言
Android中加載圖片的形式有很多種,網(wǎng)上也有很多的知名圖片加載庫,例如Glide、Picasso、Fresco等等,它們?yōu)槲覀儙淼姆奖憔筒恍柙俣嘌粤耍瑹o論是從加載到緩存還是占位圖等等都提供了簡易的Api,且實現(xiàn)強大的功能。本系列只針對Glide4.0版本源碼進行分析,提高自身閱讀源碼的能力,同時也是為了了解其中加載的流程以及緩存的原理,本文盡可能地截圖說明結(jié)合源碼解析,如有疏忽之處,還請指教。
關(guān)于作者
一個在奮斗路上的Android小生,歡迎關(guān)注,互相交流
GitHub:GitHub-ZJYWidget
CSDN博客:IT_ZJYANG
簡 書:Android小Y
?
前情回顧
前幾集已經(jīng)從Glide的最基本用法入手分析了Glide的請求、解析、加載圖片的流程。深刻體會到Glide源碼結(jié)構(gòu)的復(fù)雜,但Glide作為一個優(yōu)秀的圖片加載框架,必然要在緩存上下點功夫,本文主要分析Glide的緩存機制(用過的都能體會到它的緩存給我們帶來的絲滑體驗,特別是在請求網(wǎng)絡(luò)資源的場景,緩存機制尤為重要)
?
劇情(Glide 緩存 有備無患)
平時使用Glide做緩存相關(guān)的操作,主要有兩個api,一個是是設(shè)置skipMemoryCache(boolean),表示是否開啟內(nèi)存緩存,另外一個是diskCacheStrategy(DiskCacheStrategy)表示是否開啟硬盤緩存,如下:
Glide3.0以前的用法是:
Glide.with(this).load("http://xxx.xxx.png").skipMemoryCache(false).diskCacheStrategy(DiskCacheStrategy.NONE).into(imageView);
Glide4.0的用法是:
RequestOptions requestOptions = new RequestOptions();
requestOptions.skipMemoryCache(false);
requestOptions.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this).load("http://xxx.xxx.png").apply(requestOptions ).into(imageView);
調(diào)用方式有些差別,但核心緩存機制差不多,接下來的分析均以我們以4.0+為準,可以看到簡單的一個設(shè)置,即可決定是否要開啟緩存功能,那么Glide內(nèi)部究竟是如何對圖片做出"備份"的呢?
內(nèi)存緩存
上一集 Android Glide4.0 源碼遨游記(第四集)在講Glide的into方法的時候,有提到一個關(guān)鍵的核心類:Engine,圖片真正請求的地方正是從它的load方法開始的,而Glide也正是在這里做了核心的緩存操作,我們回頭看看那個load方法的源碼:
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();
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
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;
}
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;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
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);
}
可以看到,在創(chuàng)建engineJob和decodeJob之前,Glide還做了一些手腳,先是通過
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
創(chuàng)建了一個EngineKey對象,我們點進去buildKey看看:
class EngineKeyFactory {
@SuppressWarnings("rawtypes")
EngineKey buildKey(Object model, Key signature, int width, int height,
Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
Class<?> transcodeClass, Options options) {
return new EngineKey(model, signature, width, height, transformations, resourceClass,
transcodeClass, options);
}
}
其實就是new了一個EngineKey對象,并把關(guān)于圖片的很多加載信息和類型都傳了進去,那這個Glide創(chuàng)建這個EngineKey意圖何在?不急,我們暫且記住有這么個對象,繼續(xù)往下看到這么一段:
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;
}
上文我們講到,onResourceReady就是將資源回調(diào)給ImageView去加載,那么這里先是判斷active不為空,然后就調(diào)用了onResourceReady并且return結(jié)束當前方法,那么這個active就很有可能是Glide對圖片的一種緩存(暫且這樣直觀理解),可以看到剛才生成的key值也傳了進去,所以我們看下loadFromActiveResources這個方法里做了什么:
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
可以看到,先是判斷isMemoryCacheable,這個就是我們skipMemoryCache傳進來的值的相反值,即:
skipMemoryCache傳true,isMemoryCacheable為false
skipMemoryCache傳false,isMemoryCacheable為true
所以如果我們設(shè)置為不緩存,那么這個條件就會通過,也就是直接執(zhí)行return null,那么剛才上一步的active對象也就相應(yīng)地被賦為null,就會繼續(xù)往下走。
如果設(shè)置了啟用緩存,那么這個條件就不滿足,繼續(xù)往下,可以看到從activeResources中通過key去拿了個EngineResource,那么activeResources里面存的是什么數(shù)據(jù)呢?,我們看下ActiveResources類:
public class ActiveResources {
//忽略部分代碼
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
@Nullable
EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
}
可以看到實際上ActiveResrouces里是存了很多組key-resource的Map集合,并且resource是以弱引用的形式保存,然后get方法是根據(jù)一個key去map里面獲取對應(yīng)的資源對象,如果拿不到(即可能已經(jīng)被系統(tǒng)GC回收),那就clear,返回獲取到的EngineResource對象。
回到剛才Engine的load方法中,可以看到在調(diào)用loadFromActiveResources獲取不到的情況下,會調(diào)用loadFromCache來獲取,那么這個loadFromCache又是從什么地方獲取數(shù)據(jù)呢?loadFromCahce源碼如下:
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
同樣是判斷isMemoryCacheable,道理同上,就不再復(fù)述了,接著它調(diào)用了getEngineResourceFromCache,跟進去看看:
private EngineResource<?> getEngineResourceFromCache(Key key) {
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*/, true /*isRecyclable*/);
}
return result;
}
可以看到,這里先是調(diào)用了一個cache對象的remove,cache是一個MemoryCache接口對象,看下Glide對MemoryCache的定義:

可以看到是一個從內(nèi)存中添加和移除資源的操作接口,看下它的實現(xiàn)類
LruResourceCache:

看到繼承了LruCache,也就是說,cache實際上是一個根據(jù)LruCache算法緩存圖片資源的類,剛才把EngineResource從其中remove掉,并且返回了這個被remove掉的EngineResource對象,如果為null,說明Lru緩存中已經(jīng)沒有該對象,如果不為null,則將其返回。
所以
getEngineResourceFromCache其實就是根據(jù)Lru算法從內(nèi)存中獲取圖片資源,并且獲取到的話就順便從LruCache中移除掉,接著看回剛才的loadFromCache方法:

可以看到,假如剛才從LruResourceCache中拿到了緩存,那么就會調(diào)用EngineResource的
acquire()方法:
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;
}
這里只是將acquired+1,那么acquired變量用來干嘛的呢?我們跟蹤它被引用的地方,可以看到EngineResource的release():
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);
}
}
可以看到,每次調(diào)用release的時候(比如暫停請求或者加載完畢時,這里就不展開講了,跟蹤一下即可),會將acquired-1,并且當acquired為0的時候,會調(diào)用listener的onResourceReleased方法,而這個listener正是Engine,我們回到Engine中看它的實現(xiàn):
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
可以看到調(diào)用了activeResources的deactivate方法(這個方法作用就是將該資源對象從弱引用集合activeResources中移除),接著,可以看到再將其put進cache,cache我們剛才提到了,是一個基于Lru算法的緩存管理類,所以這里就是將其加進了LruCache緩存中。
也就是說,每次觸發(fā)
release就會將acquired變量-1,一旦它為0時,就會觸發(fā)Engine將該緩存從弱引用中移除,并且加進了LruCache緩存中。換句話理解就是,這個資源暫時不用到,Glide把它從弱引用轉(zhuǎn)移到了LruCache中。
而剛才的
loadFromCache方法里,調(diào)用了acquire()使得 acquired+1,也就是此刻我正要使用這個緩存,做個標記,這樣就不會被轉(zhuǎn)移到LruCache中。
聯(lián)合起來的邏輯就是:先從LruCache中拿緩存,如果拿到了,就從LruCache緩存轉(zhuǎn)移到弱應(yīng)用緩存池中,并標記一下此刻正在引用。反過來,一旦沒有地方正在使用這個資源,就會將其從弱引用中轉(zhuǎn)移到LruCache緩存池中
?
硬盤緩存
分析完了內(nèi)存緩存,我們再來看看Glide的硬盤緩存。依然是接著剛才最開始的Engine的load方法,在判斷了兩級內(nèi)存緩存之后,如果拿不到緩存,就會接著創(chuàng)建EngineJob和DecodeJob(這兩個的作用見我另外一篇文章Android Glide4.0 源碼遨游記(第四集) ),然后接著就會調(diào)用進DecodeJob線程的run方法:
@Override
public void run() {
TraceCompat.beginSection("DecodeJob#run");
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (Throwable t) {
if (stage != Stage.ENCODE) {
throwables.add(t);
notifyFailed();
}
if (!isCancelled) {
throw t;
}
} finally {
if (localFetcher != null) {
localFetcher.cleanup();
}
TraceCompat.endSection();
}
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
run中主要還是調(diào)用的runWrapper方法,繼而調(diào)用runGenerator:
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.
}
這里調(diào)用了一個循環(huán)獲取解析生成器Generator的方法,而解析生成器有多個實現(xiàn)類:ResourcesCacheGenerator、SourceGenerator、DataCacheGenerator,它們負責(zé)各種硬盤緩存策略下的緩存管理,所以這里關(guān)鍵的條件在于currentGenerator.startNext()循環(huán)獲取每個Generator能否獲取到緩存,獲取不到就通過getNextGenerator進行下一種:
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
所以我們看看ResourceCacheGenerator的startNext,看下它是用什么來緩存的,其中部分代碼如下:
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
這里通過一個資源的關(guān)鍵信息生成key,然后調(diào)用helper.getDiskCache().get(),我們跟進去DiskCache看看:
DiskCache getDiskCache() {
return diskCacheProvider.getDiskCache();
}
interface DiskCacheProvider {
DiskCache getDiskCache();
}
可以看到最終是調(diào)用了DiskCacheProvider接口的getDiskCache方法獲取一個DiskCache對象,那么這個D對象又是什么來頭呢?

可以看到這是一個用來緩存硬盤數(shù)據(jù)的接口,那么它的實現(xiàn)就是我們要找的最終目標:

里面的就不詳細分析下去了,這里主要維護了一個DiskLruCache,Glide就是通過這個來實現(xiàn)硬盤緩存的。
可以看到Glide的硬盤緩存是依靠DiskLruCache來進行緩存的,同樣也是Lru算法。
總結(jié)
Glide4.0的緩存機制概況來說就是,先從弱引用緩存中獲取數(shù)據(jù),假如獲取不到,就再嘗試從LruCache中獲取數(shù)據(jù),假如從LruCache中獲取到數(shù)據(jù)的話,就會將其從LruCache中轉(zhuǎn)移到弱引用緩存中,這么做的優(yōu)點是,下次再來拿數(shù)據(jù)的時候,就可以直接從弱引用中獲取。
資源對象用一個acquired變量用來記錄圖片被引用的次數(shù),調(diào)用acquire()方法會讓變量加1,調(diào)用release()方法會讓變量減1,然后一旦acquired減為0(沒有地方引用該資源),就會將其從弱引用中移除,添加到LruCache中。
使用中的資源會用弱引用來緩存,不在使用的資源會添加到LruCache中來緩存。
在二者都獲取不到的情況下會根據(jù)硬盤緩存策略通過DiskLruCache去硬盤中獲取數(shù)據(jù),正是這樣優(yōu)秀的緩存機制,讓我們在沒有網(wǎng)絡(luò)的情況下也能有很好的體驗。