本文源碼解析基于Glide
4.6.1
不知道大家最開始使用Glide的原因是什么霎冯?我的原因很簡單就是沖著那句Glide.with(this).load(url).into(imageview)
去的,再加上Google的推薦啃炸,就一直沿用至今畦娄。以前也不太了解它,就知道它使用簡潔而且很火熟尉,不過最近看了一些它的源碼設計归露,算是找到了使用Glide理由。我目前的緣由如下:
1斤儿、Glide通過高度封裝之后剧包,通過外觀模式對外提供了非常簡潔的API調用,貌似外觀模式的很多庫都很受歡迎雇毫;
2玄捕、Glide自動感知生命周期,很節(jié)約資源棚放,不會內存泄漏枚粘;
3、超級強大的緩存機制飘蚯;
4馍迄、各種圖片轉換,超級方便局骤。
Android 【手撕Glide】--Glide緩存機制
Android 【手撕Glide】--Glide緩存機制(面試)
Android 【手撕Glide】--Glide是如何關聯(lián)生命周期的攀圈?
我想只要用過Glide的同學都或多或少聽過Glide的緩存機制,比如Glide用了3級緩存峦甩;又用了Lrucache赘来、DiskLrucache;Glide緩存圖片會緩存多張等等凯傲。但還是有很多同學對緩存源碼和緩存原理沒有一個整體的清晰的思路犬辰,本文就是來解決這個問題的,為了高效的學習冰单,本文按照如下思路來講解Glide緩存機制:
- Glide緩存簡介
- Glide緩存Key
- Glide內存緩存的讀寫
- Glide磁盤緩存的讀寫
Glide緩存簡介
三級緩存or二級緩存幌缝?
在沒學習源碼之前,我連這個最基本的概念都不確定诫欠,以前老是聽人說緩存是內存--->磁盤--->網絡這樣的方式去獲取圖片資源的涵卵,但這就是3級緩存嗎浴栽?明顯不是,這個只是2級緩存轿偎;Glide也是按照這種方式獲取圖片的典鸡,但是略有不同,Glide將它的緩存分為2個大的部分贴硫,一個是內存緩存椿每,一個是硬盤緩存。其中內存緩存又分為2種英遭,弱引用和Lrucache;磁盤緩存就是DiskLrucache亦渗,DiskLrucache算法和Lrucache差不多的挖诸,所以現(xiàn)在看起來Glide3級緩存的話應該是WeakReference + Lrucache + DiskLrucache
。
內存緩存的主要作用是防止應用重復將圖片數據讀取到內存當中法精;而硬盤緩存的主要作用是防止應用重復從網絡或其他地方下載和讀取數據多律。
Glide緩存Key
緩存是為了解決重復加載問題,那必然要有一個key來區(qū)分不同的圖片資源搂蜓。從下面生成key的代碼可以看出Glide生成key的方式遠比我們想象的要復雜狼荞,決定緩存Key的參數有8種,其中包括圖片URL帮碰、寬相味、高。
#Engine.load()
//生成緩存key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
這里可以得出一個結論殉挽,幾乎任意配置的改變都會導致同一張圖片生成多個緩存key丰涉。舉個例子:同一張圖片加載到2個不同大小的ImageView會生成2個緩存圖片。至于EngineKey的作用斯碌,當然是用于讀取/寫入緩存圖片的時候用到的,別著急夕春,后面的流程你會多次看到的惶我。
Glide內存緩存的讀寫
這里先從內存緩存說起吧,首先Glide默認開啟了內存緩存冠骄,當然你可以選擇手動關閉伪煤。注意:只有開啟了內存才能使用下面的內存緩存功能。
skipMemoryCache(true) //關閉內存緩存
前面提到過內存緩存是通過弱引用+LruCache
的方式實現(xiàn)的猴抹。那內存緩存在哪里實現(xiàn)的呢带族?還記得剛才在Engine#load()
方法中生成緩存Key嗎?內存緩存的代碼也在這里實現(xiàn)的蟀给,下面一起看一下源碼:
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();
//1.生成緩存key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//2.從弱引用讀取內存緩存
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;
}
//3.從LruCache讀取緩存
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;
}
//...省略
//4.通過線程池從網絡加載圖片
看一下內存緩存部分的邏輯蝙砌,首先通過loadFromActiveResources
從弱引用讀妊舳椤;如果沒有再通過loadFromCache
從LruCache讀仍窨恕恬总;2者中的任意一個獲取到數據就會調用onResourceReady
就是將資源回調給ImageView去加載。
Engine#loadFromActiveResources()
:從弱引用讀取緩存
public class Engine {
private final ActiveResources activeResources;
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
//沒有開啟內存緩存就直接返回
if (!isMemoryCacheable) {
return null;
}
//弱引用獲取緩存圖片
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
}
#ActiveResources#get()
class ActiveResources {
//弱引用的hashmap
Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
EngineResource<?> get(Key key) {
//1.從弱引用的map獲取圖片資源
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
//2.最終需要的資源對象
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
}
這里的邏輯也不復雜肚邢,通過弱引用的hashmap來存儲資源壹堰,Key是緩存key,ResourceWeakReference代表資源骡湖,它繼承WeakReference贱纠。首先從弱引用的map獲取圖片資源,然后通過弱引用的get()方法獲取最終需要的對象响蕴,如果activeRef.get();
拿不到(可能已經被系統(tǒng)GC回收)谆焊,那就clear(從弱引用中移除,清除資源等)浦夷。
再回到剛才Engine的load方法中辖试,如果loadFromActiveResources
獲取不到,會調用loadFromCache
來獲取劈狐。
Engine#loadFromCache()
:從LruCache讀取緩存
public class Engine {
private final ActiveResources activeResources;
//Lrucache對象
private final MemoryCache cache;
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
//從lrucache獲取
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
//存到弱引用的HashMap
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
//從lrucache刪除資源
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;
}
}
邏輯比較簡單罐孝,通過lrucache獲取圖片資源,如果獲取到的話就會從LruCache中刪除這張圖片肥缔,然后會調用acquire()
方法和activate()
方法,其中activate()
是把取到的數據會存到弱引用中莲兢,說白了就是把圖片從LruCache轉移到弱引用。
EngineResource# acquire()
class EngineResource<Z> implements Resource<Z> {
//圖片引用計數器
private int acquired;
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
是用一個acquired
變量用來記錄圖片被引用的次數姑宽,調用acquire()
方法會讓變量加1遣耍,調用release()
方法會讓變量減1,release()
方法后面調用的時候會講到炮车。舵变。
到這里,內存緩存的讀取就說完了瘦穆,下面講一下內存緩存的寫入纪隙。很明顯緩存的寫入是在加載圖片之后,所以回到剛才Engine#load()
方法
public <R> LoadStatus load(//一系列參數) {
//...省略
//1.從弱引用讀取內存緩存
loadFromActiveResources()
//2.從LruCache讀取緩存
loadFromCache();
//3.通過EngineJob加載圖片
EngineJob<R> engineJob =
engineJobFactory.build();
DecodeJob<R> decodeJob =
decodeJobFactory.build();
jobs.put(key, engineJob);
engineJob.addCallback(cb);
//通過線程池加載圖片
engineJob.start(decodeJob);
}
這里有2個關鍵的對象扛或,EngineJob和DecodeJob绵咱,EngineJob
內部維護了線程池,用來管理資源加載熙兔,當資源加載完畢的時候通知回調悲伶; DecodeJob
是線程池中的一個任務艾恼。最后通過start()
方法加載圖片,實際上是在DecodeJob
的run()
方法中完成的,當圖片加載完成麸锉,最終會回調EngineJob#onResourceReady ()
#EngineJob
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
this.resource = resource;
this.dataSource = dataSource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
@Override
public boolean handleMessage(Message message) {
EngineJob<?> job = (EngineJob<?>) message.obj;
switch (message.what) {
case MSG_COMPLETE:
job.handleResultOnMainThread();
break;
case MSG_EXCEPTION:
job.handleExceptionOnMainThread();
break;
case MSG_CANCELLED:
job.handleCancelledOnMainThread();
break;
default:
throw new IllegalStateException("Unrecognized message: " + message.what);
}
return true;
}
EngineJob#handleResultOnMainThread ()
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.
//1.圖片引用計數器+1
engineResource.acquire();
//2.回調到EngineJob處理
listener.onEngineJobComplete(this, key, engineResource);
//noinspection ForLoopReplaceableByForEach to improve perf
//3.遍歷加載的圖片
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
//圖片引用計數器+1
engineResource.acquire();
//將資源回調給ImageView去加載
cb.onResourceReady(engineResource, dataSource);
}
}
// Our request is complete, so we can release the resource.
//4.釋放資源钠绍,圖片引用計數器-1
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
這里一共有4步:
1、圖片引用計數器+1花沉;
2柳爽、listener.onEngineJobComplete()
,這個listener是EngineJobListener
接口對象碱屁,這里是將結果回調給Engine#onEngineJobComplete()
處理磷脯;
3、遍歷遍歷加載的圖片娩脾,每加載到一張圖片争拐,引用計數器+1 ,并且會將資源回調給ImageView去加載晦雨;
4、釋放資源隘冲,圖片引用計數器-1 闹瞧。
public void onEngineJobComplete(EngineJob<?> engineJob, 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.activate(key, resource);
}
}
jobs.removeIfCurrent(key, engineJob);
}
可以看到,這里把資源放到弱引用展辞,也就是內存緩存的寫入了奥邮。但是LruCache緩存貌似還沒有出現(xiàn),再回頭看看剛才的Engine#onEngineJobComplete()
方法罗珍,最后還調用了還調用了 engineResource.release()
方法來釋放資源洽腺,還記得之前講過這個方法嗎,在獲取內存緩存的時候會調用acquire()
覆旱,使得acquired+1蘸朋;而調用release()
方法會讓acquired -1。
EngineResource#release()
class EngineResource<Z> implements Resource<Z> {
//圖片引用計數器
private int 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()
的時機主要是:加載網絡圖片時暫停請求/加載完畢以及清除資源扣唱。release()
將acquired-1藕坯,并且當acquired==0
的時候,會調用listener.onResourceReleased()
方法噪沙,而這個listener正是Engine炼彪。
Engine#onResourceReleased()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
//從弱引用集合activeResources中移除資源
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
//放入LruCache緩存
cache.put(cacheKey, resource);
} else {
//回收資源
resourceRecycler.recycle(resource);
}
}
}
這個onResourceReleased ()
方法也不復雜,作用是釋放資源正歼,先從弱引用集合activeResources中移除資源辐马,然后再把圖片資源放入LruCache緩存。
注意:在上面的調用
EngineJob#handleResultOnMainThread ()
去加載圖片等時候局义,如果加載圖片成功喜爷,那么acquired>=1
冗疮,說明有圖片正在被引用;而等到暫停請求/退出頁面的時候再次調用release()
時贞奋,acquired==0
才會去調用onResourceReleased ()
把緩存從弱引用轉移到Lrucache赌厅。
小結
這個
acquired
變量是用來記錄圖片被引用的次數,調用acquire()
方法會讓變量加1轿塔,調用release()
方法會讓變量減1特愿。當調用loadFromActiveResources()
、loadFromCache()
勾缭、EngineJob#handleResultOnMainThread()
獲取圖片的時候都會執(zhí)行acquire()
方法揍障;當暫停請求或者加載完畢或者清除資源時會調用release()
方法。
注意:從弱引用取緩存俩由,拿到的話毒嫡,引用計數+1;從LruCache中拿緩存幻梯,拿到的話兜畸,引用計數也是+1,同時把LruCache緩存轉移到弱應用緩存池中碘梢;從EngineJob去加載圖片咬摇,拿到的話,引用計數也是+1煞躬,會把圖片放到弱引用肛鹏。反過來,一旦沒有地方正在使用這個資源恩沛,就會將其從弱引用中轉移到LruCache緩存池中在扰。這也說明了正在使用中的圖片使用弱引用
來進行緩存,暫時不用的圖片使用LruCache
來進行緩存的功能雷客。
Glide磁盤緩存的讀寫
Glide5大磁盤緩存策略
DiskCacheStrategy.DATA
: 只緩存原始圖片芒珠;
DiskCacheStrategy.RESOURCE
:只緩存轉換過后的圖片;
DiskCacheStrategy.ALL
:既緩存原始圖片佛纫,也緩存轉換過后的圖片妓局;對于遠程圖片,緩存DATA
和RESOURCE
呈宇;對于本地圖片好爬,只緩存RESOURCE
;
DiskCacheStrategy.NONE
:不緩存任何內容甥啄;
DiskCacheStrategy.AUTOMATIC
:默認策略存炮,嘗試對本地和遠程圖片使用最佳的策略。當下載網絡圖片時,使用DATA
穆桂;對于本地圖片宫盔,使用RESOURCE
;
上面講內存緩存寫入的時候說到過享完,如果在內存緩存中沒獲取到數據灼芭,就通過DecodeJob
和EngineJob
加載圖片。EngineJob
內部維護了線程池般又,用來管理資源加載彼绷,當資源加載完畢的時候通知回調; DecodeJob
是線程池中的一個任務茴迁。
DecodeJob#run()
public void run() {
...
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
}
...
}
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);
}
}
這里會執(zhí)行到runWrapper()
方法寄悯,對于一個新的任務,會執(zhí)行第一個分支堕义。stage
:用來決定 DecodeJob 狀態(tài)猜旬,表示數據的加載狀態(tài);currentGenerator
:是解析生成器倦卖,有多個實現(xiàn)類:ResourcesCacheGenerator
洒擦、SourceGenerator
、DataCacheGenerator
怕膛,它們負責各種硬盤緩存策略下的緩存管理:
-
ResourceCacheGenerator
:管理變換之后的緩存數據秘遏; -
SourceGenerator
:管理未經轉換的原始緩存數據; -
SourceGenerator
:直接從網絡下載解析數據嘉竟。
接下來會調用3個方法getNextStage
getNextGenerator
runGenerators()
,
#DecodeJob
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);
}
}
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);
}
}
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.
}
別怕啊洋侨,寫到這里我也很惡心了舍扰,本來想要一筆帶過的,但是發(fā)現(xiàn)網上沒什么博客能說清楚這個問題,還是寫一下吧希坚。這個地方之所以這么麻煩边苹,是因為緩存策略的原因。
還是回到上面的runWrapped()
方法裁僧,它先是調用了getNextStage(Stage.INITIALIZE)
,于是進入getNextStage()
第一個分支个束,根據緩存策略返回Stage
,由于我用的是默認的緩存策略聊疲,這里decodeCachedResource
返回true茬底,于是getNextStage()
方法返回Stage.RESOURCE_CACHE
;然后執(zhí)行getNextGenerator
方法获洲,根據上一步的stage阱表,這里執(zhí)行第一個分支,返回ResourceCacheGenerator
,這個方法返回的3個Generator對象都是用于加載圖片資源的;接著調用runGenerators()
方法:它通過while循環(huán)來獲取那3個解析生成器Generator最爬,循環(huán)條件主要是currentGenerator.startNext()
,它的實際調用在那3個Generator里面涉馁,在方法內部又會獲取stage和currentGenerator,當stage == Stage.SOURCE
時會跳出循環(huán)爱致。如果是第一次從網絡加載圖片的話烤送,最終數據的加載會交給 SourceGenerator
進行;如果是從磁盤緩存獲取的話會根據緩存策略的不同從ResourceCacheGenerator
或者DataCacheGenerator
獲取。
先來看一下ResourceCacheGenerator
的startNext()
方法糠悯,果不其然帮坚,里面先是構建了緩存key,然后從DiskLruCache
獲取到了緩存圖片逢防。
#ResourceCacheGenerator
public boolean startNext() {
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
}
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( // 1 構建獲取緩存信息的鍵
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey); // 2 從緩存中獲取緩存信息
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); // 3 使用文件方式從緩存中讀取緩存數據
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
這里是通過ResourceCacheGenerator
獲取的緩存圖片叶沛,其實DataCacheGenerator
也是差不多的。到這里磁盤緩存讀取就說完了忘朝,下面來看一下磁盤緩存的寫入灰署。不用想,是第一次從網絡加載圖片時寫入的局嘁,第一次從網絡加載時溉箕,會調用SourceGenerator#startNext()
方法:
#SourceGenerator
public boolean startNext() {
//判斷是否有可以用于緩存數據
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
//調用disklrucache緩存
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;
//DataFetcher加載數據
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
這里用的是SourceGenerator
的startNext()
方法,其它2個Generator的實現(xiàn)是不一樣的悦昵。先判斷是否有可以用于緩存的數據肴茄,由于是第一次加載網絡圖片,所以是沒有的但指,然后通過DataFetcher
加載數據寡痰,具體來說網絡的話是通過HttpUrlFetcher
來實現(xiàn)的。
HttpUrlFetcher#loadData()
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//加載數據獲得文件輸入流
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//在 SourceGenerator 回調
callback.onDataReady(result);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
通過HttpURLConnection
來獲取InputStream棋凳,然后在 SourceGenerator
回調拦坠。
SourceGenerator #onDataReady ()
#SourceGenerator
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);
}
}
這里先是保存數據,然后回調到DecodeJob
中剩岳, 將會根據當前的stage從 run()
方法開始執(zhí)行一遍贞滨,并再次調用SourceGenerator
的 startNext()
方法。這次已經存在可以用于緩存的數據了拍棕。所以cacheData()
方法將會被觸發(fā):
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
//DiskLrucache保存圖片
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);
}
這里通過DiskCache
對象晓铆,實際上是DiskLruCacheWrapper
實現(xiàn)類對象在磁盤緩存圖片。
小結
磁盤緩存是在EngineJob
中的DecodeJob
任務中完成的绰播,依次通過ResourcesCacheGenerator
骄噪、SourceGenerator
、DataCacheGenerator
來獲取緩存數據蠢箩。ResourcesCacheGenerator
獲取的是轉換過的緩存數據腰池;SourceGenerator
獲取的是未經轉換的原始的緩存數據尾组;DataCacheGenerator
是通過網絡獲取圖片數據再按照按照緩存策略的不同去緩存不同的圖片到磁盤上。
到這里示弓,總算寫完了整體流程分析讳侨。不過一大堆,你基本不太可能記得住奏属,下面就給大家總結一下吧:
總結(干貨)
Glide緩存分為弱引用+ LruCache+ DiskLruCache
跨跨,其中讀取數據的順序是:弱引用 > LruCache > DiskLruCache>網絡;寫入緩存的順序是:網絡 --> DiskLruCache-->弱引用-->LruCache
內存緩存分為弱引用的和 LruCache 囱皿,其中正在使用的圖片使用弱引用緩存勇婴,暫時不使用的圖片用 LruCache緩存,這一點是通過 圖片引用計數器(acquired變量)來實現(xiàn)的嘱腥,詳情可以看內存緩存的小結耕渴。
磁盤緩存就是通過DiskLruCache實現(xiàn)的,根據緩存策略的不同會獲取到不同類型的緩存圖片齿兔。它的邏輯是:先從轉換后的緩存中瘸髁场;沒有的話再從原始的(沒有轉換過的)緩存中拿數據分苇;再沒有的話就從網絡加載圖片數據添诉,獲取到數據之后,再依次緩存到磁盤和弱引用医寿。
拋一個問題給大家思考討論:
為什么Glide內存緩存要設計2層栏赴,弱引用和LruCache?
這是一個朋友的理解 用弱引用緩存的資源都是當前活躍資源 activeRource靖秩,資源的使用頻率比較高须眷,這個時候如果從LruCache取資源,LinkHashmap查找資源的效率不是很高的沟突。所以他會設計一個弱引用來緩存當前活躍資源柒爸,來替Lrucache減壓。
參考:
Android Glide4.0 源碼遨游記(第五集——緩存機制)
Android Glide4.0 源碼遨游記(第四集)
Glide 系列-3:Glide 緩存的實現(xiàn)原理(4.8.0)
Glide 系列-2:主流程源碼分析(4.8.0)
[Glide的圖片內存優(yōu)化]
(http://www.reibang.com/p/12e318e6414f)