草庐IT

Android 【手撕Glide】--Glide缓存机制

唠嗑008 2023-09-13 原文

本文源码解析基于Glide 4.6.1
不知道大家最开始使用Glide的原因是什么?我的原因很简单就是冲着那句Glide.with(this).load(url).into(imageview)去的,再加上Google的推荐,就一直沿用至今。以前也不太了解它,就知道它使用简洁而且很火,不过最近看了一些它的源码设计,算是找到了使用Glide理由。我目前的缘由如下:
1、Glide通过高度封装之后,通过外观模式对外提供了非常简洁的API调用,貌似外观模式的很多库都很受欢迎;
2、Glide自动感知生命周期,很节约资源,不会内存泄漏;
3、超级强大的缓存机制;
4、各种图片转换,超级方便。

Android 【手撕Glide】--Glide缓存机制
Android 【手撕Glide】--Glide缓存机制(面试)
Android 【手撕Glide】--Glide是如何关联生命周期的?

我想只要用过Glide的同学都或多或少听过Glide的缓存机制,比如Glide用了3级缓存;又用了Lrucache、DiskLrucache;Glide缓存图片会缓存多张等等。但还是有很多同学对缓存源码和缓存原理没有一个整体的清晰的思路,本文就是来解决这个问题的,为了高效的学习,本文按照如下思路来讲解Glide缓存机制:

  • Glide缓存简介
  • Glide缓存Key
  • Glide内存缓存的读写
  • Glide磁盘缓存的读写

Glide缓存简介

三级缓存or二级缓存?
在没学习源码之前,我连这个最基本的概念都不确定,以前老是听人说缓存是内存--->磁盘--->网络这样的方式去获取图片资源的,但这就是3级缓存吗?明显不是,这个只是2级缓存;Glide也是按照这种方式获取图片的,但是略有不同,Glide将它的缓存分为2个大的部分,一个是内存缓存,一个是硬盘缓存。其中内存缓存又分为2种,弱引用和Lrucache;磁盘缓存就是DiskLrucache,DiskLrucache算法和Lrucache差不多的,所以现在看起来Glide3级缓存的话应该是WeakReference + Lrucache + DiskLrucache

内存缓存的主要作用是防止应用重复将图片数据读取到内存当中;而硬盘缓存的主要作用是防止应用重复从网络或其他地方下载和读取数据。

Glide缓存Key

缓存是为了解决重复加载问题,那必然要有一个key来区分不同的图片资源。从下面生成key的代码可以看出Glide生成key的方式远比我们想象的要复杂,决定缓存Key的参数有8种,其中包括图片URL、宽、高。

#Engine.load()
//生成缓存key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

这里可以得出一个结论,几乎任意配置的改变都会导致同一张图片生成多个缓存key。举个例子:同一张图片加载到2个不同大小的ImageView会生成2个缓存图片。至于EngineKey的作用,当然是用于读取/写入缓存图片的时候用到的,别着急,后面的流程你会多次看到的。

Glide内存缓存的读写

这里先从内存缓存说起吧,首先Glide默认开启了内存缓存,当然你可以选择手动关闭。注意:只有开启了内存才能使用下面的内存缓存功能。

skipMemoryCache(true) //关闭内存缓存

前面提到过内存缓存是通过弱引用+LruCache的方式实现的。那内存缓存在哪里实现的呢?还记得刚才在Engine#load()方法中生成缓存Key吗?内存缓存的代码也在这里实现的,下面一起看一下源码:

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

    //1.生成缓存key
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    //2.从弱引用读取内存缓存
    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;
    }

    //3.从LruCache读取缓存
    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;
    }
 
    //...省略
    //4.通过线程池从网络加载图片
   

看一下内存缓存部分的逻辑,首先通过loadFromActiveResources从弱引用读取;如果没有再通过loadFromCache从LruCache读取;2者中的任意一个获取到数据就会调用onResourceReady就是将资源回调给ImageView去加载。

Engine#loadFromActiveResources():从弱引用读取缓存

public class Engine  {
  private final ActiveResources activeResources;

private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
//没有开启内存缓存就直接返回   
 if (!isMemoryCacheable) {
      return null;
    }
    //弱引用获取缓存图片
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }
}

