为了账号安全,请及时绑定邮箱和手机立即绑定

从源码角度深入理解Glide(下)

标签:
Android

Glide缓存

  • Glide加载默认情况下可以分为三级缓存,哪三级呢?他们分别是内存、磁盘和网络。

  • 默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:

    • 1.活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片

    • 2.内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中

    • 3.资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存

    • 4.数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存

  • 网络级别的加载我们已经在上一篇文章了解了,上面列出的前两种情况则是内存缓存,后两种情况则是磁盘缓存,如果以上四种情况都不存在,Glide则会通过返回到原始资源以取回数据(原始文件,Uri, Url(网络)等)

缓存的key

  • 提起缓存,我们首先要明白,Glide中缓存的图片肯定不止一个,当我们加载图片的同时,如果缓存中有我们正在加载的图片,我们怎么找到这个图片的缓存呢?所以为了找到对应的缓存,则每一个缓存都有它对应的标识,这个标识在Glide中用接口Key来描述

/**Key 接口*/public interface Key {
  String STRING_CHARSET_NAME = "UTF-8";
  Charset CHARSET = Charset.forName(STRING_CHARSET_NAME);  void updateDiskCacheKey(@NonNull MessageDigest messageDigest);  @Override
  boolean equals(Object o);  @Override
  int hashCode();
}

缓存Key的生成

  • 前面提到了缓存Key的接口,那这个缓存的Key实在哪里生成的,实现类又是什么呢?这我们就要看到加载发动机Engine类的load方法

private final EngineKeyFactory keyFactory;/**Engine类的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 = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);    //省略部分代码
    ..........
  }/**EngineKey类*/  class EngineKey implements Key {  private final Object model;  private final int width;  private final int height;  private final Class<?> resourceClass;  private final Class<?> transcodeClass;  private final Key signature;  private final Map<Class<?>, Transformation<?>> transformations;  private final Options options;  private int hashCode;

  EngineKey(
      Object model,
      Key signature,      int width,      int height,
      Map<Class<?>, Transformation<?>> transformations,
      Class<?> resourceClass,
      Class<?> transcodeClass,
      Options options) {    this.model = Preconditions.checkNotNull(model);    this.signature = Preconditions.checkNotNull(signature, "Signature must not be null");    this.width = width;    this.height = height;    this.transformations = Preconditions.checkNotNull(transformations);    this.resourceClass =
        Preconditions.checkNotNull(resourceClass, "Resource class must not be null");    this.transcodeClass =
        Preconditions.checkNotNull(transcodeClass, "Transcode class must not be null");    this.options = Preconditions.checkNotNull(options);
  }  @Override
  public boolean equals(Object o) {    if (o instanceof EngineKey) {
      EngineKey other = (EngineKey) o;      return model.equals(other.model)
          && signature.equals(other.signature)
          && height == other.height
          && width == other.width
          && transformations.equals(other.transformations)
          && resourceClass.equals(other.resourceClass)
          && transcodeClass.equals(other.transcodeClass)
          && options.equals(other.options);
    }    return false;
  }  @Override
  public int hashCode() {    if (hashCode == 0) {
      hashCode = model.hashCode();
      hashCode = 31 * hashCode + signature.hashCode();
      hashCode = 31 * hashCode + width;
      hashCode = 31 * hashCode + height;
      hashCode = 31 * hashCode + transformations.hashCode();
      hashCode = 31 * hashCode + resourceClass.hashCode();
      hashCode = 31 * hashCode + transcodeClass.hashCode();
      hashCode = 31 * hashCode + options.hashCode();
    }    return hashCode;
  }
}
  • 由以上源码我们知道,通过EngineKeyFactory的buildKey方法Glide创建了缓存的Key实现类EngineKey对象,由生成EngineKey对象传入的参数我们可以明白,只要有一个参数不同,所生成的EngineKey对象都会是不同的。内存的速度是最快的,理所当然如果内存中有缓存的对应加载图片Glide会搜先从内存缓存中加载。

LRU

