Android 图片加载框架 Glide 4.9.0 (二) 从源码的角度分析 Glide 缓存策略

前言

因为以前项目搭建的是 MVP 架构,由RxJava + Glide + OKHttp + Retrofit 等开源框架组合而成,以前也都是停留在使用层面上,没有深刻的研究,最近打算把它们所有攻下,尚未关注的同窗能够先关注一波,看完这个系列文章,(不论是面试仍是工做中处理问题)相信你都在知道原理的状况下,处理问题更加驾轻就熟。java

Android 图片加载框架 Glide 4.9.0 (一) 从源码的角度分析 Glide 执行流程面试

Android 图片加载框架 Glide 4.9.0 (二) 从源码的角度分析 Glide 缓存策略算法

从源码的角度分析 Rxjava2 的基本执行流程、线程切换原理缓存

从源码的角度分析 OKHttp3 (一) 同步、异步执行流程性能优化

从源码的角度分析 OKHttp3 (二) 拦截器的魅力网络

从源码的角度分析 OKHttp3 (三) 缓存策略架构

从源码的角度分析 Retrofit 网络请求,包含 RxJava + Retrofit + OKhttp 网络请求执行流程app

介绍

在上一篇中,咱们知道了 Glide 框架的最基本的执行流程,那么只知道基本执行流程,这显然是不够的,咱们要深挖 Glide 框架的细节处理原理,好比缓存机制,图片处理等,这一篇咱们就一块儿去探索 Glide 的缓存机制。框架

Glide 缓存机制能够说是设计的很是完美,考虑的很是周全,下面就以一张表格来讲明下 Glide 缓存。异步

缓存类型 缓存表明 说明
活动缓存 ActiveResources 若是当前对应的图片资源是从内存缓存中获取的,那么会将这个图片存储到活动资源中。
内存缓存 LruResourceCache 图片解析完成并最近被加载过,则放入内存中
磁盘缓存-资源类型 DiskLruCacheWrapper 被解码后的图片写入磁盘文件中
磁盘缓存-原始数据 DiskLruCacheWrapper 网络请求成功后将原始数据在磁盘中缓存

若是对 Glide 执行流程不明白的能够先看 Android 图片加载框架 Glide 4.9.0 (一) 从源码的角度分析一次最简单的执行流程

在介绍缓存原理以前,先来看一张加载缓存执行顺序,先有个印象。

uZ8gmR.png

缓存 key 生成

不论是内存缓存仍是磁盘缓存,存储的时候确定须要一个惟一 key 值,那么 Glide cache key 是怎么生成的?经过上一篇源码加载流程介绍,咱们知道在 Engine 的 load 函数中进行对 key 的生成。下面咱们就经过代码来看一下。

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
      
      ...
        
public synchronized <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, Executor callbackExecutor) {
       ....
     //1. 生成缓存惟一 key 值,model 就是图片地址
     EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);  
      
       ....
        
      }
      ...     
    }

	//生成 key
  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);
  }
复制代码
class EngineKey implements Key {
 ...
 
   @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;
  }
   
 ...
  
}
复制代码

根据注释和代码能够看到传入的参数之多,主要是根据 url ,签名,宽高等,其内部重写了 hashCode,equals,来保证对象的惟一性。

内存缓存

经过下面代码开启内存缓存,固然 Glide 默认是为咱们开启了内存缓存因此不须要咱们调用 skipMemoryCache

//在 BaseRequestOptions 成员变量中默认为内存缓存开启。
private boolean isCacheable = true;

//调用层调用
Glide.
      with(MainActivity.this.getApplication()).
      //开启使用内存缓存
      skipMemoryCache(true).
      into(imageView);
复制代码

如今缓存 key 有了以后,就能够根据 key 拿到对应的缓存了,经过文章开始的介绍,咱们知道先加载活动缓存,若是活动缓存没有在加载内存缓存,先看下代码;

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
      
      ...
        
