Android Glide4.0 源碼遨游記(第五集——緩存機制)

Glide

?

前言

Android中加載圖片的形式有很多種,網(wǎng)上也有很多的知名圖片加載庫,例如Glide、Picasso、Fresco等等,它們?yōu)槲覀儙淼姆奖憔筒恍柙俣嘌粤耍瑹o論是從加載到緩存還是占位圖等等都提供了簡易的Api,且實現(xiàn)強大的功能。本系列只針對Glide4.0版本源碼進行分析,提高自身閱讀源碼的能力,同時也是為了了解其中加載的流程以及緩存的原理,本文盡可能地截圖說明結(jié)合源碼解析,如有疏忽之處,還請指教。

關(guān)于作者

一個在奮斗路上的Android小生,歡迎關(guān)注,互相交流
GitHubGitHub-ZJYWidget
CSDN博客IT_ZJYANG
簡 書Android小Y


?

前情回顧

前幾集已經(jīng)從Glide的最基本用法入手分析了Glide的請求、解析、加載圖片的流程。深刻體會到Glide源碼結(jié)構(gòu)的復(fù)雜,但Glide作為一個優(yōu)秀的圖片加載框架,必然要在緩存上下點功夫,本文主要分析Glide的緩存機制(用過的都能體會到它的緩存給我們帶來的絲滑體驗,特別是在請求網(wǎng)絡(luò)資源的場景,緩存機制尤為重要)

?

劇情(Glide 緩存 有備無患)

平時使用Glide做緩存相關(guān)的操作,主要有兩個api,一個是是設(shè)置skipMemoryCache(boolean),表示是否開啟內(nèi)存緩存,另外一個是diskCacheStrategy(DiskCacheStrategy)表示是否開啟硬盤緩存,如下:

Glide3.0以前的用法是:

Glide.with(this).load("http://xxx.xxx.png").skipMemoryCache(false).diskCacheStrategy(DiskCacheStrategy.NONE).into(imageView);

Glide4.0的用法是:

RequestOptions requestOptions = new RequestOptions();
requestOptions.skipMemoryCache(false);
requestOptions.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this).load("http://xxx.xxx.png").apply(requestOptions ).into(imageView);

調(diào)用方式有些差別,但核心緩存機制差不多,接下來的分析均以我們以4.0+為準,可以看到簡單的一個設(shè)置,即可決定是否要開啟緩存功能,那么Glide內(nèi)部究竟是如何對圖片做出"備份"的呢?

內(nèi)存緩存

上一集 Android Glide4.0 源碼遨游記(第四集)在講Glide的into方法的時候,有提到一個關(guān)鍵的核心類:Engine,圖片真正請求的地方正是從它的load方法開始的,而Glide也正是在這里做了核心的緩存操作,我們回頭看看那個load方法的源碼:

public <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

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

    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    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<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
}

可以看到,在創(chuàng)建engineJob和decodeJob之前,Glide還做了一些手腳,先是通過

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

創(chuàng)建了一個EngineKey對象,我們點進去buildKey看看:

class EngineKeyFactory {

  @SuppressWarnings("rawtypes")
  EngineKey buildKey(Object model, Key signature, int width, int height,
      Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
      Class<?> transcodeClass, Options options) {
    return new EngineKey(model, signature, width, height, transformations, resourceClass,
        transcodeClass, options);
  }
}

其實就是new了一個EngineKey對象,并把關(guān)于圖片的很多加載信息和類型都傳了進去,那這個Glide創(chuàng)建這個EngineKey意圖何在?不急,我們暫且記住有這么個對象,繼續(xù)往下看到這么一段:

EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
}

上文我們講到,onResourceReady就是將資源回調(diào)給ImageView去加載,那么這里先是判斷active不為空,然后就調(diào)用了onResourceReady并且return結(jié)束當前方法,那么這個active就很有可能是Glide對圖片的一種緩存(暫且這樣直觀理解),可以看到剛才生成的key值也傳了進去,所以我們看下loadFromActiveResources這個方法里做了什么:

@Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
}