LRU算法思想

  • 从官方文档描述,我们可以知道Glide的缓存底层实现原理算法都是LRU(Least Recently Used),字面意思为最近最少使用。算法核心思想(个人理解):在一个有限的集合中,存入缓存,每一个缓存都有唯一标识,当要获取一个缓存,集合中没有则存入,有则直接从集合获取,存入缓存到集合时如果集合已经满了则找到集合中最近最少的缓存删除并存入需要存入的缓存。 这样也就有效的避免了内存溢出(OOM)的问题。

  • 接下来我们看一张图能够更好的理解LRU算法


    webp

    LRU图.jpg

  • 横线上方每个数字代表要存入的数据,横线下方代表三个内存页(也可以理解为缓存结合),缓存集合最多可以存入三个缓存数据,则从1开始依次按照数字代码的缓存读取并存入缓存集合,首先开始时三个页内存是空的,前三个缓存数据不同,依次存入缓存集合,当数字4在内存中并进行缓存时,根据LRU算法思想,则2和3相较于1使用时间间隔更少,所以淘汰1,缓存数据4替换1的位置,接下去同理。

  • Glide内存缓存使用的是LruCache,磁盘缓存使用的DiskLruCache,他们核心思想都是LRU算法,而缓存集合使用的是LinkedHashMap,熟悉集合框架应该都明白LinkedHashMap集成HashMap,并且LinkedHashMap保证了key的唯一性,更符合LRU算法的实现。

  • 更深入的了解LruCache和DiskLruCache可以查看郭霖大神的解析Android DiskLruCache完全解析,硬盘缓存的最佳方案

内存缓存

内存缓存相关API

//跳过内存缓存RequestOptions requestOptions =new RequestOptions().skipMemoryCache(true);
Glide.with(this).load(IMAGE_URL).apply(requestOptions).into(imageView);//Generated API 方式GlideApp.with(this).load(IMAGE_URL).skipMemoryCache(true).into(imageView); 
//清除内存缓存,必须在主线程中调用Glide.get(context).clearMemory();

内存缓存源码分析

  • 内存缓存不需要你进行任何设置,它默认就是开启的,我们再次回到Engine类的load方法

/**Engine类的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) {    //省略部分代码
    ..........
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }      return null;
    }    //省略部分代码
    ..........
  }
活动资源 (Active Resources)
  • 通过以上Engine类load的源码,首先调用loadFromActiveResources方法来从内存中获取缓存

private final ActiveResources activeResources;/**Engine类的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;
  }/**ActiveResources类*/final class ActiveResources {     @VisibleForTesting
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();   //省略部分代码
    ........   @VisibleForTesting
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {    //省略部分代码
    ........
  }
}/**Engine类的onEngineJobComplete方法*/@SuppressWarnings("unchecked")  @Override
  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);
  }/**RequestOptions类的skipMemoryCache方法*/public RequestOptions skipMemoryCache(boolean skip) {    if (isAutoCloneEnabled) {      return clone().skipMemoryCache(true);
    }    this.isCacheable = !skip;
    fields |= IS_CACHEABLE;    return selfOrThrowIfLocked();
  }
  • 通过以上源码, 这里需要分几步来解读,首先如果是第一次加载,肯定没有内存缓存,所以如果第一次加载成功,则在加载成功之后调用了Engine对象的onEngineJobComplete方法,并在该方法中将加载成功的resource通过ActiveResources对象的activate方法保存在其内部维护的弱引用(WeakReference)HashMap中。下次再加载相同的资源,当你设置了skipMemoryCache(true),则表明你不想使用内存缓存,这时候Glide再次加载相同资源的时候则会跳过内存缓存的加载,否则可以从ActiveResources对象中获取,如果内存资源没被回收的话(关于弱引用的一下描述可以看看我以前写的一篇文章Android 学习笔记之图片三级缓存)。如果该弱引用资源被回收了(GC),则下一步就到内存中寻找是否有该资源的缓存。

内存缓存 (Memory cache)
  • 接着回到Engine类的load方法,如果弱引用缓存资源已经被回收,则调用loadFromCache方法在内存缓存中查找缓存资源

/**Engine类的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) {    //省略部分代码
    ..........
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }      return null;
    }    //省略部分代码
    ..........
  }/**Engine类的loadFromCache方法*/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;
  }/**Engine类的getEngineResourceFromCache方法*/private final MemoryCache cache;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;
  }/**GlideBuilder类的build方法*/private  MemoryCache cache;
