Glide缓存原理

glide缓存分为内存缓存和磁盘缓存。内存缓存分为活动缓存和cache。磁盘缓存又分为resource和data。本文将围绕加载图片流程介绍glide的缓存。

 Glide.with(this)
          .load(url)
           .skipMemoryCache(true)
            // .transform(CircleCrop())
           // .error(R.drawable.ic_launcher_background)
          .diskCacheStrategy(DiskCacheStrategy.NONE)
          .listener(getListener(index))
          .into(mIv)

1、load

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

    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height);

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

      if (memoryResource == null) {
        return waitForExistingOrStartNewJob(
            glideContext,
            model,
            signature,
            width,
            height);
      }
    }
  cb.onResourceReady(
        memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
    return null;
  }

1.1、loadFromMemory 先从内从中加载。

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

可以看到从内存加载又分为活动缓存和 cache
1.11、活动缓存
举例: 一个LinearLayout中有10个imageview,通过glide加载。那么这10个图片就缓存在activityResource中。 当点击的条目,删除一个item. 这个ActivityResource缓存并没有被移除。当gc运行时,将这个item回收掉之后了,就会将这个缓存加入到resourceReferenceQueue,然后通过遍历这个引用队列,从活动缓存中移除。
举例: 当在recyclerview的每个item加载一个imagview。 那么这个活动缓存的最大数目为= 可见数据+ 预加载数目 + 复用池1 ,因为从recyclerview移除之后,不一定就被立即回收了,当从复用池中复用item时,会主动释放上一次item对应的活动缓存。此时活动缓存就减少了。
活动缓存是何时存入的了?
1)、从缓存中获取到资源时,加入到活动缓存。

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

2)、从网络和磁盘加载资源完成时,加入到活动缓存。
活动缓存何时删除的了?
1)、onDestroy时,request调用clear() 会释放当前对应key的活动缓存。
2)、不能设置资源给target,释放当前key的活动缓存。
3)、获取活动缓存,发现该活动缓存EngineResource不存在,就删除key的活动缓存。也就是setIsActiveResourceRetentionAllowed(false)的情况。
4)、创建ActiveResources对象时,就开启了一个线程,一直轮询这个queue,如果存在的EngineResource,就删除对应的活动缓存。
1.12、内存缓存
获取内存时就从内存缓存中移除。

//Engine
 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,
              /* isMemoryCacheable= */ true,
              /* isRecyclable= */ true,
              key,
              /* listener= */ this);
    }
    return result;
  }

何时加入内存
从活动缓存移除就加入了内存缓存。内存缓存超过容量后被释放的bitmap等会被加入到bitmapPool中。从活动缓存中移除的要么加入内存缓存,要么加入到bitmapPool中。

//Engine
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
     //加入内存缓存
      cache.put(cacheKey, resource);
    } else {
   //资源释放,bitmap支持复用则加入到bitmapPool
      resourceRecycler.recycle(resource, /* forceNextFrame= */ false);
    }
  }


//BitmapResource
@Override
  public void recycle() {
    bitmapPool.put(bitmap);
  }

缓存资源的key是通过宽高,加载图片的model、options等生成的。所以宽高变化,可能导致内存中找不到这张图。
1.2、内存不存在,开启一个job从网络加载加载。

  private <R> Engine.LoadStatus waitForExistingOrStartNewJob(
            GlideContext glideContext,
            Object model,
            Key signature,
            int width,
            int height) {

        EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
        if (current != null) {
            current.addCallback(cb, callbackExecutor);
            return new Engine.LoadStatus(cb, current);
        }

        EngineJob<R> engineJob =
                engineJobFactory.build(
                        key,
                        isMemoryCacheable,
                        useUnlimitedSourceExecutorPool,
                        useAnimationPool,
                        onlyRetrieveFromCache);

        DecodeJob<R> decodeJob =
                decodeJobFactory.build(
                        glideContext,
                        model,
                        key,
                        signature,
                        width,
                        height,
                        engineJob);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb, callbackExecutor);
        engineJob.start(decodeJob);
        return new Engine.LoadStatus(cb, engineJob);
    }

可以看到先判断,是否是从从缓存中取获取缓存job。
如果缓存的job没有找到。则创建一个job。并且执行这个RunableJob。

  public synchronized void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor =
        decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

所以接下来,我们看下这个runnable(DecodeJob)的run方法。

 public void run() {
        DataFetcher<?> localFetcher = currentFetcher;
        try {
            runWrapped();
        }
    }

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

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

 private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE
            : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE
            : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
  }

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