public synchronized <R> LoadStatus load( ....//参数 ) {
       ....
     //1. 生成缓存惟一 key 值,model 就是图片地址
     EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);  
      
     //2. 优先加载内存中的活动缓存 - ActiveResources
    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;
    }
		
   	//3. 若是活动缓存中没有,就加载 LRU 内存缓存中的资源数据。
    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;
    }
        
      ...     
      
    }
复制代码

Glide 为何会设计 2 个内存缓存

不知道你们经过上面代码跟注释(2,3 ) , 有没有发现为何 Glide 会弄 2 个内存缓存(一个 Map + 弱引用,一个 LRU 内存缓存),你们有没有想过为何?在看 郭霖大神对 Glide 缓存机制分析 中说道, ActiveResources 就是一个弱引用的 HashMap ,用来缓存正在使用中的图片,使用 ActiveResources 来缓存正在使用中的图片,能够保护这些图片不会被 LruCache 算法回收掉 ,起初当我看见这句话的时候,我是真的很不理解,由于 Lru 是最近最少时候才会回收尾端数据,那么这里的 ActiveResources 来缓存正在使用中的图片,能够保护这些图片不会被 LruCache 算法回收掉 ,我是越想越以为矛盾,后来中午吃饭的时候,灵光一闪忽然想到了一种状况,我也不知道是否是这样,先看下面一张图。

uZdRJI.png

详细举例说明: 好比咱们 Lru 内存缓存 size 设置装 99 张图片,在滑动 RecycleView 的时候,若是刚刚滑动到 100 张,那么就会回收掉咱们已经加载出来的第一张,这个时候若是返回滑动到第一张,会从新判断是否有内存缓存,若是没有就会从新开一个 Request 请求,很明显这里若是清理掉了第一张图片并非咱们要的效果。因此在从内存缓存中拿到资源数据的时候就主动添加到活动资源中,而且清理掉内存缓存中的资源。这么作很显然好处是 保护不想被回收掉的图片不被 LruCache 算法回收掉,充分利用了资源。 我也不知道这样理解是否正确,但愿若是有其它的含义,麻烦告知一下,谢谢 !

上面咱们说到了 Glide 为何会设计 2 个内存缓存,下面咱们就对这 2 个缓存的 存/取/删 来具体说明下。

ActiveResources 活动资源

获取活动资源

经过以前的介绍咱们知道,活动缓存是在Engine load中获取

public synchronized <R> LoadStatus load( ....//参数 ) {
      
     //1. 优先加载内存中的活动缓存 - ActiveResources
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      // 若是找到获取资源,就返回上层
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      return null;
    }       
      ...     
      
    }


  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
 		// 经过 活动资源的 get 函数拿到活动资源的缓存
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      //正在使用,引用计数 +1
      active.acquire();
    }

    return active;
  }

复制代码

继续看 get 的具体实现

final class ActiveResources {
 
  //
  @VisibleForTesting
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
  
  ...

  //外部调用 get 函数拿到活动资源缓存
  @Nullable
  synchronized EngineResource<?> get(Key key) {
    //经过 HashMap + WeakReference 的存储结构
    //经过 HashMap 的 get 函数拿到活动缓存
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }

  //继承 WeakReference 弱引用,避免内存泄漏。
    @VisibleForTesting
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
  ....
}

复制代码

经过上面代码咱们知道活动缓存是 Map + WeakReference 来进行维护的,这样作的好处是避免图片资源内存泄漏。

存储活动资源

经过文章开头表格中提到,活动资源是加载内存资源以后存储的,那么咱们就来看下什么时候加载内存资源,看下面代码。

仍是在Engine load函数中

//1. 若是活动缓存中没有,就加载 LRU 内存缓存中的资源数据。
    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;
    }

	// 加载内存中图片资源
  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
		//2. 拿到内存缓存,内部将当前的 key 缓存 remove 了
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      // 3. 若是内存缓存不为空,则引用计数器 +1
      cached.acquire();
      //4. 添加进活动缓存中
      activeResources.activate(key, cached);
    }
    return cached;
  }
复制代码

经过注释 4 ,咱们知道在拿到内存缓存的时候,先将内存缓存的当前 key 删除了,而后添加到活动缓存中。