@NonNull
  Glide build(@NonNull Context context) {    //省略部分代码
    ..........    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }    if (engine == null) {
      engine =          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }    //省略部分代码
    ..........
}    
/**LruResourceCache类的实现继承关系*/    public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache{......}
  • 通过以上源码,在loadFromCache同样也判断了Glide是否设置了skipMemoryCache(true)方法,没有设置则调用getEngineResourceFromCache方法,在该方法中我们可以看到cache对象就是MemoryCache对象,而该对象实际是一个接口,他的实现类是LruResourceCache,该对象我们前面在GlideBuilder的build方法中进行了新建(在第一步with方法中调用了Glide.get方法,在get方法中初始化Glide调用了在GlideBuilder的build方法),这里也就说明Glide的内存缓存还是使用LruCache来实现,这里如果获取到了内存缓存,则获取内容缓存的同时移除该缓存,并在loadFromCache方法中将该资源标记为正在使用同时加入在弱引用中。这样在ListView或者Recyclerview中加载图片则下次加载首先从弱引用Map中获取缓存资源,并且标志当前资源正在使用,可以防止该资源被LRU算法回收掉。

内存缓存写入
  • 前面我们只是分析了如何获取内存缓存,而内存缓存又是在哪里写入的呢?根据前面分析,首先获取在弱引用Map中的缓存资源,而前面我们在分析活动资源(Active Resources)时候已经说过是在onEngineJobComplete放中往弱引用Map存放缓存资源,而
    onEngineJobComplete方法是在哪里调用呢,这我们就要回想起上一篇文章中我们再网络加载图片成功后腰切换在主线程回调来显示图片,也就是EngineJob对象的handleResultOnMainThread方法

/**EngineJob类的handleResultOnMainThread方法*/@Synthetic
  void handleResultOnMainThread() {    //省略部分代码
    ..........
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;    //省略部分代码
    ..........
    engineResource.acquire();
    listener.onEngineJobComplete(this, key, engineResource);
    engineResource.release();    //省略部分代码
    ..........
  }/**EngineJob类的EngineResourceFactory内部类*/  @VisibleForTesting
  static class EngineResourceFactory {    public <R> EngineResource<R> build(Resource<R> resource, boolean isMemoryCacheable) {      return new EngineResource<>(resource, isMemoryCacheable, /*isRecyclable=*/ true);
    }
  }/**Engine类的onEngineJobComplete方法*/@SuppressWarnings("unchecked")  @Override
  public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {    //省略部分代码
    ..........    if (resource != null) {
      resource.setResourceListener(key, this);      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }    //省略部分代码
    ..........
  }
  • 通过以上源码,EngineJob类的handleResultOnMainThread方法首先构建了获取好的包含图片的资源,标记当前资源正在使用,通过listener.onEngineJobComplete回调,而listener就是Engine对象,也就到了Engine类的onEngineJobComplete方法,并在该方法中存入了图片资源到弱引用Map中。

  • 上面我是分析了弱引用资源的缓存存入,接着我们看看内存缓存是在哪里存入的,在次看回handleResultOnMainThread方法,我们看到onEngineJobComplete回调前后分别调用了EngineResource对象的acquire方法和release方法

/**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;
  }/**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);
    }
  }/**Engine类的onResourceReleased方法*/@Override  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    activeResources.deactivate(cacheKey);    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
  • 通过以上源码,其实我们应该能恍然大悟,Glide的内存缓存存入其实就是通过一个acquired变量来进行控制,如果当前弱引用资源不再使用,也就是acquired等于零的时候,则调用回调listener.onResourceReleased(listener就是Engine对象),在onResourceReleased方法中移除了弱引用资源资源,并且没有设置skipMemoryCache(true),则通过cache.put存入内存缓存。

  • 总的来说Glide的内存缓存主要是结合了弱引用和内存来实现的。

Glide内存缓存机制示意图

webp

Glide内存缓存机制示意图.jpg

磁盘缓存

  • 说去磁盘缓存,上一篇文章我们在简单使用Glide的例子中就已经使用了Glide的磁盘缓存

RequestOptions requestOptions = new RequestOptions()
         .diskCacheStrategy(DiskCacheStrategy.NONE);//不使用缓存
     Glide.with(Context).load(IMAGE_URL).apply(requestOptions).into(mImageView);
  • 既然知道如何使用Glide的磁盘缓存,首先我们要了解Glide4中给我提供了哪几种磁盘缓存策略