可以看到,根据当前的状态和diskCacheStrategy磁盘策略的配置,来决定是加载RESOURCE_CACHE、DATA_CACHE、SOURCE。
RESOURCE_CACHE : 是转换过或者改变过采样率的文件。解码之后的。
DATA_CACHE:原始的未修改的数据。解码之前的。
SOURCE :网络文件,源文件。
1.3、INITIALIZE
如果配置全部允许,会首先去加载Resource_cache,再加载DATA_CACHE,如果都加载不了,再从网络加载SOURCE。
ResourceCacheGenerator和DataCacheGenerator会从磁盘缓存中加载数据,这里不看了,看下从Remote加载,SourceGenerator从网络加载数据。

// SourceGenerator
public boolean startNext() {
        if (dataToCache != null) {
            Object data = dataToCache;
            boolean isDataInCache = cacheData(data);
            dataToCache = null;
        }
        if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
            return true;
        }
        sourceCacheGenerator = null;
        loadData = null;
        boolean started = false;
        while (!started && hasNextModelLoader()) {
            loadData = helper.getLoadData().get(loadDataListIndex++);
            if (loadData != null
                    && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
                    || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
                started = true;
                startNextLoad(loadData);
            }
        }
        return started;
    }

    private void startNextLoad(final ModelLoader.LoadData<?> toStart) {
        loadData.fetcher.loadData(
                helper.getPriority(),
                new DataFetcher.DataCallback<Object>() {
                    @Override
                    public void onDataReady(@Nullable Object data) {
                        if (isCurrentRequest(toStart)) {
                            onDataReadyInternal(toStart, data);
                        }
                    }

                    @Override
                    public void onLoadFailed(@NonNull Exception e) {
                        if (isCurrentRequest(toStart)) {
                            onLoadFailedInternal(toStart, e);
                        }
                    }
                });
    }

  void onDataReadyInternal(LoadData<?> loadData, Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(
          loadData.sourceKey,
          data,
          loadData.fetcher,
          loadData.fetcher.getDataSource(),
          originalKey);
    }
  }

首先从网络loadData.fetcher.loadData中加载,然后判断isDataCacheable()是否可以缓存data,这个配置是缓存策略中配置的。如果可以缓存,调用cb.reschedule()。
1.4、SWITCH_TO_SOURCE_SERVICE

  private void reschedule(RunReason runReason) {
    this.runReason = runReason;
    callback.reschedule(this);
  }

  @Override
  public void reschedule() {
    reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE);
  }

这个方法会再次调用run()方法,状态变为SWITCH_TO_SOURCE_SERVICE从网络获取数据切换到从磁盘获取, 还会再次执行要这个类。 此时startNext方法中的dataToCache不为空,并且会调用将数据缓存到磁盘中,然后调用DataCacheGenerator.startNext(),调用磁盘缓存加载,返回磁盘加载的结果。

// SourceGenerator
 private boolean cacheData(Object dataToCache) throws IOException {
        try {
            DataRewinder<Object> rewinder = helper.getRewinder(dataToCache);
            Object data = rewinder.rewindAndGet();
            Encoder<Object> encoder = helper.getSourceEncoder(data);
            DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, data, helper.getOptions());
            DataCacheKey newOriginalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
            DiskCache diskCache = helper.getDiskCache();
            diskCache.put(newOriginalKey, writer);

            if (diskCache.get(newOriginalKey) != null) {
                originalKey = newOriginalKey;
                sourceCacheGenerator =
                        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
                return true;
            } else {
                cb.onDataFetcherReady(
                        loadData.sourceKey,
                        rewinder.rewindAndGet(),
                        loadData.fetcher,
                        loadData.fetcher.getDataSource(),
                        loadData.sourceKey);
            }
            return false;
        }
    }

上面就是在SourceGenerator中缓存数据Stream到磁盘过程。
1.5、DECODE_DATA
从getNextStage分析完INITIALIZE、SWITCH_TO_SOURCE_SERVICE,接下看下DECODE_DATA状态,从网络请求对应的HttpGlideUrlLoader,返回的输入流数据是ContentLengthInputStream。
从磁盘缓存对应的FileLoader,返回的输入流是文件输入流。
对于这些流需要转化成Bitmap、Drawable等。
decode的过程 就是将InputStream 转化成bitmap的过程。