清理活动资源

经过上一篇 Android 图片加载框架 Glide 4.9.0 (一) 从源码的角度分析一次最简单的执行流程 中得知,咱们是在EngineJob中发出的通知回调,告知Engine onResourceReleased来删除 活动资源。下面咱们在回顾一下执行流程,那么咱们就从 EngineJob 发出通知开始看吧:

class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
      
      ....
      
@Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    synchronized (this) {
      this.resource = resource;
      this.dataSource = dataSource;
    }
    notifyCallbacksOfResult();
  }


  @Synthetic
  void notifyCallbacksOfResult() {
 		 .....

    //回调上层 Engine 任务完成了
    listener.onEngineJobComplete(this, localKey, localResource);

    //遍历资源回调给 ImageViewTarget ,并显示
    for (final ResourceCallbackAndExecutor entry : copy) {
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
    //这里是通知发出上层删除活动资源数据
    decrementPendingCallbacks();
  }
      
      
 ....
   
    }

	//重要的就是这里了
  @Synthetic
  synchronized void decrementPendingCallbacks() {
    if (decremented == 0) {
      if (engineResource != null) {
        		//若是不为空,那么就调用内部 release 函数
        		engineResource.release();
      }
    }
  }

复制代码

看一下 EngineResource 的 release 函数

class EngineResource<Z> implements Resource<Z> {
  ...
    
    
    void release() {
    synchronized (listener) {
      synchronized (this) {
        if (acquired <= 0) {
          throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        //这里每次调用一次 release 内部引用计数法就会减一,到没有引用也就是为 0 的时候 ,就会通知上层
        if (--acquired == 0) {
          //回调出去,Engine 来接收
          listener.onResourceReleased(key, this);
        }
      }
    }
  }
  
  ...
  
}
复制代码

根据注释,咱们知道这里用了引用计数法,有点像 GC 回收的 引用计数法的影子。也就是说,当彻底没有使用这样图片的时候,就会把活动资源清理掉,接着往下看,会调用 Engine 的 onResourceReleased 函数。

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
      
   。。。
   
  //接收来自 EngineResource 的调用回调
  @Override
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
   //1. 收到当前图片没有引用,清理图片资源
    activeResources.deactivate(cacheKey);
   //2. 若是开启了内存缓存
    if (resource.isCacheable()) {
      //3. 将缓存存储到内存缓存中。
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
  。。。
    }
    
复制代码

经过上面注释 1 能够知道,这里首先会将缓存图片从 activeResources 中移除,而后再将它 put 到 LruResourceCache 内存缓存当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用 LruCache 来进行缓存的功能,设计的真的很巧妙。

LruResourceCache 内存资源

获取内存资源

不知道有没有小伙伴注意到,其实在讲活动资源存储的时候已经涉及到了内存缓存的存储,下面咱们在来看一下,具体代码以下:

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
      
    ...//忽略一些成员变量跟构造函数
     public synchronized <R> LoadStatus load( ...//忽略参数 ) {   
    
    ....
		//1. 加载内存缓存
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      //若是内存缓存中有,就通知上层,最后在 SingleRequest 接收
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
      
    }
      
    ...
 }

  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
		//2. 经过 getEngineResourceFromCache 获取内存资源
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {//若是内存资源存在
      //则引用计数 +1
      cached.acquire();
      //3. 将内存资源存入活动资源
      activeResources.activate(key, cached);
    }
    return cached;
  }


  private EngineResource<?> getEngineResourceFromCache(Key key) {
    //经过 Engine load 中传参可知,cache 就是 Lru 内存资源缓存
    //2.1 这里是经过 remove 删除来拿到缓存资源
    Resource<?> cached = cache.remove(key);
    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      result = (EngineResource<?>) cached;
    } else {
      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }
复制代码

经过注释1得知,这里是开始加载内存缓存中的资源;

经过注释2.1 可知,这里经过 Lru 内存缓存的 remove 来拿到内存缓存

最后将内存存储到活动缓存中,并缓存当前找到的内存缓存。