磁盘缓存策略

  • 1.DiskCacheStrategy.NONE: 表示不使用磁盘缓存

  • 2.DiskCacheStrategy.DATA: 表示磁盘缓存只缓存原始加载的图片

  • DiskCacheStrategy.RESOURCE: 表示磁盘缓存只缓存经过解码转换后的图片

  • DiskCacheStrategy.ALL: 表示磁盘缓存既缓存原始图片,也缓存经过解码转换后的图片

  • DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种磁盘缓存策略,该选项也是我们在不进行手动设置的时候Glide的默认设置

磁盘缓存源码分析

  • 不知你是否还记得上一篇文章中在加载图片的时候我们是在开启子线程任务在线程池中进行的,我们来回顾一下

/**DecodeJob类的start方法*/public void start(DecodeJob<R> decodeJob) {    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }/**DecodeJob类的willDecodeFromCache方法*/ 
 boolean willDecodeFromCache() {
    Stage firstStage = getNextStage(Stage.INITIALIZE);    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
  } 
/**DecodeJob类的getNextStage方法*/   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:        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;     //省略部分代码
    ......
    }
  }/**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;
    }
  };  
/**GlideBuilder类的build方法*/  @NonNull
  Glide build(@NonNull Context context) {    //省略部分代码
    ......    if (diskCacheExecutor == null) {
      diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
    }    //省略部分代码
    ......
}/**GlideExecutor类的newDiskCacheExecutor方法*/ private static final int DEFAULT_DISK_CACHE_EXECUTOR_THREADS = 1;public static GlideExecutor newDiskCacheExecutor() {    return newDiskCacheExecutor(
        DEFAULT_DISK_CACHE_EXECUTOR_THREADS,
        DEFAULT_DISK_CACHE_EXECUTOR_NAME,
        UncaughtThrowableStrategy.DEFAULT);
  }/**GlideExecutor类的newDiskCacheExecutor方法*/ 
 public static GlideExecutor newDiskCacheExecutor(      int threadCount, String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {    return new GlideExecutor(        new ThreadPoolExecutor(
            threadCount /* corePoolSize */,
            threadCount /* maximumPoolSize */,            0 /* keepAliveTime */,
            TimeUnit.MILLISECONDS,            new PriorityBlockingQueue<Runnable>(),            new DefaultThreadFactory(name, uncaughtThrowableStrategy, true)));
  }
  • 通过以上源码,可以分两个步骤来进行解读:

    • 第一步在DecodeJob对象的start方法开启子线程来加载图片,这里使用了线程池,通过willDecodeFromCache方法和getNextStage放结合,主要通过Stage枚举来判断当前使用的缓存策略,而缓存策略的设置则通过DiskCacheStrategy对象的decodeCachedResource和decodeCachedData方法来进行设置,而这两个方法在DiskCacheStrategy抽象类都是抽象方法,而他们的实现就是我们前面提到的Glide磁盘缓存的五种策略,上面代码中列出其中一种ALL代码,decodeCachedResource和decodeCachedData方法都返回ture,也就说明磁盘缓存既缓存原始图片,也缓存经过解码转换后的图片;如果decodeCachedResource返回false和decodeCachedData方法返回true,也就代表DATA策略,磁盘缓存只缓存原始加载的图片,其他同理

    • 第二步通过前面对设置策略的判断,如果有缓存策略,则拿到的线程池就是磁盘缓存加载的线程池(线程池的理解可以看看我以前写的一篇文章),该线程的初始化还是在GlideExecutor对象的build方法中,通过以上源码,该线程池只有唯一一个核心线程,这就保证所有执行的的任务都在这一个线程中执行,并且是顺序执行,也就不用在考虑线程同步的问题了。

  • 根据前面官网的说明,不管是内存缓存还是磁盘缓存,都是使用LRU,接着看看Glide磁盘缓存在哪获取LRU对象,还是得看到GlideBuilder对象的build方法

/**GlideBuilder类的build方法*/  private DiskCache.Factory diskCacheFactory;@NonNull
  Glide build(@NonNull Context context) {    //省略部分代码
    ..........    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }    //省略部分代码
    ..........
}/**InternalCacheDiskCacheFactory类的继承关系*/public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {   //省略实现代码
    ..........  
}/**DiskLruCacheFactory类的部分代码*/public class DiskLruCacheFactory implements DiskCache.Factory {   //省略部分代码
    ..........    @Override
  public DiskCache build() {
    File cacheDir = cacheDirectoryGetter.getCacheDirectory();    //省略部分代码
    ..........    return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
  }
}/**DiskLruCacheWrapper类的部分代码*/public class DiskLruCacheWrapper implements DiskCache {//省略部分代码
    ..........   private synchronized DiskLruCache getDiskCache() throws IOException {    if (diskLruCache == null) {
      diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
    }    return diskLruCache;
  } 
  //省略部分代码
    ..........
}/**DiskLruCache类的部分代码*/public final class DiskLruCache implements Closeable {    //省略部分代码
    ..........    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
      throws IOException {
          DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);          //省略部分代码
          ..........          return cache;
      }      //省略部分代码
      ..........
}
  • 通过以上源码,我们在GlideBuilder对象的build方法中已经新建了InternalCacheDiskCacheFactory对象,也就是DiskLruCacheFactory对象,同样该对象已经被我们传入Engine对象的构造方法中,最终包装成LazyDiskCacheProvider对象(该对象代码就不贴出了),所以只要调用DiskLruCacheFactory对象的build方法就能够最终获取到DiskLruCache对象,该对象是Glide自己实现的,但是其原理和谷歌官方推荐的DiskLruCache也差不了太多,核心还是使用LRU算法来实现磁盘缓存。

