Glide源码学习笔记四:内存二级缓存

Glide源码学习笔记四:内存二级缓存

前言

上一章Glide源码学习笔记三:into方法做了什么?阅读into(target)方法,构建了有无缩略图的几种情况的Request,来到Engine.load()方法。

Engine

先看看类注解是如何描述的:

/** Responsible for starting loads and managing active and cached resources. */
/** 负责开始下载以及管理内存和缓存资源 */
public class Engine
    implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
        ......
        ......
        }

Engine就是负责加载以及资源控制的核心类,在Glide初始化就已经设置完成。
接着看load方法,代码不长,参数有点多,给出了注释,方便大家理解:

public <R> LoadStatus load(
      GlideContext glideContext,//整个Glide的上下文
      Object model,//图片资源,可能是一个url字符串,也可能是一个resourceId等等
      Key signature,//requestOptions的签名
      int width,//图片宽
      int height,//图片高
      Class<?> resourceClass,//资源类型,RequestManager的asBitmap和asGif额外设置的选项
      Class<R> transcodeClass,//解码类型,如bitmap,gif,drawable
      Priority priority,//加载优先级
      diskCacheStrategy,//缓存策略,缓存的数据有两种,未解码和已解码,对于这两种数据是否缓存的组合策略
      Map<Class<?>, Transformation<?>> transformations,//图片剪裁的转换器,宽高,圆角等等
      boolean isTransformationRequired,//是否转换
      boolean isScaleOnlyOrNoTransform,//设置了fitCenter或CenterInside为true
      Options options,//实现了Key接口,使用CachedHashCodeArrayMap保存若干个Option,而Option是保存缓存质量以及缓存格式的类
      boolean isMemoryCacheable,//是否缓存,默认为true
      boolean useUnlimitedSourceExecutorPool,//是否使用不受限制的缓存池
      boolean useAnimationPool,//是否使用动画池,一般是加载gif图使用
      boolean onlyRetrieveFromCache,//是否只在缓存中索引资源
      ResourceCallback cb,//回调
      Executor callbackExecutor//线程池) {
      //参数过多,不用强行记忆,后续分析都会用到
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
    //复用加载时,内存缓存的key
    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource<?> memoryResource;
    //通过上文的key在内存中检索资源
    synchronized (this) {
    //从内存获取资源
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      if (memoryResource == null) {
      //未获取到,等待已经开始的加载或者开始一个新任务加载
        return waitForExistingOrStartNewJob(
            glideContext,
            model,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            options,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache,
            cb,
            callbackExecutor,
            key,
            startTime);
      }
    }

    // Avoid calling back while holding the engine lock, doing so makes it easier for callers to
    // deadlock.
    //回调onResourceReady,就是在之前分析onSizeReady()未分析的方法
    cb.onResourceReady(
        memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
    return null;
  }

上段代码中,loadFromMemory()方法获取内存缓存,waitForExistingOrStartNewJob()等待已经存在的资源加载完成或者开始新的加载任务,onResourceReady()回调资源已经准备好。
这三个方法就是开始加载后资源获取的逻辑,逐个分析一下:

loadFromMemory()

 @Nullable
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    //如果跳过了内存缓存,直接返回null
    if (!isMemoryCacheable) {
      return null;
    }
	
	//从ActiveResources中检索资源,ActiveResources中保存的是正在使用,也就是被其他对象所持有的资源
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }
	//从LruResourceCache内存缓存中索引资源
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
  }

从代码中可以很明显看出,Glide的内存缓存其实是有两级的,一个是正在使用的资源ActiveResources一个是经常说的使用Lru算法构建的缓存队列,ActiveResources优先级更高(特别强调,v4.4.0版本及之后版本是如此,之前的版本是Lru队列优先级更高)
接下来看看这两个缓存容器:

loadFromActiveResources()

在Enigine的构造方法中有这一行代码:

activeResources = new ActiveResources(isActiveResourceRetentionAllowed)

这里也有一个开关,是否开启正在使用的资源缓存。以下是ActiveResources关键方法,代码做了省略