这里的活动缓存跟内存缓存紧密联系在一块儿,环环相扣,就像以前咱们的疑问,为何 Glide 会设计 2 个内存缓存的缘由了。

存储内存资源

经过 EngineResource 的引用计数法机制 release 函数,只要没有引用那么就回调,请看下面代码:

class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
      
      ....
      
@Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    synchronized (this) {
      this.resource = resource;
      this.dataSource = dataSource;
    }
    notifyCallbacksOfResult();
  }


  @Synthetic
  void notifyCallbacksOfResult() {
 		 .....

    //这里是通知发出上层删除活动资源数据
    decrementPendingCallbacks();
  }  
      
 ....
   
    }

	//重要的就是这里了
  @Synthetic
  synchronized void decrementPendingCallbacks() {
    if (decremented == 0) {
      if (engineResource != null) {
        		//若是不为空,那么就调用内部 release 函数
        		engineResource.release();
      }
    }
  }

  void release() {
    synchronized (listener) {
      synchronized (this) {
        //引用计数为 0 的时候,回调。
        if (--acquired == 0) {
          listener.onResourceReleased(key, this);
        }
      }
    }
  }

复制代码

最后会回调到 Engine 的 onResourceReleased 函数:

@Override
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    //1. 清理活动缓存
    activeResources.deactivate(cacheKey);
    //若是开启了内存缓存
    if (resource.isCacheable()) {
      //2. 清理出来的活动资源,添加进内存缓存
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
复制代码

到了这里咱们知道,在清理活动缓存的时候添加进了内存缓存。

清理内存缓存

这里的清理是在获取内存缓存的时候经过 remove 清理掉的,详细能够看内存缓存获取的方式。

内存缓存小结

经过上面分析可知,内存缓存有活动缓存和内存资源缓存,下面看一个图来总结下它们相互之间是怎么配合交换数据的。

内存缓存加载机制

总结下步骤:

  1. 第一次加载没有获取到活动缓存。
  2. 接着加载内存资源缓存,先清理掉内存缓存,在添加进行活动缓存。
  3. 第二次加载活动缓存已经存在。
  4. 当前图片引用为 0 的时候,清理活动资源,而且添加进内存资源。
  5. 又回到了第一步,而后就这样环环相扣。

磁盘缓存

在介绍磁盘缓存以前先看一张表格

缓存表示 说明
DiskCacheStrategy.NONE 表示不开启磁盘缓存
DiskCacheStrategy.RESOURCE 表示只缓存转换以后的图片。
DiskCacheStrategy.ALL 表示既缓存原始图片,也缓存转换事后的图片。
DiskCacheStrategy.DATA 表示只缓存原始图片
DiskCacheStrategy.AUTOMATIC 根据数据源自动选择磁盘缓存策略(默认选择)

上面这 4 中参数其实很好理解,这里有一个概念须要记住,就是当咱们使用 Glide 去加载一张图片的时候,Glide 默认并不会将原始图片展现出来,而是会对图片进行压缩和转换,总之就是通过种种一系列操做以后获得的图片,就叫转换事后的图片。而 Glide 默认状况下在硬盘缓存的就是 DiskCacheStrategy.AUTOMATIC

如下面的代码来开启磁盘缓存:

Glide.
      with(MainActivity.this.getApplication()).
      //使用磁盘资源缓存功能
      diskCacheStrategy(DiskCacheStrategy.RESOURCE).
      into(imageView);
复制代码

知道了怎么开启,下面咱们就来看一下磁盘缓存的的加载与存储。

这 2 个加载流程几乎如出一辙,只是加载的数据源不一样,下面咱们具体来看一下

DiskCacheStrategy.RESOURCE 资源类型

获取资源数据

经过上一篇Glide 加载流程 咱们知道,若是在活动缓存、内存缓存中没有找数据,那么就从新开启一个 GlideExecutor 线程池在 DecodeJob run 执行新的请求,下面咱们就直接来看 DecodeJob run 函数,跟着它去找 资源数据的加载:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<?>>, Poolable {
      
   ...
    
   @Override
  public void run() {
     ...
    try {
      //若是取消就通知加载失败
      if (isCancelled) {
        notifyFailed();
        return;
      }
      //1. 执行runWrapped
      runWrapped();
    } catch (CallbackException e) {
    ...
    }
  }
   ... 
 }

  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        //2. 找到执行的状态
        stage = getNextStage(Stage.INITIALIZE);
        //3. 找到具体执行器
        currentGenerator = getNextGenerator();
        //4. 开始执行
        runGenerators();
        break;
     ...
    }
  }
  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE: //3.1解码后的资源执行器
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE://原始数据执行器
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE://新的请求,http 执行器
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }
复制代码

