Glide緩存簡(jiǎn)介
Glide的緩存設(shè)計(jì)可以說是非常先進(jìn)的,考慮的場(chǎng)景也很周全。在緩存這一功能上,Glide又將它分成了兩個(gè)模塊,一個(gè)是內(nèi)存緩存,一個(gè)是硬盤緩存。
這兩個(gè)緩存模塊的作用各不相同,內(nèi)存緩存的主要作用是防止應(yīng)用重復(fù)將圖片數(shù)據(jù)讀取到內(nèi)存當(dāng)中,而硬盤緩存的主要作用是防止應(yīng)用重復(fù)從網(wǎng)絡(luò)或其他地方重復(fù)下載和讀取數(shù)據(jù)。
內(nèi)存緩存和硬盤緩存的相互結(jié)合才構(gòu)成了Glide極佳的圖片緩存效果,那么接下來我們就分別來分析一下這兩種緩存的使用方法以及它們的實(shí)現(xiàn)原理。
緩存Key
既然是緩存功能,就必然會(huì)有用于進(jìn)行緩存的Key。那么Glide的緩存Key是怎么生成的呢?我不得不說,Glide的緩存Key生成規(guī)則非常繁瑣,決定緩存Key的參數(shù)然有10個(gè)之多。不過繁瑣歸繁瑣,至少邏輯還是比較簡(jiǎn)單的,我們先來看一下Glide緩存Key的生成邏輯。
生成緩存Key的代碼在Engine類的load()方法當(dāng)中,這部分代碼我們?cè)谏弦黄恼庐?dāng)中已經(jīng)分析過了,只不過當(dāng)時(shí)忽略了緩存相關(guān)的內(nèi)容,那么我們現(xiàn)在重新來看一下:
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread(); long startTime = LogTime.getLogTime(); final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
...
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
可以看到,這里在第11行調(diào)用了fetcher.getId()方法獲得了一個(gè)id字符串,這個(gè)字符串也就是我們要加載的圖片的標(biāo)識(shí),比如說如果是一張網(wǎng)絡(luò)上的圖片的話,那么這個(gè)id就是這張圖片的url地址。
接下來在第12行,將這個(gè)id連同著signature、width、height等等10個(gè)參數(shù)一起傳入到EngineKeyFactory的buildKey()方法當(dāng)中,從而構(gòu)建出了一個(gè)EngineKey對(duì)象,這個(gè)EngineKey也就是Glide中的緩存Key了。
可見,決定緩存Key的條件非常多,即使你用override()方法改變了一下圖片的width或者h(yuǎn)eight,也會(huì)生成一個(gè)完全不同的緩存Key。
EngineKey類的源碼大家有興趣可以自己去看一下,其實(shí)主要就是重寫了equals()和hashCode()方法,保證只有傳入EngineKey的所有參數(shù)都相同的情況下才認(rèn)為是同一個(gè)EngineKey對(duì)象,我就不在這里將源碼貼出來了。
內(nèi)存緩存
有了緩存Key,接下來就可以開始進(jìn)行緩存了,那么我們先從內(nèi)存緩存看起。
首先你要知道,默認(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ò)或硬盤上讀取了,這樣無(wú)疑就可以大幅度提升圖片的加載效率。比方說你在一個(gè)RecyclerView當(dāng)中反復(fù)上下滑動(dòng),RecyclerView中只要是Glide加載過的圖片都可以直接從內(nèi)存當(dāng)中迅速讀取并展示出來,從而大大提升了用戶體驗(yàn)。
而Glide為人性化的是,你甚至不需要編寫任何額外的代碼就能自動(dòng)享受到這個(gè)極為便利的內(nèi)存緩存功能,因?yàn)镚lide默認(rèn)就已經(jīng)將它開啟了。
那么既然已經(jīng)默認(rèn)開啟了這個(gè)功能,還有什么可講的用法呢?只有一點(diǎn),如果你有什么特殊的原因需要禁用內(nèi)存緩存功能,Glide對(duì)此提供了接口:
Glide.with(this)
.load(url)
.skipMemoryCache(true)
.into(imageView);
可以看到,只需要調(diào)用skipMemoryCache()方法并傳入true,就表示禁用掉Glide的內(nèi)存緩存功能。
沒錯(cuò),關(guān)于Glide內(nèi)存緩存的用法就只有這么多,可以說是相當(dāng)簡(jiǎn)單。但是我們不可能只停留在這么簡(jiǎn)單的層面上,接下來就讓我們就通過閱讀源碼來分析一下Glide的內(nèi)存緩存功能是如何實(shí)現(xiàn)的。
其實(shí)說到內(nèi)存緩存的實(shí)現(xiàn),非常容易就讓人想到LruCache算法(Least Recently Used),也叫近期少使用算法。它的主要算法原理就是把近使用的對(duì)象用強(qiáng)引用存儲(chǔ)在LinkedHashMap中,并且把近少使用的對(duì)象在緩存值達(dá)到預(yù)設(shè)定值之前從內(nèi)存中移除。LruCache的用法也比較簡(jiǎn)單,我在Android高效加載大圖、多圖解決方案,有效避免程序OOM 這篇文章當(dāng)中有提到過它的用法,感興趣的朋友可以去參考一下。
那么不必多說,Glide內(nèi)存緩存的實(shí)現(xiàn)自然也是使用的LruCache算法。不過除了LruCache算法之外,Glide還結(jié)合了一種弱引用的機(jī)制,共同完成了內(nèi)存緩存功能,下面就讓我們來通過源碼分析一下。
首先回憶一下,在上一篇文章的第二步load()方法中,我們當(dāng)時(shí)分析到了在loadGeneric()方法中會(huì)調(diào)用Glide.buildStreamModelLoader()方法來獲取一個(gè)ModelLoader對(duì)象。當(dāng)時(shí)沒有再跟進(jìn)到這個(gè)方法的里面再去分析,那么我們現(xiàn)在來看下它的源碼:
public class Glide { public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,
Context context) { if (modelClass == null) { if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Unable to load null model, setting placeholder only");
} return null;
} return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass);
} public static Glide get(Context context) { if (glide == null) { synchronized (Glide.class) { if (glide == null) {
Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
GlideBuilder builder = new GlideBuilder(applicationContext); for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
glide = builder.createGlide(); for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide);
}
}
}
} return glide;
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
這里我們還是只看關(guān)鍵,在第11行去構(gòu)建ModelLoader對(duì)象的時(shí)候,先調(diào)用了一個(gè)Glide.get()方法,而這個(gè)方法就是關(guān)鍵。我們可以看到,get()方法中實(shí)現(xiàn)的是一個(gè)單例功能,而創(chuàng)建Glide對(duì)象則是在第24行調(diào)用GlideBuilder的createGlide()方法來創(chuàng)建的,那么我們跟到這個(gè)方法當(dāng)中:
public class GlideBuilder { ...
Glide createGlide() { if (sourceService == null) { final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
sourceService = new FifoPriorityThreadPoolExecutor(cores);
} if (diskCacheService == null) {
diskCacheService = new FifoPriorityThreadPoolExecutor(1);
}
MemorySizeCalculator calculator = new MemorySizeCalculator(context); if (bitmapPool == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { int size = calculator.getBitmapPoolSize();
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
} if (memoryCache == null) {
memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
} if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
} if (engine == null) {
engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
} if (decodeFormat == null) {
decodeFormat = DecodeFormat.DEFAULT;
} return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
}
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
這里也就是構(gòu)建Glide對(duì)象的地方了。那么觀察第22行,你會(huì)發(fā)現(xiàn)這里new出了一個(gè)LruResourceCache,并把它賦值到了memoryCache這個(gè)對(duì)象上面。你沒有猜錯(cuò),這個(gè)就是Glide實(shí)現(xiàn)內(nèi)存緩存所使用的LruCache對(duì)象了。不過我這里并不打算展開來講LruCache算法的具體實(shí)現(xiàn),如果你感興趣的話可以自己研究一下它的源碼。
現(xiàn)在創(chuàng)建好了LruResourceCache對(duì)象只能說是把準(zhǔn)備工作做好了,接下來我們就一步步研究Glide中的內(nèi)存緩存到底是如何實(shí)現(xiàn)的。
剛才在Engine的load()方法中我們已經(jīng)看到了生成緩存Key的代碼,而內(nèi)存緩存的代碼其實(shí)也是在這里實(shí)現(xiàn)的,那么我們重新來看一下Engine類load()方法的完整源碼:
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { ... public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread(); long startTime = LogTime.getLogTime(); final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) {
cb.onResourceReady(cached); if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
} return null;
}
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) {
cb.onResourceReady(active); if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
} return null;
}
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);
}
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable); if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
} return new LoadStatus(cb, engineJob);
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
可以看到,這里在第17行調(diào)用了loadFromCache()方法來獲取緩存圖片,如果獲取到就直接調(diào)用cb.onResourceReady()方法進(jìn)行回調(diào)。如果沒有獲取到,則會(huì)在第26行調(diào)用loadFromActiveResources()方法來獲取緩存圖片,獲取到的話也直接進(jìn)行回調(diào)。只有在兩個(gè)方法都沒有獲取到緩存的情況下,才會(huì)繼續(xù)向下執(zhí)行,從而開啟線程來加載圖片。
也就是說,Glide的圖片加載過程中會(huì)調(diào)用兩個(gè)方法來獲取內(nèi)存緩存,loadFromCache()和loadFromActiveResources()。這兩個(gè)方法中一個(gè)使用的就是LruCache算法,另一個(gè)使用的就是弱引用。我們來看一下它們的源碼:
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { private final MemoryCache cache; private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
... private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
} return cached;
} private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key); final EngineResource result; if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource) cached;
} else {
result = new EngineResource(cached, true );
} return result;
} private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null;
}
EngineResource<?> active = null;
WeakReference<EngineResource<?>> activeRef = activeResources.get(key); if (activeRef != null) {
active = activeRef.get(); if (active != null) {
active.acquire();
} else {
activeResources.remove(key);
}
} return active;
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
在loadFromCache()方法的一開始,首先就判斷了isMemoryCacheable是不是false,如果是false的話就直接返回null。這是什么意思呢?其實(shí)很簡(jiǎn)單,我們剛剛不是學(xué)了一個(gè)skipMemoryCache()方法嗎?如果在這個(gè)方法中傳入true,那么這里的isMemoryCacheable就會(huì)是false,表示內(nèi)存緩存已被禁用。
我們繼續(xù)住下看,接著調(diào)用了getEngineResourceFromCache()方法來獲取緩存。在這個(gè)方法中,會(huì)使用緩存Key來從cache當(dāng)中取值,而這里的cache對(duì)象就是在構(gòu)建Glide對(duì)象時(shí)創(chuàng)建的LruResourceCache,那么說明這里其實(shí)使用的就是LruCache算法了。
但是呢,觀察第22行,當(dāng)我們從LruResourceCache中獲取到緩存圖片之后會(huì)將它從緩存中移除,然后在第16行將這個(gè)緩存圖片存儲(chǔ)到activeResources當(dāng)中。activeResources就是一個(gè)弱引用的HashMap,用來緩存正在使用中的圖片,我們可以看到,loadFromActiveResources()方法就是從activeResources這個(gè)HashMap當(dāng)中取值的。使用activeResources來緩存正在使用中的圖片,可以保護(hù)這些圖片不會(huì)被LruCache算法回收掉。
好的,從內(nèi)存緩存中讀取數(shù)據(jù)的邏輯大概就是這些了。概括一下來說,就是如果能從內(nèi)存緩存當(dāng)中讀取到要加載的圖片,那么就直接進(jìn)行回調(diào),如果讀取不到的話,才會(huì)開啟線程執(zhí)行后面的圖片加載邏輯。
現(xiàn)在我們已經(jīng)搞明白了內(nèi)存緩存讀取的原理,接下來的問題就是內(nèi)存緩存是在哪里寫入的呢?這里我們又要回顧一下上一篇文章中的內(nèi)容了。還記不記得我們之前分析過,當(dāng)圖片加載完成之后,會(huì)在EngineJob當(dāng)中通過Handler發(fā)送一條消息將執(zhí)行邏輯切回到主線程當(dāng)中,從而執(zhí)行handleResultOnMainThread()方法。那么我們現(xiàn)在重新來看一下這個(gè)方法,代碼如下所示:
class EngineJob implements EngineRunnable.EngineRunnableManager { private final EngineResourceFactory engineResourceFactory;
... private void handleResultOnMainThread() { if (isCancelled) {
resource.recycle(); return;
} else if (cbs.isEmpty()) { throw new IllegalStateException("Received a resource without any callbacks to notify");
}
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
engineResource.acquire();
listener.onEngineJobComplete(key, engineResource); for (ResourceCallback cb : cbs) { if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource);
}
}
engineResource.release();
} static class EngineResourceFactory { public <R> EngineResource<R> build(Resource<R> resource, boolean isMemoryCacheable) { return new EngineResource<R>(resource, isMemoryCacheable);
}
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
在第13行,這里通過EngineResourceFactory構(gòu)建出了一個(gè)包含圖片資源的EngineResource對(duì)象,然后會(huì)在第16行將這個(gè)對(duì)象回調(diào)到Engine的onEngineJobComplete()方法當(dāng)中,如下所示:
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { ... @Override public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread(); if (resource != null) {
resource.setResourceListener(key, this); if (resource.isCacheable()) {
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
jobs.remove(key);
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
現(xiàn)在就非常明顯了,可以看到,在第13行,回調(diào)過來的EngineResource被put到了activeResources當(dāng)中,也就是在這里寫入的緩存。
那么這只是弱引用緩存,還有另外一種LruCache緩存是在哪里寫入的呢?這就要介紹一下EngineResource中的一個(gè)引用機(jī)制了。觀察剛才的handleResultOnMainThread()方法,在第15行和第19行有調(diào)用EngineResource的acquire()方法,在第23行有調(diào)用它的release()方法。其實(shí),EngineResource是用一個(gè)acquired變量用來記錄圖片被引用的次數(shù),調(diào)用acquire()方法會(huì)讓變量加1,調(diào)用release()方法會(huì)讓變量減1,代碼如下所示:
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;
} 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);
}
}
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
也就是說,當(dāng)acquired變量大于0的時(shí)候,說明圖片正在使用中,也就應(yīng)該放到activeResources弱引用緩存當(dāng)中。而經(jīng)過release()之后,如果acquired變量等于0了,說明圖片已經(jīng)不再被使用了,那么此時(shí)會(huì)在第24行調(diào)用listener的onResourceReleased()方法來釋放資源,這個(gè)listener就是Engine對(duì)象,我們來看下它的onResourceReleased()方法:
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { private final MemoryCache cache; private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
... @Override public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey); if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
可以看到,這里首先會(huì)將緩存圖片從activeResources中移除,然后再將它put到LruResourceCache當(dāng)中。這樣也就實(shí)現(xiàn)了正在使用中的圖片使用弱引用來進(jìn)行緩存,不在使用中的圖片使用LruCache來進(jìn)行緩存的功能。
這就是Glide內(nèi)存緩存的實(shí)現(xiàn)原理。
硬盤緩存
接下來我們開始學(xué)習(xí)硬盤緩存方面的內(nèi)容。
不知道你還記不記得,在本系列的篇文章中我們就使用過硬盤緩存的功能了。當(dāng)時(shí)為了禁止Glide對(duì)圖片進(jìn)行硬盤緩存而使用了如下代碼:
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
調(diào)用diskCacheStrategy()方法并傳入DiskCacheStrategy.NONE,就可以禁用掉Glide的硬盤緩存功能了。
這個(gè)diskCacheStrategy()方法基本上就是Glide硬盤緩存功能的一切,它可以接收四種參數(shù):
-
DiskCacheStrategy.NONE: 表示不緩存任何內(nèi)容。
-
DiskCacheStrategy.SOURCE: 表示只緩存原始圖片。
-
DiskCacheStrategy.RESULT: 表示只緩存轉(zhuǎn)換過后的圖片(默認(rèn)選項(xiàng))。
-
DiskCacheStrategy.ALL : 表示既緩存原始圖片,也緩存轉(zhuǎn)換過后的圖片。
上面四種參數(shù)的解釋本身并沒有什么難理解的地方,但是有一個(gè)概念大家需要了解,就是當(dāng)我們使用Glide去加載一張圖片的時(shí)候,Glide默認(rèn)并不會(huì)將原始圖片展示出來,而是會(huì)對(duì)圖片進(jìn)行壓縮和轉(zhuǎn)換(我們會(huì)在后面學(xué)習(xí)這方面的內(nèi)容)。總之就是經(jīng)過種種一系列操作之后得到的圖片,就叫轉(zhuǎn)換過后的圖片。而Glide默認(rèn)情況下在硬盤緩存的就是轉(zhuǎn)換過后的圖片,我們通過調(diào)用diskCacheStrategy()方法則可以改變這一默認(rèn)行為。
好的,關(guān)于Glide硬盤緩存的用法也就只有這么多,那么接下來還是老套路,我們通過閱讀源碼來分析一下,Glide的硬盤緩存功能是如何實(shí)現(xiàn)的。
首先,和內(nèi)存緩存類似,硬盤緩存的實(shí)現(xiàn)也是使用的LruCache算法,而且Google還提供了一個(gè)現(xiàn)成的工具類DiskLruCache。我之前也專門寫過一篇文章對(duì)這個(gè)DiskLruCache工具進(jìn)行了比較全面的分析,感興趣的朋友可以參考一下 Android DiskLruCache完全解析,硬盤緩存的佳方案 。當(dāng)然,Glide是使用的自己編寫的DiskLruCache工具類,但是基本的實(shí)現(xiàn)原理都是差不多的。
接下來我們看一下Glide是在哪里讀取硬盤緩存的。這里又需要回憶一下上篇文章中的內(nèi)容了,Glide開啟線程來加載圖片后會(huì)執(zhí)行EngineRunnable的run()方法,run()方法中又會(huì)調(diào)用一個(gè)decode()方法,那么我們重新再來看一下這個(gè)decode()方法的源碼:
private Resource<?> decode() throws Exception { if (isDecodingFromCache()) { return decodeFromCache();
} else { return decodeFromSource();
}
}
可以看到,這里會(huì)分為兩種情況,一種是調(diào)用decodeFromCache()方法從硬盤緩存當(dāng)中讀取圖片,一種是調(diào)用decodeFromSource()來讀取原始圖片。默認(rèn)情況下Glide會(huì)優(yōu)先從緩存當(dāng)中讀取,只有緩存中不存在要讀取的圖片時(shí),才會(huì)去讀取原始圖片。那么我們現(xiàn)在來看一下decodeFromCache()方法的源碼,如下所示:
private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null; try {
result = decodeJob.decodeResultFromCache();
} catch (Exception e) { if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Exception decoding result from cache: " + e);
}
} if (result == null) {
result = decodeJob.decodeSourceFromCache();
} return result;
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
可以看到,這里會(huì)先去調(diào)用DecodeJob的decodeResultFromCache()方法來獲取緩存,如果獲取不到,會(huì)再調(diào)用decodeSourceFromCache()方法獲取緩存,這兩個(gè)方法的區(qū)別其實(shí)就是DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE這兩個(gè)參數(shù)的區(qū)別,相信不需要我再做什么解釋吧。
那么我們來看一下這兩個(gè)方法的源碼吧,如下所示:
public Resource<Z> decodeResultFromCache() throws Exception { if (!diskCacheStrategy.cacheResult()) { return null;
} long startTime = LogTime.getLogTime();
Resource<T> transformed = loadFromCache(resultKey);
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed); return result;
} public Resource<Z> decodeSourceFromCache() throws Exception { if (!diskCacheStrategy.cacheSource()) { return null;
} long startTime = LogTime.getLogTime();
Resource<T> decoded = loadFromCache(resultKey.getOriginalKey()); return transformEncodeAndTranscode(decoded);
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
可以看到,它們都是調(diào)用了loadFromCache()方法從緩存當(dāng)中讀取數(shù)據(jù),如果是decodeResultFromCache()方法就直接將數(shù)據(jù)解碼并返回,如果是decodeSourceFromCache()方法,還要調(diào)用一下transformEncodeAndTranscode()方法先將數(shù)據(jù)轉(zhuǎn)換一下再解碼并返回。
然而我們注意到,這兩個(gè)方法中在調(diào)用loadFromCache()方法時(shí)傳入的參數(shù)卻不一樣,一個(gè)傳入的是resultKey,另外一個(gè)卻又調(diào)用了resultKey的getOriginalKey()方法。這個(gè)其實(shí)非常好理解,剛才我們已經(jīng)解釋過了,Glide的緩存Key是由10個(gè)參數(shù)共同組成的,包括圖片的width、height等等。但如果我們是緩存的原始圖片,其實(shí)并不需要這么多的參數(shù),因?yàn)椴挥脤?duì)圖片做任何的變化。那么我們來看一下getOriginalKey()方法的源碼:
public Key getOriginalKey() { if (originalKey == null) {
originalKey = new OriginalKey(id, signature);
} return originalKey;
}
可以看到,這里其實(shí)就是忽略了絕大部分的參數(shù),只使用了id和signature這兩個(gè)參數(shù)來構(gòu)成緩存Key。而signature參數(shù)絕大多數(shù)情況下都是用不到的,因此基本上可以說就是由id(也就是圖片url)來決定的Original緩存Key。
搞明白了這兩種緩存Key的區(qū)別,那么接下來我們看一下loadFromCache()方法的源碼吧:
private Resource<T> loadFromCache(Key key) throws IOException {
File cacheFile = diskCacheProvider.getDiskCache().get(key); if (cacheFile == null) { return null;
}
Resource<T> result = null; try {
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally { if (result == null) {
diskCacheProvider.getDiskCache().delete(key);
}
} return result;
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
這個(gè)方法的邏輯非常簡(jiǎn)單,調(diào)用getDiskCache()方法獲取到的就是Glide自己編寫的DiskLruCache工具類的實(shí)例,然后調(diào)用它的get()方法并把緩存Key傳入,就能得到硬盤緩存的文件了。如果文件為空就返回null,如果文件不為空則將它解碼成Resource對(duì)象后返回即可。
這樣我們就將硬盤緩存讀取的源碼分析完了,那么硬盤緩存又是在哪里寫入的呢?趁熱打鐵我們趕快繼續(xù)分析下去。
剛才已經(jīng)分析過了,在沒有緩存的情況下,會(huì)調(diào)用decodeFromSource()方法來讀取原始圖片。那么我們來看下這個(gè)方法:
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource(); return transformEncodeAndTranscode(decoded);
}
這個(gè)方法中只有兩行代碼,decodeSource()顧名思義是用來解析原圖片的,而transformEncodeAndTranscode()則是用來對(duì)圖片進(jìn)行轉(zhuǎn)換和轉(zhuǎn)碼的。我們先來看decodeSource()方法:
private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null; try { long startTime = LogTime.getLogTime(); final A data = fetcher.loadData(priority); if (isCancelled) { return null;
}
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
} return decoded;
} private Resource<T> decodeFromSourceData(A data) throws IOException { final Resource<T> decoded; if (diskCacheStrategy.cacheSource()) {
decoded = cacheAndDecodeSourceData(data);
} else { long startTime = LogTime.getLogTime();
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
} return decoded;
} private Resource<T> cacheAndDecodeSourceData(A data) throws IOException { long startTime = LogTime.getLogTime();
SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
startTime = LogTime.getLogTime();
Resource<T> result = loadFromCache(resultKey.getOriginalKey()); return result;
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
這里會(huì)在第5行先調(diào)用fetcher的loadData()方法讀取圖片數(shù)據(jù),然后在第9行調(diào)用decodeFromSourceData()方法來對(duì)圖片進(jìn)行解碼。接下來會(huì)在第18行先判斷是否允許緩存原始圖片,如果允許的話又會(huì)調(diào)用cacheAndDecodeSourceData()方法。而在這個(gè)方法中同樣調(diào)用了getDiskCache()方法來獲取DiskLruCache實(shí)例,接著調(diào)用它的put()方法就可以寫入硬盤緩存了,注意原始圖片的緩存Key是用的resultKey.getOriginalKey()。
好的,原始圖片的緩存寫入就是這么簡(jiǎn)單,接下來我們分析一下transformEncodeAndTranscode()方法的源碼,來看看轉(zhuǎn)換過后的圖片緩存是怎么寫入的。代碼如下所示:
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) { long startTime = LogTime.getLogTime();
Resource<T> transformed = transform(decoded);
writeTransformedToCache(transformed);
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed); return result;
} private void writeTransformedToCache(Resource<T> transformed) { if (transformed == null || !diskCacheStrategy.cacheResult()) { return;
} long startTime = LogTime.getLogTime();
SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
diskCacheProvider.getDiskCache().put(resultKey, writer);
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
這里的邏輯就更加簡(jiǎn)單明了了。先是在第3行調(diào)用transform()方法來對(duì)圖片進(jìn)行轉(zhuǎn)換,然后在writeTransformedToCache()方法中將轉(zhuǎn)換過后的圖片寫入到硬盤緩存中,調(diào)用的同樣是DiskLruCache實(shí)例的put()方法,不過這里用的緩存Key是resultKey。
這樣我們就將Glide硬盤緩存的實(shí)現(xiàn)原理也分析完了。雖然這些源碼看上去如此的復(fù)雜,但是經(jīng)過Glide出色的封裝,使得我們只需要通過skipMemoryCache()和diskCacheStrategy()這兩個(gè)方法就可以輕松自如地控制Glide的緩存功能了。
了解了Glide緩存的實(shí)現(xiàn)原理之后,接下來我們?cè)賮韺W(xué)習(xí)一些Glide緩存的高級(jí)技巧吧。
高級(jí)技巧
雖說Glide將緩存功能高度封裝之后,使得用法變得非常簡(jiǎn)單,但同時(shí)也帶來了一些問題。
比如之前有一位群里的朋友就跟我說過,他們項(xiàng)目的圖片資源都是存放在七牛云上面的,而七牛云為了對(duì)圖片資源進(jìn)行保護(hù),會(huì)在圖片url地址的基礎(chǔ)之上再加上一個(gè)token參數(shù)。也就是說,一張圖片的url地址可能會(huì)是如下格式:
http://url.com/image.jpg?token=d9caa6e02c990b0a
而使用Glide加載這張圖片的話,也就會(huì)使用這個(gè)url地址來組成緩存Key。
但是接下來問題就來了,token作為一個(gè)驗(yàn)證身份的參數(shù)并不是一成不變的,很有可能時(shí)時(shí)刻刻都在變化。而如果token變了,那么圖片的url也就跟著變了,圖片url變了,緩存Key也就跟著變了。結(jié)果就造成了,明明是同一張圖片,就因?yàn)閠oken不斷在改變,導(dǎo)致Glide的緩存功能完全失效了。
這其實(shí)是個(gè)挺棘手的問題,而且我相信絕對(duì)不僅僅是七牛云這一個(gè)個(gè)例,大家在使用Glide的時(shí)候很有可能都會(huì)遇到這個(gè)問題。
那么該如何解決這個(gè)問題呢?我們還是從源碼的層面進(jìn)行分析,首先再來看一下Glide生成緩存Key這部分的代碼:
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread(); long startTime = LogTime.getLogTime(); final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
...
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
來看一下第11行,剛才已經(jīng)說過了,這個(gè)id其實(shí)就是圖片的url地址。那么,這里是通過調(diào)用fetcher.getId()方法來獲取的圖片url地址,而我們?cè)谏弦黄恼轮幸呀?jīng)知道了,fetcher就是HttpUrlFetcher的實(shí)例,我們就來看一下它的getId()方法的源碼吧,如下所示:
public class HttpUrlFetcher implements DataFetcher<InputStream> { private final GlideUrl glideUrl;
... public HttpUrlFetcher(GlideUrl glideUrl) { this(glideUrl, DEFAULT_CONNECTION_FACTORY);
}
HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) { this.glideUrl = glideUrl; this.connectionFactory = connectionFactory;
} @Override public String getId() { return glideUrl.getCacheKey();
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
可以看到,getId()方法中又調(diào)用了GlideUrl的getCacheKey()方法。那么這個(gè)GlideUrl對(duì)象是從哪里來的呢?其實(shí)就是我們?cè)趌oad()方法中傳入的圖片url地址,然后Glide在內(nèi)部把這個(gè)url地址包裝成了一個(gè)GlideUrl對(duì)象。
很明顯,接下來我們就要看一下GlideUrl的getCacheKey()方法的源碼了,如下所示:
public class GlideUrl { private final URL url; private final String stringUrl;
... public GlideUrl(URL url) { this(url, Headers.DEFAULT);
} public GlideUrl(String url) { this(url, Headers.DEFAULT);
} public GlideUrl(URL url, Headers headers) {
... this.url = url;
stringUrl = null;
} public GlideUrl(String url, Headers headers) {
... this.stringUrl = url; this.url = null;
} public String getCacheKey() { return stringUrl != null ? stringUrl : url.toString();
}
...
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
這里我將代碼稍微進(jìn)行了一點(diǎn)簡(jiǎn)化,這樣看上去更加簡(jiǎn)單明了。GlideUrl類的構(gòu)造函數(shù)接收兩種類型的參數(shù),一種是url字符串,一種是URL對(duì)象。然后getCacheKey()方法中的判斷邏輯非常簡(jiǎn)單,如果傳入的是url字符串,那么就直接返回這個(gè)字符串本身,如果傳入的是URL對(duì)象,那么就返回這個(gè)對(duì)象toString()后的結(jié)果。
其實(shí)看到這里,我相信大家已經(jīng)猜到解決方案了,因?yàn)間etCacheKey()方法中的邏輯太直白了,直接就是將圖片的url地址進(jìn)行返回來作為緩存Key的。那么其實(shí)我們只需要重寫這個(gè)getCacheKey()方法,加入一些自己的邏輯判斷,就能輕松解決掉剛才的問題了。
創(chuàng)建一個(gè)MyGlideUrl繼承自GlideUrl,代碼如下所示:
public class MyGlideUrl extends GlideUrl { private String mUrl; public MyGlideUrl(String url) { super(url);
mUrl = url;
} @Override public String getCacheKey() { return mUrl.replace(findTokenParam(), "");
} private String findTokenParam() {
String tokenParam = ""; int tokenKeyIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token="); if (tokenKeyIndex != -1) { int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1); if (nextAndIndex != -1) {
tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
} else {
tokenParam = mUrl.substring(tokenKeyIndex);
}
} return tokenParam;
}
}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
可以看到,這里我們重寫了getCacheKey()方法,在里面加入了一段邏輯用于將圖片url地址中token參數(shù)的這一部分移除掉。這樣getCacheKey()方法得到的就是一個(gè)沒有token參數(shù)的url地址,從而不管token怎么變化,終Glide的緩存Key都是固定不變的了。
當(dāng)然,定義好了MyGlideUrl,我們還得使用它才行,將加載圖片的代碼改成如下方式即可:
Glide.with(this)
.load(new MyGlideUrl(url))
.into(imageView);
也就是說,我們需要在load()方法中傳入這個(gè)自定義的MyGlideUrl對(duì)象,而不能再像之前那樣直接傳入url字符串了。不然的話Glide在內(nèi)部還是會(huì)使用原始的GlideUrl類,而不是我們自定義的MyGlideUrl類。
這樣我們就將這個(gè)棘手的緩存問題給解決掉了。
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對(duì)作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請(qǐng)及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對(duì)此聲明的最終解釋權(quán)。