#ActiveResources#get()
class ActiveResources {
    //弱引用的hashmap
    Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

    EngineResource<?> get(Key key) {
    //1.从弱引用的map获取图片资源
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }
    //2.最终需要的资源对象
    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }
}

这里的逻辑也不复杂,通过弱引用的hashmap来存储资源,Key是缓存key,ResourceWeakReference代表资源,它继承WeakReference。首先从弱引用的map获取图片资源,然后通过弱引用的get()方法获取最终需要的对象,如果activeRef.get();拿不到(可能已经被系统GC回收),那就clear(从弱引用中移除,清除资源等)。

再回到刚才Engine的load方法中,如果loadFromActiveResources获取不到,会调用loadFromCache来获取。

Engine#loadFromCache():从LruCache读取缓存

public class Engine  {
  private final ActiveResources activeResources;
 //Lrucache对象
    private final MemoryCache cache;

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    //从lrucache获取
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      //存到弱引用的HashMap
      activeResources.activate(key, cached);
    }
    return cached;
  }

private EngineResource<?> getEngineResourceFromCache(Key key) {
    //从lrucache删除资源
    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;
  }
}

逻辑比较简单,通过lrucache获取图片资源,如果获取到的话就会从LruCache中删除这张图片,然后会调用acquire()方法和activate()方法,其中activate()是把取到的数据会存到弱引用中,说白了就是把图片从LruCache转移到弱引用

EngineResource# acquire()

class EngineResource<Z> implements Resource<Z> {
  //图片引用计数器 
 private int acquired;

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是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,release()方法后面调用的时候会讲到。

到这里,内存缓存的读取就说完了,下面讲一下内存缓存的写入。很明显缓存的写入是在加载图片之后,所以回到刚才Engine#load()方法

 public <R> LoadStatus load(//一系列参数) {

  //...省略

  //1.从弱引用读取内存缓存
   loadFromActiveResources()

    //2.从LruCache读取缓存
 loadFromCache();

    //3.通过EngineJob加载图片

    EngineJob<R> engineJob =
        engineJobFactory.build();

    DecodeJob<R> decodeJob =
        decodeJobFactory.build();

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    //通过线程池加载图片
    engineJob.start(decodeJob);

  }

这里有2个关键的对象,EngineJob和DecodeJob,EngineJob 内部维护了线程池,用来管理资源加载,当资源加载完毕的时候通知回调; DecodeJob 是线程池中的一个任务。最后通过start()方法加载图片,实际上是在DecodeJobrun()方法中完成的,当图片加载完成,最终会回调EngineJob#onResourceReady ()

#EngineJob
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    this.resource = resource;
    this.dataSource = dataSource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
  }

@Override
public boolean handleMessage(Message message) {
  EngineJob<?> job = (EngineJob<?>) message.obj;
  switch (message.what) {
    case MSG_COMPLETE:
      job.handleResultOnMainThread();
      break;
    case MSG_EXCEPTION:
      job.handleExceptionOnMainThread();
      break;
    case MSG_CANCELLED:
      job.handleCancelledOnMainThread();
      break;
    default:
      throw new IllegalStateException("Unrecognized message: " + message.what);
  }
  return true;
}

EngineJob#handleResultOnMainThread ()

void handleResultOnMainThread() {
    stateVerifier.throwIfRecycled();
    if (isCancelled) {
      resource.recycle();
      release(false /*isRemovedFromQueue*/);
      return;
    } else if (cbs.isEmpty()) {
      throw new IllegalStateException("Received a resource without any callbacks to notify");
    } else if (hasResource) {
      throw new IllegalStateException("Already have resource");
    }
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;

    // Hold on to resource for duration of request so we don't recycle it in the middle of
    // notifying if it synchronously released by one of the callbacks.
    //1.图片引用计数器+1    
    engineResource.acquire();
    //2.回调到EngineJob处理
    listener.onEngineJobComplete(this, key, engineResource);

    //noinspection ForLoopReplaceableByForEach to improve perf
    //3.遍历加载的图片
    for (int i = 0, size = cbs.size(); i < size; i++) {
      ResourceCallback cb = cbs.get(i);
      if (!isInIgnoredCallbacks(cb)) {
        //图片引用计数器+1    
        engineResource.acquire();
        //将资源回调给ImageView去加载
        cb.onResourceReady(engineResource, dataSource);
      }
    }
    // Our request is complete, so we can release the resource.
    //4.释放资源,图片引用计数器-1  
    engineResource.release();

    release(false /*isRemovedFromQueue*/);
  }