经过上面分析的代码跟注释,咱们知道这里是在找具体的执行器,找完了以后注释 4 开始执行,如今咱们直接看注释 4。先提一下 注释 3.1 由于外部咱们配置的是 RESOURCE 磁盘资源缓存策略,因此直接找到的是 ResourceCacheGenerator 执行器。

private void runGenerators() {
    //若是当前任务没有取消,执行器不为空,那么就执行 currentGenerator.startNext() 函数
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();
      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
	..
  }
复制代码

经过上面代码可知,主要是执行 currentGenerator.startNext() 就句代码,currentGerator 是一个接口,经过注释 3.1 咱们知道这里它的实现类是 ResourceCacheGenerator ,那么咱们具体看下 ResourceCacheGenerator 的 startNext 函数;

class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {
      
    ...
      
      @Override
  public boolean startNext() {

    ...
      
    while (modelLoaders == null || !hasNextModelLoader()) {
      resourceClassIndex++;
  
      ...
      //1. 拿到资源缓存 key
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      //2. 经过 key 获取到资源缓存
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

		loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //3. 获取一个数据加载器
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      //3.1 为资源缓存文件,构建一个加载器,这是构建出来的是 ByteBufferFileLoader 的内部类 ByteBufferFetcher
      loadData = modelLoader.buildLoadData(cacheFile,
          helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        //3.2 利用 ByteBufferFetcher 加载,最后把结果会经过回调给 DecodeJob 的 onDataFetcherReady 函数
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
      
    ...
      
      
    }
复制代码

经过上面注释能够获得几点信息

  1. 首先根据 资源 ID 等一些信息拿到资源缓存 Key
  2. 经过 key 拿到缓存文件
  3. 构建一个 ByteBufferFetcher 加载缓存文件
  4. 加载完成以后回调到 DecodeJob 中。

存储资源数据

先来看下面一段代码:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<?>>, Poolable {
    
    ...
      
      
     private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
   
      ....

    stage = Stage.ENCODE;
    try {
      //1. 是否能够将转换后的图片缓存
      if (deferredEncodeManager.hasResourceToEncode()) {
        //1.1 缓存入口
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
		...
    }
    onEncodeComplete();
  }     
    }
		
    void encode(DiskCacheProvider diskCacheProvider, Options options) {
      GlideTrace.beginSection("DecodeJob.encode");
      try {
        //1.2 将 Bitmap 缓存到资源磁盘
        diskCacheProvider.getDiskCache().put(key,
            new DataCacheWriter<>(encoder, toEncode, options));
      } finally {
        toEncode.unlock();
        GlideTrace.endSection();
      }
    }
复制代码

经过上面咱们知道 http 请求到图片输入流以后通过一系列处理,转换获得目标 Bitmap 资源,最后经过回调到 DecodeJob 进行缓存起来。

清理资源缓存

  1. 用户主动经过系统来清理
  2. 卸载软件
  3. 调用 DisCache.clear();

DiskCacheStrategy.DATA 原始数据类型

获取原始数据

参考上小节DiskCacheStrategy.RESOURCE 获取资源,不一样的是把 ResourceCacheGenerator 换成 DataCacheGenerator 加载了。

存储原始数据

这里既然存的是原始数据那么咱们直接从 http 请求以后的响应数据开始查看,经过上一篇咱们知道是在 HttpUrlFetcher 中请求网络,直接定位到目的地:

public class HttpUrlFetcher implements DataFetcher<InputStream> {
  
    @Override
  public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //1. 经过 loadDataWithRedirects 来进行http 请求,返回 InputStream
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //2. 将请求以后的数据返回出去
      callback.onDataReady(result);
    } catch (IOException e) {
		  ...
    } finally {
			...
    }
  }
}
复制代码

根据注释能够得知,这里主要用于网络请求,请求响应数据回调给 MultiModelLoader 中。咱们看下 它具体实现:

class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {  
  ...
@Override
    public void onDataReady(@Nullable Data data) {
    //若是数据不为空,那么就回调给 SourceGenerator
      if (data != null) {
        callback.onDataReady(data);
      } else {
        startNextOrFail();
      }
    }
 .... 
}
复制代码

这里的 callback 指的是 SourceGenerator ,继续跟

class SourceGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object>, DataFetcherGenerator.FetcherReadyCallback {
    ....
      
    
      
