Glide源碼探究(二) - 內(nèi)存緩存

系列文章:

讓我們接著上一篇筆記繼續(xù)講Engine的load方法,這里面就是Glide的資源加載流程。

public <R> LoadStatus load(...) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);

    EngineResource<?> memoryResource;
    synchronized (this) {
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      if (memoryResource == null) {
        return waitForExistingOrStartNewJob(...);
      }
    }
    
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE, false);
    return null;
}

這個(gè)方法的流程其實(shí)也挺清晰的:

  1. 創(chuàng)建緩存的key,這個(gè)key由一系列的參數(shù)組成,其中最重要的參數(shù)model在我們的例子中就是傳進(jìn)去的url。
  2. 使用這個(gè)key從內(nèi)存緩存中查詢資源
  3. 如果內(nèi)存緩存中查不到資源就開啟線程去加載資源
  4. 如果內(nèi)存緩存中可以查到資源就調(diào)用cb.onResourceReady回調(diào)

流程圖如下:

1.png

內(nèi)存緩存

內(nèi)存緩存的流程也比較清晰從代碼上看,如果開啟了內(nèi)存緩存的話會(huì)先從ActiveResources中查詢,查不到的話再?gòu)腃ache里面查詢:

private EngineResource<?> loadFromMemory( EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      ...
      return active;
    }

    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      ...
      return cached;
    }

    return null;
}

這兩個(gè)東西同樣是內(nèi)存緩存,那有啥區(qū)別呢?我們先看ActiveResources:

// Engine
private EngineResource<?> loadFromActiveResources(Key key) {
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
}

// ActiveResources
final class ActiveResources {
  ...
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  ...
  synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }
  ...
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    ...
  }
  ...
}

loadFromActiveResources實(shí)際上是從弱引用緩存里面查詢資源。既然是緩存當(dāng)然就要講講它的添加和刪除。

弱引用緩存的添加

首先是添加,弱引用緩存的添加基本有兩個(gè)時(shí)機(jī)。

  1. 從Cache里面查詢到的時(shí)候如果能查到,會(huì)將查到的資源放入弱引用緩存:
private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
}
  1. 子線程加載完資源后會(huì)將資源放入弱引用緩存:
public synchronized void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    if (resource != null && resource.isMemoryCacheable()) {
      activeResources.activate(key, resource);
    }
    ...
}

弱引用緩存的刪除

細(xì)心的同學(xué)可能會(huì)看到cached.acquire()這個(gè)操作,我們來看看它的代碼:

synchronized void acquire() {
    ...
    ++acquired;
}

有沒有想到些啥?沒錯(cuò),引用計(jì)數(shù)!

EngineResource是通過引用計(jì)數(shù)來管理的。有引用計(jì)數(shù)增加那就有引用計(jì)數(shù)減少。減少的操作在release方法里面:

void release() {
  boolean release = false;
  synchronized (this) {
    if (acquired <= 0) {
      throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
    }
    if (--acquired == 0) {
      release = true;
    }
  }
  if (release) {
    listener.onResourceReleased(key, this);
  }
}

如果引用計(jì)數(shù)降到了0就會(huì)調(diào)用listener的onResourceReleased回調(diào)回去,在onResourceReleased里面Engine會(huì)將資源從弱引用緩存刪除然后移到cache里:

// Engine
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
    }
}

// ActiveResources
synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
}

EngineResource.release又是什么時(shí)候被調(diào)用的呢?其實(shí)調(diào)用的地方有好幾處,但是最重要的兩處是

  1. 我們手動(dòng)調(diào)Glide的clear清理資源的時(shí)候:
// 手動(dòng)清理資源
Glide.with(context)
    .clear(img)
  1. 綁定的生命LifecycleListener.onDestroy的時(shí)候:
// RequestManager
public synchronized void onDestroy() {
  ...
  for (Target<?> target : targetTracker.getAll()) {
    clear(target);
  }
  ...
}

public void clear(@Nullable final Target<?> target) {
  ...
  untrackOrDelegate(target);
}

private void untrackOrDelegate(@NonNull Target<?> target) {
  ...
  Request request = target.getRequest();
  if (!isOwnedByUs && !glide.removeFromManagers(target) && request != null) {
    target.setRequest(null);
    request.clear();
  }
}

// SingleRequest
public void clear() {
  ...
  engine.release(toRelease);
  ...
}

// Engine
public void release(Resource<?> resource) {
  if (resource instanceof EngineResource) {
    ((EngineResource<?>) resource).release();
  }
  ...
}

簡(jiǎn)單來講就是加載資源的時(shí)候會(huì)把資源放入弱引用緩存,但資源不需要的時(shí)候會(huì)從弱引用緩存里面拿出移到另一個(gè)內(nèi)存緩存里面。所以這些資源都是正在使用的,這個(gè)弱引用緩存Glide把它叫做ActiveResources也是比較準(zhǔn)確的。