这里一共有4步:
1、图片引用计数器+1;
2、listener.onEngineJobComplete(),这个listener是EngineJobListener接口对象,这里是将结果回调给Engine#onEngineJobComplete()处理;
3、遍历遍历加载的图片,每加载到一张图片,引用计数器+1 ,并且会将资源回调给ImageView去加载;
4、释放资源,图片引用计数器-1 。

public void onEngineJobComplete(EngineJob<?> engineJob, 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.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }

可以看到,这里把资源放到弱引用,也就是内存缓存的写入了。但是LruCache缓存貌似还没有出现,再回头看看刚才的Engine#onEngineJobComplete()方法,最后还调用了还调用了 engineResource.release()方法来释放资源,还记得之前讲过这个方法吗,在获取内存缓存的时候会调用acquire(),使得acquired+1;而调用release()方法会让acquired -1。

EngineResource#release()

class EngineResource<Z> implements Resource<Z> {
  //图片引用计数器 
 private int acquired;

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

调用release()的时机主要是:加载网络图片时暂停请求/加载完毕以及清除资源。release()将acquired-1,并且当acquired==0的时候,会调用listener.onResourceReleased()方法,而这个listener正是Engine。

Engine#onResourceReleased()

public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {

@Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    //从弱引用集合activeResources中移除资源
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      //放入LruCache缓存
      cache.put(cacheKey, resource);
    } else {
      //回收资源
      resourceRecycler.recycle(resource);
    }
  }
}

这个onResourceReleased ()方法也不复杂,作用是释放资源,先从弱引用集合activeResources中移除资源,然后再把图片资源放入LruCache缓存。

注意:在上面的调用EngineJob#handleResultOnMainThread ()去加载图片等时候,如果加载图片成功,那么acquired>=1,说明有图片正在被引用;而等到暂停请求/退出页面的时候再次调用release()时,acquired==0才会去调用onResourceReleased ()把缓存从弱引用转移到Lrucache。

小结

这个acquired变量是用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1。当调用loadFromActiveResources()loadFromCache()EngineJob#handleResultOnMainThread()获取图片的时候都会执行acquire()方法;当暂停请求或者加载完毕或者清除资源时会调用release()方法。

注意:从弱引用取缓存,拿到的话,引用计数+1;从LruCache中拿缓存,拿到的话,引用计数也是+1,同时把LruCache缓存转移到弱应用缓存池中;从EngineJob去加载图片,拿到的话,引用计数也是+1,会把图片放到弱引用。反过来,一旦没有地方正在使用这个资源,就会将其从弱引用中转移到LruCache缓存池中。这也说明了正在使用中的图片使用弱引用来进行缓存,暂时不用的图片使用LruCache来进行缓存的功能。


Glide磁盘缓存的读写

Glide5大磁盘缓存策略
DiskCacheStrategy.DATA: 只缓存原始图片;
DiskCacheStrategy.RESOURCE:只缓存转换过后的图片;
DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换过后的图片;对于远程图片,缓存 DATARESOURCE;对于本地图片,只缓存 RESOURCE
DiskCacheStrategy.NONE:不缓存任何内容;
DiskCacheStrategy.AUTOMATIC:默认策略,尝试对本地和远程图片使用最佳的策略。当下载网络图片时,使用DATA;对于本地图片,使用RESOURCE

上面讲内存缓存写入的时候说到过,如果在内存缓存中没获取到数据,就通过DecodeJobEngineJob加载图片。EngineJob 内部维护了线程池,用来管理资源加载,当资源加载完毕的时候通知回调; DecodeJob 是线程池中的一个任务。

DecodeJob#run()

