前言
本文基於Glide v3.7.0源碼分析,Glide v4.0大致流程和v3.7.0差不多,在一些技術細節上有修改。主要內容有:
- 內存緩存讀取
- 內存緩存寫入
- 緩存引用計數
- 硬盤緩存讀取
- 硬盤緩存寫入
內存緩存讀取
內存緩存相關代碼主要在Engine.java中
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());
// 從LruCache中尋找緩存
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;
}
// LruCache中沒有找到緩存,從弱引用中尋找緩存
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;
}
...
}
兩級內存緩存:先從LruCache中尋找,如果找到了緩存,將圖片移出LruCache,加入activeResources弱引用緩存。如果在LruCache中沒找到的話到activeResources弱引用緩存中尋找。如果在內存緩存中找到,則引用計數加1。使用中的圖片用弱引用緩存來管理,沒有使用的圖片用LruCache來管理,判斷圖片有沒有使用的依據之一是引用計數,當引用計數等於0時,將圖片從弱引用緩存中移走,加入LruCache中。
內存緩存寫入
圖片會先寫入到activeResources弱引用緩存中。
@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()));
}
}
jobs.remove(key);
}
當引用計數爲0的時候會將圖片放到LruCache中。
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
緩存引用計數
這裏其實就有個問題了,當我們滑動圖片列表的時候,被回收的View中的圖片自然也用不到了,我們並沒有人爲的去把計數器減1,那麼Glide又是怎麼知道圖片已經沒有被引用了,從而將它放到LruCache中的呢?其實Glide並不知道,但activeResources包含的值是圖片資源的弱引用。
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
當滑動圖片列表的時候,系統會根據需要將這些圖片資源給回收掉,所以activeResources.get(key).get()
得到的就會爲null,爲null也沒有必要添加到LruCache中了。
但是這樣子一來,activeResources中就會有很多沒有用的item了,而它們又沒有被移除掉。爲了解決這個問題,Glide用了MessageQueue.IdleHandler
這個利器來解決這個問題。
(IdleHandler也可以用來解決App啓動延時加載的問題,具體可以看Android 啓動優化之延時加載)
private static class RefQueueIdleHandler implements MessageQueue.IdleHandler {
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
private final ReferenceQueue<EngineResource<?>> queue;
public RefQueueIdleHandler(Map<Key, WeakReference<EngineResource<?>>> activeResources,
ReferenceQueue<EngineResource<?>> queue) {
this.activeResources = activeResources;
this.queue = queue;
}
@Override
public boolean queueIdle() {
ResourceWeakReference ref = (ResourceWeakReference) queue.poll();
if (ref != null) {
activeResources.remove(ref.key);
}
return true;
}
}
在Glide v4.0版本,Glide不是用IdleHandler來解決這個問題的了,而是開了一個線程優先級爲Process.THREAD_PRIORITY_BACKGROUND
的線程,然後再通過MainHandler發送消息到主線程中去處理的,技術實現不同,但目的是一樣的。
硬盤緩存讀取
當從兩級內存緩存中都獲取不到圖片的時候,會開啓線程,嘗試從硬盤中獲取緩存。根據硬盤緩存策略,可以只緩存轉換過後的圖片,也可以緩存原始圖片。所以從硬盤中獲取緩存的時候,會有兩種方法,分別對應DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE。相關代碼在DecodeJob.java中。
private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null;
try {
// DiskCacheStrategy.RESULT
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Exception decoding result from cache: " + e);
}
}
if (result == null) {
// DiskCacheStrategy.SOURCE
result = decodeJob.decodeSourceFromCache();
}
return result;
}
硬盤緩存寫入
在沒有任何緩存的情況下,會解碼原始圖片
public Resource<Z> decodeFromSource() throws Exception {
// 解碼原始數據
Resource<T> decoded = decodeSource();
// 根據需要轉換圖片
return transformEncodeAndTranscode(decoded);
}
緩存原始圖片:
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;
}
可以看到,開啓了原始圖片緩存的情況下,Glide選擇將原始圖片先寫入硬盤緩存,然後再從硬盤緩存中加載圖片。如果沒有開啓原始圖片緩存,則直接解碼原始數據。
緩存結果圖片:
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);
}