      @Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      //1. 收到网络下载好的图片原始数据,赋值给成员变量 dataToCache
      dataToCache = data;
      //2. 交给 EngineJob 
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }      
   ....       
    }
复制代码

经过上面注释能够知道 cb.reschedule(); 最后回调到 EngineJob 类,会执行 reschedule(DecodeJob<?> job) 函数的 getActiveSourceExecutor().execute(job); 用线程池执行任务,最后又回到了 DecodeJob 的 run 函数 拿到执行器DataCacheGenerator ,最终会在 SourceGenerator 的 startNext() 函数,以前流程代码我就不贴了,上面讲了不少次了,相信你们应该记得了,咱们直接看 startNext() 函数吧:

class SourceGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object>, DataFetcherGenerator.FetcherReadyCallback {

  /**这个临时的变量就是 http 请求回来的图片原始数据 */
  private Object dataToCache;


@Override
  public boolean startNext() {
   ....
     
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      //放入缓存
      cacheData(data);
    }
    
    ...
    }
    return started;
  }


  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());
      //存储原始数据
      //经过 StreamEncoder encode 写入文件
      helper.getDiskCache().put(originalKey, writer);
    
    } finally {
      loadData.fetcher.cleanup();
    }

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

经过上面代码得知,这里将原始数据写入文件中了。

清理资源缓存

  1. 用户主动经过系统来清理
  2. 卸载软件
  3. 调用 DisCache.clear();

磁盘缓存小节

存储

  1. 资源缓存是在把图片转换完以后才缓存;
  2. 原始数据是网络请求成功以后就写入缓存;

获取

  1. 资源缓存跟原始数据都是在 GlideExecutor 线程池中,Decodejob 中检查获取数据。

这里咱们看一张流程图吧

复用池

Glide 中复用池也起了一个很大的做用,这里我就不贴代码了,由于这个很好理解,你们能够去 Glide 中的 Downsample 详细了解。在这里我就简单说一下 Glide 中复用池的处理。

在 Glide 中,在每次解析一张图片为 Bitmap 的时候不论是内存缓存仍是磁盘缓存,都会从其BitmapPool 中查找一个可被复用的 Bitmap ,以后在将此块的内存缓存起来。

注意:在使用复用池的时候,若是存在能被复用的图片会重复使用该图片的内存。 因此复用并不能减小程序正在使用的内存大小。Bitmap 复用,解决的是减小频繁申请内存带来的性能(抖动、碎片)问题。

总结

能够看到 Glide 在性能优化方面可谓是达到了极致,不光设计了多级复杂的缓存策略,就连开销较大的 Bitmap 内存也利用了复用池进行了管理,因此就算用户在没有开启全部缓存的状况下, Bitmap 也保证了内存的合理使用,避免 OOM。尽量的减小了对象的建立开销,保证了 Glide 加载的流畅性。

到这里 Glide 4.9.0 版本的缓存机制也已经讲完了,相信在看完以后,你将对 Glide 缓存机制有了必定了解。

感谢你的阅读,文章中若有误,还请指出,谢谢!

参考

  • [郭霖大神的 Glide 源码分析
相关文章
相关标签/搜索