Glide緩存機制

前言

本文基於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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章