public void run() {
   ...
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      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;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

这里会执行到runWrapper()方法,对于一个新的任务,会执行第一个分支。stage:用来决定 DecodeJob 状态,表示数据的加载状态;currentGenerator:是解析生成器,有多个实现类:ResourcesCacheGeneratorSourceGeneratorDataCacheGenerator,它们负责各种硬盘缓存策略下的缓存管理:

  • ResourceCacheGenerator:管理变换之后的缓存数据;
  • SourceGenerator:管理未经转换的原始缓存数据;
  • SourceGenerator:直接从网络下载解析数据。

接下来会调用3个方法getNextStage getNextGenerator runGenerators()

#DecodeJob
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;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }

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

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

别怕啊,写到这里我也很恶心了,本来想要一笔带过的,但是发现网上没什么博客能说清楚这个问题,还是写一下吧。这个地方之所以这么麻烦,是因为缓存策略的原因

还是回到上面的runWrapped()方法,它先是调用了getNextStage(Stage.INITIALIZE),于是进入getNextStage()第一个分支,根据缓存策略返回Stage,由于我用的是默认的缓存策略,这里decodeCachedResource返回true,于是getNextStage()方法返回Stage.RESOURCE_CACHE;然后执行getNextGenerator方法,根据上一步的stage,这里执行第一个分支,返回ResourceCacheGenerator,这个方法返回的3个Generator对象都是用于加载图片资源的;接着调用runGenerators()方法:它通过while循环来获取那3个解析生成器Generator,循环条件主要是currentGenerator.startNext(),它的实际调用在那3个Generator里面,在方法内部又会获取stage和currentGenerator,当stage == Stage.SOURCE时会跳出循环。如果是第一次从网络加载图片的话,最终数据的加载会交给 SourceGenerator 进行;如果是从磁盘缓存获取的话会根据缓存策略的不同从ResourceCacheGenerator或者DataCacheGenerator获取

先来看一下ResourceCacheGeneratorstartNext()方法,果不其然,里面先是构建了缓存key,然后从DiskLruCache获取到了缓存图片。