可以看到,先是判斷isMemoryCacheable,這個就是我們skipMemoryCache傳進來的值的相反值,即:

skipMemoryCache傳true,isMemoryCacheable為false
skipMemoryCache傳false,isMemoryCacheable為true

所以如果我們設(shè)置為不緩存,那么這個條件就會通過,也就是直接執(zhí)行return null,那么剛才上一步的active對象也就相應(yīng)地被賦為null,就會繼續(xù)往下走。
如果設(shè)置了啟用緩存,那么這個條件就不滿足,繼續(xù)往下,可以看到從activeResources中通過key去拿了個EngineResource,那么activeResources里面存的是什么數(shù)據(jù)呢?,我們看下ActiveResources類:

public class ActiveResources {
  //忽略部分代碼

  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

  @Nullable
  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;
  }
}

可以看到實際上ActiveResrouces里是存了很多組key-resource的Map集合,并且resource是以弱引用的形式保存,然后get方法是根據(jù)一個key去map里面獲取對應(yīng)的資源對象,如果拿不到(即可能已經(jīng)被系統(tǒng)GC回收),那就clear,返回獲取到的EngineResource對象。

回到剛才Engine的load方法中,可以看到在調(diào)用loadFromActiveResources獲取不到的情況下,會調(diào)用loadFromCache來獲取,那么這個loadFromCache又是從什么地方獲取數(shù)據(jù)呢?loadFromCahce源碼如下:

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
}

同樣是判斷isMemoryCacheable,道理同上,就不再復(fù)述了,接著它調(diào)用了getEngineResourceFromCache,跟進去看看:

private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource<?>) cached;
    } else {
      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
}

可以看到,這里先是調(diào)用了一個cache對象的remove,cache是一個MemoryCache接口對象,看下Glide對MemoryCache的定義:

MemoryCache聲明

可以看到是一個從內(nèi)存中添加和移除資源的操作接口,看下它的實現(xiàn)類LruResourceCache

LruResourceCache

看到繼承了LruCache,也就是說,cache實際上是一個根據(jù)LruCache算法緩存圖片資源的類,剛才把EngineResource從其中remove掉,并且返回了這個被remove掉的EngineResource對象,如果為null,說明Lru緩存中已經(jīng)沒有該對象,如果不為null,則將其返回。
所以getEngineResourceFromCache其實就是根據(jù)Lru算法從內(nèi)存中獲取圖片資源,并且獲取到的話就順便從LruCache中移除掉,接著看回剛才的loadFromCache方法:

loadFromCache

可以看到,假如剛才從LruResourceCache中拿到了緩存,那么就會調(diào)用EngineResource的acquire()方法:

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;
}

這里只是將acquired+1,那么acquired變量用來干嘛的呢?我們跟蹤它被引用的地方,可以看到EngineResource的release()

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);
    }
}

可以看到,每次調(diào)用release的時候(比如暫停請求或者加載完畢時,這里就不展開講了,跟蹤一下即可),會將acquired-1,并且當acquired為0的時候,會調(diào)用listener的onResourceReleased方法,而這個listener正是Engine,我們回到Engine中看它的實現(xiàn):

@Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
}

可以看到調(diào)用了activeResources的deactivate方法(這個方法作用就是將該資源對象從弱引用集合activeResources中移除),接著,可以看到再將其put進cache,cache我們剛才提到了,是一個基于Lru算法的緩存管理類,所以這里就是將其加進了LruCache緩存中。

也就是說,每次觸發(fā)release就會將acquired變量-1,一旦它為0時,就會觸發(fā)Engine將該緩存從弱引用中移除,并且加進了LruCache緩存中。換句話理解就是,這個資源暫時不用到,Glide把它從弱引用轉(zhuǎn)移到了LruCache中。

而剛才的loadFromCache方法里,調(diào)用了acquire()使得 acquired+1,也就是此刻我正要使用這個緩存,做個標記,這樣就不會被轉(zhuǎn)移到LruCache中。