final class ActiveResources {
  ......
  ......
  //缓存容器,资源文件是弱引用包装的EngineResource,是一个弱引用,是ActiveResources 的内部类
  @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  //资源引用对列
  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
  //设置资源是活动的,也就是被其他对象引用了
  synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }
  ......
  ......
  //设置资源是非活动的
  synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }
  //通过key获取资源
  @Nullable
  synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

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

有一些逻辑还不是很理解,比如如何保证资源的活动属性?最近最少算法如何实现?先不急,看看其他的逻辑。

loadFromCache()

  private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      //这个方法是EngineResource的,++了一个int变量
      cached.acquire();
      //ActiveResources代码中的activate,在这里调用了,赋予了资源的活动属性
      activeResources.activate(key, cached);
    }
    return cached;
  }

  private EngineResource<?> getEngineResourceFromCache(Key key) {
  //获取资源,是从成员变量的MemoryCache中索引找到
    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 {
      //缓存的不是EngineResource类型,就构造一个
      result =
          new EngineResource<>(
              cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }

提出问题

代码逻辑不多,但是有几个重点逻辑需要理解:
1.EngineResource的acquire()方法有什么作用?
2.为什么要在获取到了资源的时候给调用ActiveResources的activate方法?
3.MemoryCache和ActiveResources有什么区别?

解决问题

进入EngineResource类中,代码部分省略:

class EngineResource<Z> implements Resource<Z> {
......
private int acquired;
......
//该方法在两层内存缓存获取资源的方法都有调用,同步方法,当资源被获取引用的时候,acquired+1
 synchronized void acquire() {
 //如果正在回收资源,就不能设置
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
  //自增  
    ++acquired;
  }
  //资源被回收
 @Override
  public synchronized void recycle() {
    //还有对象引用该资源,就不能回收
    if (acquired > 0) {
      throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
    }
    if (isRecycled) {
      throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
    }
    isRecycled = true;
    if (isRecyclable) {
      resource.recycle();
    }
  }
  void release() {
    boolean release = false;
    synchronized (this) {
    //acquired 小于等于0,说明没有被引用,也就无需释放资源
      if (acquired <= 0) {
        throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
      }
      if (--acquired == 0) {
        release = true;
      }
    }
    if (release) {
    //看下一个方法
      listener.onResourceReleased(key, this);
    }
  }
}

/*这里还有一个方法,是Engine的回调*/
@Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    //从activeResources中移除
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
    //加入MemoryCache中
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
    }
  }

回答问题

现在就可以回答问题了:
1.EngineResource的acquire()方法有什么作用?
与其说acquire()有什么作用,不如说EngineResource内部维护了一个int变量acquired,代表着引用次数。这也是ActiveResources是否缓存该资源的唯一标准:acquired(引用次数)大于0
2.为什么要在获取到了资源的时候给调用ActiveResources的activate方法?
在调用activate()之前,EngineResource调用了acquire()方法将引用次数+1,而activate只是为了更新缓存容器中存放的EngineResource的acquired值。
3.MemoryCache和ActiveResources有什么区别与联系?
容器不同:
MemoryCache真正的实现类为LruResourceCache,而LruResourceCache继承自LruCache,内部维护的是一个初始容量为100的LinkedHashMap。
ActiveResources内部是维护一个HashMap,将资源包装弱引用之后存储。
缓存策略不同:
MemoryCache插入的新数据是ActiveResources释放的那个EngineResource。而移除一个数据,是根据Lru算法的策略来的。有兴趣的同学可以自行查找资料。还有就是当获取成功一个资源成功,就添加到ActiveResources中,MemoryCache就没有必要保存它了。
ActiveResources插入数据的节点有两个,一个是根据Key读取ActiveResources没有找到却在MemoryCache中找到了,另外一个是Engine.onEngineJobComplete()回调,该方法还没有阅读过,在后续章节中解答。而删除数据,则是当EngineResource的acquired为0时触发,而该待删除的资源会触发插入MemoryCache中的逻辑,但是不保证成功。

总结

在这里插入图片描述
二级缓存的逻辑并不复杂,需要注意的是ActiveResources与MemoryCache的缓存策略。细节在回答问题中。
若大家有不同意见,欢迎讨论,一起学习一起进步!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值