這個(gè)緩存使用弱引用的意義在于: 資源是保存在request里面的,而根據(jù)我們上篇筆記的知識(shí),request是以setTag的方式保存在view里面的。所以當(dāng)view被回收之后,resource也就沒有別的強(qiáng)引用可以連接到gc root,可以被java垃圾回收機(jī)制回收

LRUCache

Engine.load會(huì)先從ActiveResources中查詢,查不到的話再?gòu)腃ache里面查詢,這個(gè)Cache其實(shí)是一個(gè)LruResourceCache:

public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
    ...
}

從這個(gè)lru cache里面加載資源意味著把資源從lru cache里面移出,放到弱引用緩存中:

private EngineResource<?> loadFromCache(Key key) {
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire();
    activeResources.activate(key, cached);
  }
  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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
  }
  return result;
}

而正如上節(jié)我們講的資源的引用計(jì)數(shù)被清零的時(shí)候就會(huì)從弱引用緩存中刪除,加入lru cache中(注意這里放的是強(qiáng)引用,因?yàn)槭菑膙iew里面getTage拿到Resource強(qiáng)引用進(jìn)行release的):

// Engine
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
    }
}

// ActiveResources
synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
}

內(nèi)存緩存整體流程

至此整個(gè)內(nèi)存緩存的架構(gòu)就大體完整了,當(dāng)資源被使用的時(shí)候會(huì)被放到弱引用緩存,當(dāng)資源不再被使用的時(shí)候就會(huì)被放入LRU Cache:

2.png

補(bǔ)充: 內(nèi)存緩存的查詢順序

先查弱引用緩存再查lru cache這個(gè)順序并不是一開始就這樣的,我剛看glide源碼的時(shí)候看的是比較舊的版本,那個(gè)時(shí)候是先查lru cahe,查不到再查弱引用緩存。

這個(gè)順序在2017年11月這個(gè)commit之后修改的:

3.png

這個(gè)修改是為了修復(fù)資源被重復(fù)加載的bug,但實(shí)際上我看這部分修改的時(shí)候,感覺交換查詢順序應(yīng)該和解這個(gè)bug沒有直接關(guān)系,它更像是作者在重構(gòu)之后覺得先查lru cache再查弱引用緩存的順序怪怪的(我那個(gè)時(shí)候也有這種感覺),順手改了下:

4.png

這里它將原本寫在Engine的弱引用Map封裝成了ActiveResources。

那為啥順序不是一開始就是先查弱引用緩存呢?原因可能是一開始的代碼內(nèi)存緩存就沒有弱引用緩存:

public <T, Z> LoadStatus load(String id, int width, int height, ResourceDecoder<InputStream, Z> cacheDecoder,
        ResourceFetcher<T> fetcher, ResourceDecoder<T, Z> decoder,  Transformation<Z> transformation,
        ResourceEncoder<Z> encoder, Priority priority, ResourceCallback cb) {

    Key key = keyFactory.buildKey(id, width, height, cacheDecoder, decoder, transformation, encoder);

    Resource cached = cache.get(key);
    if (cached != null) {
        cached.acquire(1);
        cb.onResourceReady(cached);
        return null;
    }

    ResourceRunner current = runners.get(key);
    if (current != null) {
        EngineJob job = current.getJob();
        job.addCallback(cb);
        return new LoadStatus(cb, job);
    }

    ResourceRunner<Z> runner = factory.build(key, width, height, cacheDecoder, fetcher, decoder, transformation,
            encoder, priority, this);
    runner.getJob().addCallback(cb);
    runners.put(key, runner);
    runner.queue();
    return new LoadStatus(cb, runner.getJob());
}

可能是作者在后面優(yōu)化添加這個(gè)弱引用緩存的時(shí)候就順手放到了原有邏輯的后面。

其實(shí)仔細(xì)想想內(nèi)存緩存的架構(gòu),我也覺得這個(gè)順序其實(shí)并不重要,誰先誰后都不會(huì)有什么問題,無非是說流程是從lru cache拿出來放到弱引用緩存的,查詢的時(shí)候先查弱引用緩存會(huì)比較符合一般人的思路。

我們回想下兩個(gè)緩存存放的資源,簡(jiǎn)化到Activity的場(chǎng)景。弱引用緩存放的都是當(dāng)前activity正在使用的圖片,lru cache是activity退出之后回收的圖片。如果先查弱引用緩存,意味著當(dāng)上下不?;瑒?dòng)recyclerview的時(shí)候效率高一丟丟。如果先查lru cahe,意味著反復(fù)進(jìn)出同一個(gè)activity的時(shí)候效率高一丟丟。很難說哪個(gè)順序性能比較高。而且這一丟丟性能在現(xiàn)代設(shè)備中的確真的是毫無影響,所以讓人好理解是最重要的,先查弱引用緩存沒毛病。

這篇的篇幅也差不多了,下一篇我們繼續(xù)講資源的加載部分

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容