#ResourceCacheGenerator
  public boolean startNext() {
    List<Key> sourceIds = helper.getCacheKeys();
    if (sourceIds.isEmpty()) {
      return false;
    }
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
    if (resourceClasses.isEmpty()) {
      if (File.class.equals(helper.getTranscodeClass())) {
        return false;
      }
    }
    while (modelLoaders == null || !hasNextModelLoader()) {
      resourceClassIndex++;
      if (resourceClassIndex >= resourceClasses.size()) {
        sourceIdIndex++;
        if (sourceIdIndex >= sourceIds.size()) {
          return false;
        }
        resourceClassIndex = 0;
      }

      Key sourceId = sourceIds.get(sourceIdIndex);
      Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
      Transformation<?> transformation = helper.getTransformation(resourceClass);
      currentKey =
          new ResourceCacheKey( // 1 构建获取缓存信息的键
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey); // 2 从缓存中获取缓存信息
      if (cacheFile != null) {
        sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); // 3 使用文件方式从缓存中读取缓存数据
      loadData = modelLoader.buildLoadData(cacheFile,
          helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }

    return started;
  }

这里是通过ResourceCacheGenerator获取的缓存图片,其实DataCacheGenerator也是差不多的。到这里磁盘缓存读取就说完了,下面来看一下磁盘缓存的写入。不用想,是第一次从网络加载图片时写入的,第一次从网络加载时,会调用SourceGenerator#startNext()方法:

#SourceGenerator
public boolean startNext() {
    //判断是否有可以用于缓存数据
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      //调用disklrucache缓存
      cacheData(data);
    }

    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;
        //DataFetcher加载数据
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

这里用的是SourceGeneratorstartNext()方法,其它2个Generator的实现是不一样的。先判断是否有可以用于缓存的数据,由于是第一次加载网络图片,所以是没有的,然后通过DataFetcher加载数据,具体来说网络的话是通过HttpUrlFetcher来实现的。

HttpUrlFetcher#loadData()

public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //加载数据获得文件输入流
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //在 SourceGenerator 回调
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

通过HttpURLConnection来获取InputStream,然后在 SourceGenerator 回调。

SourceGenerator #onDataReady ()

#SourceGenerator
public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      //保存图片资源数据
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      cb.reschedule(); //回调
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

这里先是保存数据,然后回调到DecodeJob中, 将会根据当前的stage从 run() 方法开始执行一遍,并再次调用SourceGeneratorstartNext() 方法。这次已经存在可以用于缓存的数据了。所以cacheData()方法将会被触发:

private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      //DiskLrucache保存图片
      helper.getDiskCache().put(originalKey, writer);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

这里通过DiskCache对象,实际上是DiskLruCacheWrapper实现类对象在磁盘缓存图片。

小结
磁盘缓存是在EngineJob中的DecodeJob任务中完成的,依次通过ResourcesCacheGeneratorSourceGeneratorDataCacheGenerator来获取缓存数据。ResourcesCacheGenerator获取的是转换过的缓存数据;SourceGenerator获取的是未经转换的原始的缓存数据;DataCacheGenerator是通过网络获取图片数据再按照按照缓存策略的不同去缓存不同的图片到磁盘上。

到这里,总算写完了整体流程分析。不过一大堆,你基本不太可能记得住,下面就给大家总结一下吧:

总结(干货)

Glide缓存分为弱引用+ LruCache+ DiskLruCache,其中读取数据的顺序是:弱引用 > LruCache > DiskLruCache>网络;写入缓存的顺序是:网络 --> DiskLruCache-->弱引用-->LruCache

内存缓存分为弱引用的和 LruCache ,其中正在使用的图片使用弱引用缓存,暂时不使用的图片用 LruCache缓存,这一点是通过 图片引用计数器(acquired变量)来实现的,详情可以看内存缓存的小结。

磁盘缓存就是通过DiskLruCache实现的,根据缓存策略的不同会获取到不同类型的缓存图片。它的逻辑是:先从转换后的缓存中取;没有的话再从原始的(没有转换过的)缓存中拿数据;再没有的话就从网络加载图片数据,获取到数据之后,再依次缓存到磁盘和弱引用。

抛一个问题给大家思考讨论:
为什么Glide内存缓存要设计2层,弱引用和LruCache?

这是一个朋友的理解 用弱引用缓存的资源都是当前活跃资源 activeRource,资源的使用频率比较高,这个时候如果从LruCache取资源,LinkHashmap查找资源的效率不是很高的。所以他会设计一个弱引用来缓存当前活跃资源,来替Lrucache减压。

参考:
Android Glide4.0 源码遨游记(第五集——缓存机制)
Android Glide4.0 源码遨游记(第四集)
Glide 系列-3:Glide 缓存的实现原理(4.8.0)
Glide 系列-2:主流程源码分析(4.8.0)
[Glide的图片内存优化]
(https://www.jianshu.com/p/12e318e6414f)

有关Android 【手撕Glide】--Glide缓存机制的更多相关文章

  1. ruby - 如何在 Ubuntu 中清除 Ruby Phusion Passenger 的缓存? - 2

    我试过重新启动apache,缓存的页面仍然出现,所以一定有一个文件夹在某个地方。我没有“公共(public)/缓存”,那么我还应该查看哪些其他地方?是否有一个URL标志也可以触发此效果? 最佳答案 您需要触摸一个文件才能清除phusion,例如:touch/webapps/mycook/tmp/restart.txt参见docs 关于ruby-如何在Ubuntu中清除RubyPhusionPassenger的缓存?,我们在StackOverflow上找到一个类似的问题:

  2. ruby-on-rails - Ruby on Rails 计数器缓存错误 - 2

    尝试在我的RoR应用程序中实现计数器缓存列时出现错误Unknownkey(s):counter_cache。我在这个问题中实现了模型关联:Modelassociationquestion这是我的迁移:classAddVideoVotesCountToVideos0Video.reset_column_informationVideo.find(:all).eachdo|p|p.update_attributes:videos_votes_count,p.video_votes.lengthendenddefself.downremove_column:videos,:video_vot

  3. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  4. ruby-on-rails - bundle 安装尝试使用缓存文件 - 2

    当我尝试进行bundle安装时,我的gem_path和gem_home指向/usr/local/rvm/gems/我没有写入权限,并且由于权限无效而失败。因此,我已将两个路径都更改为我具有写入权限的本地目录。这样做时,我进行了bundle安装,我得到:bruno@test6:~$bundleinstallFetchinggemmetadatafromhttps://rubygems.org/.........Fetchinggemmetadatafromhttps://rubygems.org/..Bundler::GemspecError:Couldnotreadgemat/afs/

  5. ruby-on-rails - Heroku Action 缓存似乎不起作用 - 2

    我一直在Heroku上尝试不同的缓存策略,并添加了他们的memcached附加组件,目的是为我的应用程序添加Action缓存。但是,当我在我当前的应用程序上查看Rails.cache.stats时(安装了memcached并使用dalligem),在执行应该缓存的操作后,我得到current和total_items为0。在Controller的顶部,我想缓存我有的Action:caches_action:show此外,我修改了我的环境配置(对于在Heroku上运行的配置)config.cache_store=:dalli_store我是否可以查看其他一些统计数据,看看它是否有效或我做错

  6. ruby-on-rails - rails expire_page 没有删除缓存的文件 - 2

    我有一个具有页面缓存的ControllerAction,我制作了一个清扫程序,它使用Controller和指定的Action调用expire_page...Controller操作呈现一个js.erb模板,所以我试图确保expire_page删除public/javascripts中的.js文件,但它没有这样做。classJavascriptsController"javascripts",:action=>"lol",:format=>'js')endend...所以,我访问javascripts/lol.js并呈现我的模板。我验证了public/javascripts/lol.js

  7. ruby-on-rails - rails 3 缓存 : expire action for named route - 2

    我的Controller有这个:caches_action:render_ticker_for_channel,:expires_in=>30.seconds在我的路由文件中我有这个:match'/render_c_t/:channel_id'=>'render#render_ticker_for_channel',:as=>:render_channel_ticker在日志文件中我看到了这个:Writefragmentviews/mcr3.dev/render_c_t/63(11.6ms)我如何手动使它过期?我需要从与渲染Controller不同的Controller使它过期,但即使

  8. ruby - 更新 gem 时 Docker 包安装缓存问题 - 2

    我在开发和生产中都使用docker,真正困扰我的一件事是docker缓存的简单性。我的ruby​​应用程序需要bundleinstall来安装依赖项,因此我从以下Dockerfile开始:添加GemfileGemfile添加Gemfile.lockGemfile.lock运行bundleinstall--path/root/bundle所有依赖项都被缓存,并且在我添加新gem之前效果很好。即使我添加的gem只有0.5MB,从头开始安装所有应用程序gem仍然需要10-15分钟。由于依赖项文件夹的大小(大约300MB),然后再花10分钟来部署它。我在node_modules和npm上遇到了

  9. ruby-on-rails - 在缓存中找不到 StaleElementReference 错误元素 - 2

    我正在使用Capybara2.1和Ruby1.9.3,使用selenium驱动程序(带有Minitest和测试单元)来测试网络应用程序。我正在努力解决StaleElementReferenceException问题。我已经看到很多关于该主题的讨论,但我无法找到解决我所面临问题的方法。所以基本上,我试图使用以下代码在我的页面上找到所有分页元素:pagination_elements=page.all('.paginationa')然后我对这些元素做一些断言,例如:pagination_elements.first.must_have_content('1')在这些断言之后,我通过单击下一

  10. 最强Http缓存策略之强缓存和协商缓存的详解与应用实例 - 2

    HTTP缓存是指浏览器或者代理服务器将已经请求过的资源保存到本地,以便下次请求时能够直接从缓存中获取资源,从而减少网络请求次数,提高网页的加载速度和用户体验。缓存分为强缓存和协商缓存两种模式。一.强缓存强缓存是指浏览器直接从本地缓存中获取资源,而不需要向web服务器发出网络请求。这是因为浏览器在第一次请求资源时,服务器会在响应头中添加相关缓存的响应头,以表明该资源的缓存策略。常见的强缓存响应头如下所述:Cache-ControlCache-Control响应头是用于控制强制缓存和协商缓存的缓存策略。该响应头中的指令如下:max-age:指定该资源在本地缓存的最长有效时间,以秒为单位。例如:Ca

随机推荐