聯(lián)合起來的邏輯就是:先從LruCache中拿緩存,如果拿到了,就從LruCache緩存轉(zhuǎn)移到弱應(yīng)用緩存池中,并標記一下此刻正在引用。反過來,一旦沒有地方正在使用這個資源,就會將其從弱引用中轉(zhuǎn)移到LruCache緩存池中

?

硬盤緩存

分析完了內(nèi)存緩存,我們再來看看Glide的硬盤緩存。依然是接著剛才最開始的Engineload方法,在判斷了兩級內(nèi)存緩存之后,如果拿不到緩存,就會接著創(chuàng)建EngineJobDecodeJob(這兩個的作用見我另外一篇文章Android Glide4.0 源碼遨游記(第四集) ),然后接著就會調(diào)用進DecodeJob線程的run方法:

@Override
public void run() {
    TraceCompat.beginSection("DecodeJob#run");
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (Throwable t) {
      if (stage != Stage.ENCODE) {
        throwables.add(t);
        notifyFailed();
      }
      if (!isCancelled) {
        throw t;
      }
    } finally {
      if (localFetcher != null) {
        localFetcher.cleanup();
      }
      TraceCompat.endSection();
    }
  }

  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
}

run中主要還是調(diào)用的runWrapper方法,繼而調(diào)用runGenerator:

private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }

    // Otherwise a generator started a new load and we expect to be called back in
    // onDataFetcherReady.
}

這里調(diào)用了一個循環(huán)獲取解析生成器Generator的方法,而解析生成器有多個實現(xiàn)類:ResourcesCacheGenerator、SourceGenerator、DataCacheGenerator,它們負責(zé)各種硬盤緩存策略下的緩存管理,所以這里關(guān)鍵的條件在于currentGenerator.startNext()循環(huán)獲取每個Generator能否獲取到緩存,獲取不到就通過getNextGenerator進行下一種:

private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
}

所以我們看看ResourceCacheGenerator的startNext,看下它是用什么來緩存的,其中部分代碼如下:

currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);

這里通過一個資源的關(guān)鍵信息生成key,然后調(diào)用helper.getDiskCache().get(),我們跟進去DiskCache看看:

DiskCache getDiskCache() {
    return diskCacheProvider.getDiskCache();
}
interface DiskCacheProvider {
    DiskCache getDiskCache();
}

可以看到最終是調(diào)用了DiskCacheProvider接口的getDiskCache方法獲取一個DiskCache對象,那么這個D對象又是什么來頭呢?


DiskCache

可以看到這是一個用來緩存硬盤數(shù)據(jù)的接口,那么它的實現(xiàn)就是我們要找的最終目標:


DiskLruCache.png

里面的就不詳細分析下去了,這里主要維護了一個DiskLruCache,Glide就是通過這個來實現(xiàn)硬盤緩存的。

可以看到Glide的硬盤緩存是依靠DiskLruCache來進行緩存的,同樣也是Lru算法。

總結(jié)

Glide4.0的緩存機制概況來說就是,先從弱引用緩存中獲取數(shù)據(jù),假如獲取不到,就再嘗試從LruCache中獲取數(shù)據(jù),假如從LruCache中獲取到數(shù)據(jù)的話,就會將其從LruCache中轉(zhuǎn)移到弱引用緩存中,這么做的優(yōu)點是,下次再來拿數(shù)據(jù)的時候,就可以直接從弱引用中獲取。
資源對象用一個acquired變量用來記錄圖片被引用的次數(shù),調(diào)用acquire()方法會讓變量加1,調(diào)用release()方法會讓變量減1,然后一旦acquired減為0(沒有地方引用該資源),就會將其從弱引用中移除,添加到LruCache中。
使用中的資源會用弱引用來緩存,不在使用的資源會添加到LruCache中來緩存。
在二者都獲取不到的情況下會根據(jù)硬盤緩存策略通過DiskLruCache去硬盤中獲取數(shù)據(jù),正是這樣優(yōu)秀的緩存機制,讓我們在沒有網(wǎng)絡(luò)的情況下也能有很好的體驗。

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

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