资源类型(Resource)
  • 根据前面分分析,假定没有内存缓存,而是由磁盘缓存,则结合前面分析我们得到了磁盘缓存处理的线程池,也获得枚举Stage是RESOURCE_CACHE或DATA_CACHE,则在DecodeJob对象getNextGenerator方法,我们就能得到对应的Generator

/**DecodeJob的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);
    }
  }
  • 通过getNextGenerator方法的源码,如果之前设置磁盘缓存策略为DiskCacheStrategy.RESOURCE,则应该对应的就是枚举Stage.RESOURCE_CACHE,也就是说接下来使用的资源Generator是ResourceCacheGenerator,结合上一篇文章,我们分析网络加载流程是这里获取的是SourceGenerator,我们接着来看ResourceCacheGenerator的startNext()方法

/** ResourceCacheGenerator类的startNext方法*/
  @SuppressWarnings("PMD.CollapsibleIfStatements")  @Override
  public boolean startNext() {     //省略部分代码
      ..........
      currentKey =          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey);      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++);
      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;
  }/**DecodeHelper类的getDiskCache方法*/
 DiskCache getDiskCache() {    return diskCacheProvider.getDiskCache();
  }/** LazyDiskCacheProvider类的getDiskCache方法 */@Override
    public DiskCache getDiskCache() {      if (diskCache == null) {        synchronized (this) {          if (diskCache == null) {
            diskCache = factory.build();
          }          if (diskCache == null) {
            diskCache = new DiskCacheAdapter();
          }
        }
      }      return diskCache;
    }
  • 通过以上源码其实已经很清晰,首先还是获取缓存的唯一key,然后helper.getDiskCache().get(currentKey)这一句话就是获取缓存,helper对象就是DecodeHelper,它的getDiskCache方法获取的对象也就是前面提到的包含DiskLruCacheFactory对象的LazyDiskCacheProvider对象,而LazyDiskCacheProvider对象的getDiskCache方法调用了factory.build(),factory对象DiskLruCacheFactory,也就是获取了我们前面所说的DiskLruCache对象

  • 接着继续看数据返回走的流程还是通过回调通cb.onDataFetcherReady将获取的缓存资源传递到DecodeJob,由DecodeJob继续执行剩余图片显示步骤,大致流程和网络加载差不多,这里就不进行讨论了

/** ResourceCacheGenerator类的startNext方法*/
 @Override
  public void onDataReady(Object data) {    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,        currentKey);
  }
数据来源 (Data)
  • 同理资源类型(Resource),则设置磁盘缓存策略为DiskCacheStrategy.DATA,则应该对应的就是枚举Stage.DATA_CACHE,使用的资源Generator是DataCacheGenerator,所以直接看看DataCacheGenerator的startNext()方法,该方法源码如下,同样是根据key通过DiskLruCache对象来获取磁盘缓存(DATA),数据返回走的流程还是通过回调通cb.onDataFetcherReady将获取的缓存资源传递到DecodeJob,由DecodeJob继续执行剩余图片显示

/** DataCacheGenerator类的startNext方法*/
 @Override
  public boolean startNext() {      //省略部分代码
      ..........
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);      if (cacheFile != null) {        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;    boolean started = false;    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      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;
  }/** DataCacheGenerator类的onDataReady方法*/  @Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
  }