//DecodeJob
 private void decodeFromRetrievedData() {
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
    } else {
      runGenerators();
    }
  }
 //DecodePath 
 public Resource<Transcode> decode(
      DataRewinder<DataType> rewinder,
      int width,
      int height,
      @NonNull Options options,
      DecodeCallback<ResourceType> callback)
      throws GlideException {
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    return transcoder.transcode(transformed, options);
  }

//DecodeJob
<Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
    @SuppressWarnings("unchecked")
    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
    Transformation<Z> appliedTransformation = null;
    Resource<Z> transformed = decoded;
    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
      transformed = appliedTransformation.transform(glideContext, decoded, width, height);
    }

    final EncodeStrategy encodeStrategy;
    final ResourceEncoder<Z> encoder;
    if (decodeHelper.isResourceEncoderAvailable(transformed)) {
      encoder = decodeHelper.getResultEncoder(transformed);
      encodeStrategy = encoder.getEncodeStrategy(options);
    } else {
      encoder = null;
      encodeStrategy = EncodeStrategy.NONE;
    }

    Resource<Z> result = transformed;
    boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
    if (diskCacheStrategy.isResourceCacheable(
        isFromAlternateCacheKey, dataSource, encodeStrategy)) {
      final Key key;
      switch (encodeStrategy) {
        case SOURCE:
          key = new DataCacheKey(currentSourceKey, signature);
          break;
        case TRANSFORMED:
          key =
              new ResourceCacheKey(
                  decodeHelper.getArrayPool(),
                  currentSourceKey,
                  signature,
                  width,
                  height,
                  appliedTransformation,
                  resourceSubClass,
                  options);
          break;
        default:
          throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
      }

      LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
      deferredEncodeManager.init(key, encoder, lockedResult);
      result = lockedResult;
    }
    return result;
  }

上面第一步是decode的流程,将stream转化成bitmap,设置采样率等,transformed,可以看到在解析完成调用了diskCacheStrategy.isResourceCacheable()这个方法主要是为了初始化Resouce_cache相关信息。

// DecodeJob
private void notifyEncodeAndRelease(
      Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
    GlideTrace.beginSection("DecodeJob.notifyEncodeAndRelease");
    try {
      Resource<R> result = resource;
      LockedResource<R> lockedResource = null;
      if (deferredEncodeManager.hasResourceToEncode()) {
        lockedResource = LockedResource.obtain(resource);
        result = lockedResource;
      }
      notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey)
      stage = Stage.ENCODE;
      try {
        if (deferredEncodeManager.hasResourceToEncode()) {
          deferredEncodeManager.encode(diskCacheProvider, options);
        }
      } finally {
      onEncodeComplete();
    } finally {
      GlideTrace.endSection();
    }
  }

private void notifyComplete(
      Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
    setNotifiedOrThrow();
    callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey);
  }

  void encode(DiskCacheProvider diskCacheProvider, Options options) {
      GlideTrace.beginSection("DecodeJob.encode");
      try {
        diskCacheProvider
            .getDiskCache()
            .put(key, new DataCacheWriter<>(encoder, toEncode, options));
      } finally {
        toEncode.unlock();
        GlideTrace.endSection();
      }
    }

notifyComplete就是调用callback.onResourceReady这个方法最终就回调到我们给request设置的RequestListener中。
上面我们知道缓存Resource相关key已经初始化了,接着deferredEncodeManager.encode将resource缓存在本地,整个加载过程就结束。
上面我们一直提到磁盘缓存策略,那么我们看一下DiskCacheStrategy.ALL的代码.

 public static final DiskCacheStrategy ALL =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource == DataSource.REMOTE;
        }

        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return dataSource != DataSource.RESOURCE_DISK_CACHE
              && dataSource != DataSource.MEMORY_CACHE;
        }

        @Override
        public boolean decodeCachedResource() {
          return true;
        }

        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };

isDataCacheable 表示可以将原始数据data缓存。
isResourceCacheable 表示可以将Resouce数据缓存。
decodeCachedResource 表示可以从Resource 中加载数据。
decodeCachedData 表示可以从Data中加载数据。

image.png

上面这幅图展示了glide缓存加载流程,没有BitmapPool,BitmapPool 只是从磁盘缓存、网络加载图片时,decode图片需要复用的之前图片的内存,从bitmappool中获取。 活动内存释放、cache的大小超过了容量会加入到bitmapPool。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容