Glide的缓存机制
Glide的緩存分為兩個模塊,一個是內存緩存,一個是硬盤緩存。
內存緩存的作用是防止應用重復將圖片數據讀取到內存當中;
硬盤緩存的作用是防止應用重復從網絡或其他地方下載和讀取數據。
Glide的緩存Key
生成緩存Key的代碼在Engine類的load()方法當中:
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height,
loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(),
transformation, loadProvider.getEncoder(),transcoder,
loadProvider.getSourceEncoder());
fetcher.getId()方法獲得了一個id字符串,這個字符串也就是我們要加載的圖片的唯一標識。
如果是網絡上的圖片,那么這個id就是這張圖片的url地址。
內存緩存
默認情況下,Glide自動就是開啟內存緩存的。
若要禁用內存緩存:
Glide.with(this).load(url).skipMemoryCache(true).into(imageView);
Glide內存緩存是使用LruCache實現的
在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);
}
第21行,創建了一個LruResourceCache實例,LruResourceCache繼承了LruCache。
在Engine類的load()方法當中:
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);}
可以看到,這里在第12行調用了loadFromCache()方法來獲取緩存圖片,如果獲取到就直接調用cb.onResourceReady()方法進行回調。如果沒有獲取到,則會在第21行調用loadFromActiveResources()方法來獲取緩存圖片,獲取到的話也直接進行回調。只有在兩個方法都沒有獲取到緩存的情況下,才會繼續向下執行,從而開啟線程來加載圖片。
也就是說,Glide的圖片加載過程中會調用兩個方法來獲取內存緩存,loadFromCache()和loadFromActiveResources()。這兩個方法中一個使用的就是LruCache算法,另一個使用的就是弱引用。
在Engine類中:
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 /*isCacheable*/);}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;}
在loadFromCache()方法中,調用了getEngineResourceFromCache()方法來獲取緩存。在這個方法中,會使用緩存Key來從cache當中取值,而這里的cache對象就是在構建Glide對象時創建的LruResourceCache。
第16行,當我們從LruResourceCache中獲取到緩存圖片之后會將它從緩存中移除,然后在第10行將這個緩存圖片存儲到activeResources當中。activeResources就是一個弱引用的HashMap,用來緩存正在使用中的圖片,我們可以看到,loadFromActiveResources()方法就是從activeResources這個HashMap當中取值的。使用activeResources來緩存正在使用中的圖片,可以保護這些圖片不會被LruCache算法回收掉。
總結一下,從內存緩存中讀取數據的邏輯,如果能從內存緩存當中讀取到要加載的圖片,那么就直接進行回調,如果讀取不到的話,才會開啟線程執行后面的圖片加載邏輯。
內存緩存的邏輯分析完了,接著分析內存緩存是在哪里寫入的。
Engine的onEngineJobComplete()方法當中:
@Overridepublic 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()));}}jobs.remove(key);}
在第8行,回調過來的EngineResource被put到了activeResources當中,也就是在這里寫入的緩存。那么這只是弱引用緩存,還有另外一種LruCache緩存是在哪里寫入的呢?
EngineResource類中的引用機制,用一個acquired變量記錄圖片被引用的次數,調用acquire()方法會讓變量加1,調用release()方法會讓變量減1,代碼如下所示:
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);}}
當acquired==0時,調用listener的onResourceReleased()方法來釋放資源,listener是Engine對象,它的onResourceReleased()方法:
@Overridepublic void onResourceReleased(Key cacheKey, EngineResource resource) {Util.assertMainThread();activeResources.remove(cacheKey);if (resource.isCacheable()) {cache.put(cacheKey, resource);} else {resourceRecycler.recycle(resource);}}
可以看到,這里首先會將緩存圖片從activeResources中移除,然后再將它put到LruResourceCache當中。這樣也就實現了正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用LruCache來進行緩存的功能。
總結:
1)先從LruCache中獲取緩存,若沒有則從弱引用中獲取緩存;
2)異步加載圖片完成后,緩存到弱引用;
3)從LruCache中獲取緩存后,先移除,再緩存到弱引用;
4)資源釋放時,先從弱引用中移除,再存入到LruCache中。
硬盤緩存
禁止硬盤緩存:
Glide.with(this).load(url).diskCacheStrategy(DiskCacheStrategy.NONE).into(imageView);
硬盤緩存的實現也是使用的LruCache算法,Glide使用的自己編寫的DiskLruCache工具類,但是基本的實現原理都是差不多的。
Glide開啟線程來加載圖片,會執行EngineRunnable的run()方法,run()方法中又會調用一個decode()方法:
private Resource<?> decode() throws Exception {if (isDecodingFromCache()) {return decodeFromCache();} else {return decodeFromSource();}
}
可以看到,這里分為兩種情況,一種是調用decodeFromCache()方法從硬盤緩存當中讀取圖片,一種是調用decodeFromSource()來讀取原始圖片。默認情況下Glide會優先從緩存當中讀取,只有緩存中不存在要讀取的圖片時,才會去讀取原始圖片。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;
}
可以看到,這里會先去調用DecodeJob的decodeResultFromCache()方法來獲取緩存,如果獲取不到,會再調用decodeSourceFromCache()方法獲取緩存,這兩個方法的區別其實就是DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE這兩個參數的區別。
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);
}
可以看到,它們都是調用了loadFromCache()方法從緩存當中讀取數據,如果是decodeResultFromCache()方法就直接將數據解碼并返回,如果是decodeSourceFromCache()方法,還要調用一下transformEncodeAndTranscode()方法先將數據轉換一下再解碼并返回。
需要注意,這兩個方法中在調用loadFromCache()方法時傳入的參數不一樣,一個傳入的是resultKey,另外一個卻又調用了resultKey的getOriginalKey()方法。
接著看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;
}
調用getDiskCache()方法獲取到的就是Glide自己編寫的DiskLruCache工具類的實例,然后調用它的get()方法并把緩存Key傳入,就能得到硬盤緩存的文件了。如果文件為空就返回null,如果文件不為空則將它解碼成Resource對象后返回即可。
接著分析硬盤緩存是在哪里寫入的。在沒有緩存的情況下,會調用decodeFromSource()方法來讀取原始圖片。
public Resource<Z> decodeFromSource() throws Exception {Resource<T> decoded = decodeSource();return transformEncodeAndTranscode(decoded);
}
decodeSource()是用來解析原圖片的,而transformEncodeAndTranscode()則是用來對圖片進行轉換和轉碼的。先看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;
}
在第5行先調用fetcher的loadData()方法讀取圖片數據,然后在第9行調用decodeFromSourceData()方法來對圖片進行解碼。
接下來會在第18行先判斷是否允許緩存原始圖片,如果允許的話又會調用cacheAndDecodeSourceData()方法。而在這個方法中同樣調用了getDiskCache()方法來獲取DiskLruCache實例,接著調用它的put()方法就可以寫入硬盤緩存了,注意原始圖片的緩存Key是用的resultKey.getOriginalKey()。
接下來分析一下transformEncodeAndTranscode()方法的源碼,來看看轉換過后的圖片緩存是怎么寫入的:
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);
}
先在第3行調用transform()方法來對圖片進行轉換,然后在writeTransformedToCache()方法中將轉換過后的圖片寫入到硬盤緩存中,調用的同樣是DiskLruCache實例的put()方法,不過這里用的緩存Key是resultKey。
總結:
1)從硬盤緩存中獲取,先獲取處理后的圖片,獲取不到再獲取原圖
2)沒有緩存時,會去獲取原圖,下載原圖后,若允許緩存原圖(一般不會緩存原圖),會先緩存原圖,接著處理圖片后,再緩存處理后圖片。
到這里,硬盤緩存的實現原理就分析完了。
總結
以上是生活随笔為你收集整理的Glide的缓存机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 内存缓存LruCache的简单使用
- 下一篇: Android线程池简单使用