磁盘缓存数据存入
  • 前面我们只是了解了磁盘缓存的获取,磁盘缓存又是在哪里存入的,接着往下看。

  • 根据上一篇文章的分析,加载图片会走到DecodeJob对象的decodeFromRetrievedData方法

/** DecodeJob类的decodeFromRetrievedData方法*/  private void decodeFromRetrievedData() {    //省略部分代码
      ..........
    notifyEncodeAndRelease(resource, currentDataSource);    //省略部分代码
      ..........
} 
/** DecodeJob类的notifyEncodeAndRelease方法*/  private final DeferredEncodeManager<?> deferredEncodeManager = new DeferredEncodeManager<>();private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {//省略部分代码
      ..........
    stage = Stage.ENCODE;    try {      if (deferredEncodeManager.hasResourceToEncode()) {
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } 
    //省略部分代码
    ..........
  }/** DeferredEncodeManager类的encode方法**/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();
      }
    }
  • 通过以上源码可以看到DecodeJob对象的decodeFromRetrievedData方法通过调用notifyEncodeAndRelease方法,在该方法中调用了内部类DeferredEncodeManager的encode方法存入了磁盘缓存,这里存入的是转换后的磁盘缓存(Resource)。

  • 原始数据也就是SourceGenerator第一次网络下载成功之后获取的图片数据,之后再做磁盘缓存,所以再次回到看到SourceGenerator的onDataReady方法

/**SourceGenerator类的onDataReady方法**/ private Object dataToCache;@Override
  public void onDataReady(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);
    }
  }  
/**SourceGenerator类的startNext方法**/@Override
  public boolean startNext() {    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {      return true;
    }   //省略部分代码
  ..........
  }/**SourceGenerator类的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());
      helper.getDiskCache().put(originalKey, writer);
    } 
    //省略部分代码
    ..........
    sourceCacheGenerator =        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }/**DecodeJob类的reschedule方法**/ @Override
  public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }/**Engine类的reschedule方法**/   
 @Override
  public void reschedule(DecodeJob<?> job) {
    getActiveSourceExecutor().execute(job);
  }
  • 通过以上源码,其实逻辑已经很清晰,会让你有“柳暗花明又一村”的感觉,onDataReady网络请求成功并且设置了缓存策略,则将图片资源赋值给Object类型的dataToCache,执行回调cb.reschedule,cb就是DecodeJob对象,所以接着执行了DecodeJob对象的reschedule方法,该方法再次执行回调也就是执行了Engine对象的reschedule方法,该方法再次执行DecodeJob,也就会再次触发SourceGenerator类的startNext方法,该方法首先判断了Object类型的dataToCache是否有值,前面分析该对象已经赋值,所以就进入到SourceGenerator对象的cacheData方法存入了我们的原始下载图片的缓存。

仅从缓存加载图片

  • 前面我基本把Glide的缓存模块梳理了一遍,但是还差个东西,那就是如果我只想Glide加载缓存呢?这种需求还是有的,比如说我们在有些应用看到的省流量模式,不就是正好对应这个需求,没关系Gldie也已经为我们考虑到了,那就是onlyRetrieveFromCache(true),只要设置了这个,图片在内存缓存或在磁盘缓存中就会被加载出来,而没有缓存,则这一次加载失败。

  • 我们看看如何使用

RequestOptions requestOptions = new RequestOptions().onlyRetrieveFromCache(true);
Glide.with(this).load(IMAGE_URL).apply(requestOptions).into(mImageView);//Generated API 方式        GlideApp.with(this)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.ALL)
  .into(mImageView);
  • 使用起来还是很方便的,只要设置onlyRetrieveFromCache(true)方法就行,而它的原理也其实也很简单,我们再次回到DecodeJob对象的getNextStage方法,如果前面获取了缓存,则相应得到对应的Generator加载图片,如果获取不到缓存,则枚举Stage.FINISHED,DecodeJob对象的getNextGenerator方法则会返回null。(如下代码所示)

/**DecodeJob类的getNextStage方法**/ private Stage getNextStage(Stage current) {    switch (current) {    //省略部分代码
    ..........      case DATA_CACHE:        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;      case SOURCE:      case FINISHED:        return Stage.FINISHED;
    }
  }/**DecodeJob类的getNextGenerator方法**/   private DataFetcherGenerator getNextGenerator() {    switch (stage) {    //省略部分代码
    ..........      case FINISHED:        return null;      default:        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }
Glide磁盘缓存机制示意图

webp

Glide磁盘缓存机制示意图.jpg

Glide缓存小结

  • 通过前面对Glide缓存的分析,让我再次认识到Glide的强大,使用时只是简单的几个方法设置或者不设置,Glide都能够在背后依靠其复杂的逻辑为我们快速的加载出图片并显示,缓存还有一些细节比如可以自定义key等,这里就不进行展开了,有兴趣的可以自行研究。

Glide 回调与监听

图片加载成功回调原理

  • 由上一篇文章分析,我们来回顾一下图片加载成功之后的逻辑。数据加载成功之后切换主线程最终调用SingleRequest类的onResourceReady方法,在该方法中加载成功的数据通过target.onResourceReady方法将数据加载出来,target就是DrawableImageViewTarget对象,他继续了实现了Target接口的基类ImageViewTarget,所以调用它实现的onResourceReady方法或者父类实现的onResourceReady方法就实现了加载成功数据的回调,并由DrawableImageViewTarget对象显示加载成功的图片,这就是数据加载成功回调原理。

/**SingleRequest类的onResourceReady方法**/    @Nullable private List<RequestListener<R>> requestListeners;private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {    //省略部分代码
    ..........
    isCallingCallbacks = true;    try {      boolean anyListenerHandledUpdatingTarget = false;      //省略部分代码
     ..........      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
    } 
    //省略部分代码
    ..........
  }/**Target 接口**/      public interface Target<R> extends LifecycleListener {}/**ImageViewTarget类的onResourceReady方法**/ @Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {    if (transition == null || !transition.transition(resource, this)) {
      setResourceInternal(resource);
    } else {
      maybeUpdateAnimatable(resource);
    }
  }/**ImageViewTarget类的setResourceInternal方法**/ private void setResourceInternal(@Nullable Z resource) {
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }
DrawableImageViewTarget/**DrawableImageViewTarget类的setResource方法**/ 
 @Override
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }

Glide监听(listener)

  • 再次看看Glide监听(listener)的例子

Glide.with(this).load(IMAGE_URL).listener(new RequestListener<Drawable>() {           @Override
           public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {               return false;
           }           @Override
           public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {               return false;
           }
       }).into(mImageView);
  • Glide监听的实现同样还是基于我们上面分析的SingleRequest对象的onResourceReady方法,使用的时候调用RequestBuilder对象的listener方法,传入的RequestListener对象加入到requestListeners,这样在SingleRequest对象的onResourceReady方法中遍历requestListeners,来回调listener.onResourceReady方法,布尔类型的anyListenerHandledUpdatingTarget则接收回调listener.onResourceReady方法的返回值,如果返回true,则不会执会往下执行,则接着的into方法就不会被触发,说明我们自己在监听中处理,返回false则不拦截。

/**RequestBuilder类的listener方法**/ @Nullable private List<RequestListener<TranscodeType>> requestListeners;  public RequestBuilder<TranscodeType> listener(
      @Nullable RequestListener<TranscodeType> requestListener) {    this.requestListeners = null;    return addListener(requestListener);
  }/**RequestBuilder类的addListener方法**/  
  public RequestBuilder<TranscodeType> addListener(
      @Nullable RequestListener<TranscodeType> requestListener) {    if (requestListener != null) {      if (this.requestListeners == null) {        this.requestListeners = new ArrayList<>();
      }      this.requestListeners.add(requestListener);
    }    return this;
  }/**SingleRequest类的onResourceReady方法**/    @Nullable private List<RequestListener<R>> requestListeners;private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {    //省略部分代码
    ..........
    isCallingCallbacks = true;    try {      boolean anyListenerHandledUpdatingTarget = false;      if (requestListeners != null) {        for (RequestListener<R> listener : requestListeners) {
          anyListenerHandledUpdatingTarget |=
              listener.onResourceReady(result, model, target, dataSource, isFirstResource);
        }
      }
      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
    } 
    //省略部分代码
    ..........
  }

Target(目标)

  • Target在Glide中相当于中间人的作用,在图片的展示起到承上启下的功效,首先看看Target接口的继承关系图


    webp



作者:maoqitian
链接:https://www.jianshu.com/p/d15c33721d8d


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消