Glide缓存机制

Glide缓存机制

Glide缓存机制简介

  • Glide的缓存分为两种,一种是内存缓存,一种是硬盘缓存,其中内存缓存包含弱引用缓存HashMap和LruCache,硬盘缓存就是DiskLruCache。获取缓存的顺序是先去弱引用缓存中寻找,没有再去LruCache中寻找,内存缓存中没有则取硬盘缓存中寻找,如果内存缓存和硬盘缓存中都没有该图片,则再通过HttpUrlConnection新建网络请求去获取图片资源。

内存缓存的取资源

在上一篇博客的介绍中,我们知道Glide请求流程的最后只通过调用Engine的load方法来真正开始加载的逻辑,所以自然而然读缓存的操作也是从Engine的load方法中开始:

public synchronized <R> LoadStatus load(......{
      
	// 获取资源的 key ,参数有8个
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    
	EngineResource<?> memoryResource;
    synchronized (this) {
      //去内存中获取
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime)
    }

	// 如果都获取不到,则去硬盘缓存或网络加载
	.... 
    }
}
                                        
                                        

由以上几行代码,我们把内存缓存的步骤分成以下几步来解析

第一步:获取缓存key

// 获取资源的 key ,参数有8个
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

我们看到,在创建key的时候,会传入8个参数,所以及时我们每次要获取的是同一张图片,但是大小不一样的话,也会生成一个新的缓存key。去进行缓存。

  • Glide是默认开启内存缓存的,如果我们想关掉内存缓存,可以调用:

  • //关闭内存缓存
    skipMemoryCache(false) 
    

第二步:从弱引用缓存中获取

EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);

调用了loadFromActiveResources()方法。

loadFromActiveResources()
  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    //去弱引用activeResources这个HashMap中获取
    EngineResource<?> active = activeResources.get(key);
    //如果能拿到资源,调用acquire()方法令计数器 +1
    if (active != null) {
      active.acquire();
    }
    return active;
  }

@Nullable
  synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    //如果没有查询到此弱引用对象,则删除此key
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }

  synchronized void acquire() {
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    ++acquired;
  }

这个方法就是去弱引用的activeResources这个HashMap中通过key拿到弱引用的对象,如果获取不到,则队形可能被GC,就从map中移除这个key;如果获取到则调用acquire()方法,令计数器acquire加1,代表被使用,关于计数器的应用,我们最后会总结。

第三步:从内存LruCache中获取

// 从 lrucache 获取对象
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);

调用了loadFromCache方法

loadFromCache()
  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
	
    //根据key去内存中获取资源
    EngineResource<?> cached = getEngineResourceFromCache(key);
    //如果获取到了资源,令其计数器加1,并且存放到弱引用缓存中
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }
getEngineResourceFromCache()
  private EngineResource<?> getEngineResourceFromCache(Key key) {
    //调用的remove方法去获取,也就是说,获取到资源之后,会把此资源从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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }
  • 通过以上两个方法,我们得知从LruCache中获取资源,是首先通过remove方法获取资源,获取到之后将资源从LruCache中移除,令其计数器加1并将此资源存入弱缓存中。

至此,我们就过了一遍内存缓存的拿取资源的流程:先从弱引用中取对象,如果存在,引用计数+1,如果不存在,从 LruCache 取,如果存在,则引用计数+1,并把它存到弱引用中,且自身从 LruCache 移除。

内存缓存的存资源

存储资源也一定是在图片加载的方法Engine的load方法中进行存储。我们看一下:

Engine.load()
  public synchronized <R> LoadStatus load(
    ..
    // 获取 key
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
 // 从 弱引用中获取对象
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    ...
 // 从 LruCache 获取对象
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
 ...
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);
    jobs.put(key, engineJob);

    engineJob.addCallback(cb, callbackExecutor);
    engineJob.start(decodeJob);
 ...
      }

这里会创建两个关键的对象,一个是EngineJob,这是一个线程池,维护了编码、资源解析、网络下载等工作;另一个是DecodeJob,用来对图片进行解码,它继承了Runnable,相当于EngineJob的一个任务。然后调用engineJob.start(decodeJob),具体细节先不说,等方法结束过后,会回调onResourceReady()方法

onResourceReady()
  @Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    synchronized (this) {
      this.resource = resource;
      this.dataSource = dataSource;
    }
    notifyCallbacksOfResult();
  }
notifyCallbacksOfResult()
  @Synthetic
  void notifyCallbacksOfResult() {
    ResourceCallbacksAndExecutors copy;
    Key localKey;
    EngineResource<?> localResource;
    synchronized (this) {
      stateVerifier.throwIfRecycled();
      //是否被取消
      if (isCancelled) {

        resource.recycle();
        release();
        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;
      copy = cbs.copy();
      // 引用计数 +1
      incrementPendingCallbacks(copy.size() + 1);

      localKey = key;
      localResource = engineResource;
    }
    // 把对象put到弱引用上
    listener.onEngineJobComplete(this, localKey, localResource);

	// 遍历所有图片
    for (final ResourceCallbackAndExecutor entry : copy) {
    	// 把资源加载到 imageview 中,引用计数 +1
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
    //引用计数 -1
    decrementPendingCallbacks();
  }

从上面看,notifyCallbacksOfResult() 方法做了以下事情

  1. 图片的引用计数 +1
  2. 通过 listener.onEngineJobComplete() ,它的回调为 Engine.onEngineJobComplete(),把资源 put 到 弱引用上
  3. 遍历加载的图片,如果加载成功,则引用计数+1,且通过 cb.onResourceReady(engineResource, dataSource) 回调给 target (imageview) 去加载
  4. 通过 decrementPendingCallbacks() 释放资源,引用计数 -1

当引用计数减到 0 时,即图片已经没有使用时,就会调用 onResourceReleased() 接口

onResourceReleased()
  @Override
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
  // 从弱引用中移除
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
    //添加到 LruCache 中
      cache.put(cacheKey, resource);
    } else {
     // 回收资源
      resourceRecycler.recycle(resource);
    }
  }

磁盘缓存

如果在内存缓存中拿不到图片,此时Glide就会从两个方面来进行判断:

  • 一个是资源类型:该图片是否之前曾被解码、转换并写入过磁盘缓存。
  • 一个是数据来源:构建这个图片的资源是否之前被写入过文件缓存。

我们的硬盘缓存;Glide 的硬盘策略可以分为如下几种:

  • DiskCacheStrategy.RESOURCE :只缓存解码过的图片
  • DiskCacheStrategy.DATA :只缓存原始图片
  • DiskCacheStrategy.ALL : 即缓存原始图片,也缓存解码过的图片啊, 对于远程图片,缓存 DATA 和 RESOURCE;对本地使用 只缓存 RESOURCE。
  • DiskCacheStrategy.NONE :不使用硬盘缓存
  • DiskCacheStrategy.AUTOMATIC :默认策略,会对本地和和远程图片使用最佳的策略;对下载网络图片,使用 DATA,对于本地图片,使用 RESOURCE

在讲述磁盘缓存的源码时,先附一张流程图:

在这里插入图片描述

从上面我们知道,一个图片的加载在 DecodeJob 这个类中,这个任务由 EngineJob 这个线程池去执行的。去到 run 方法,可以看到有个 runWrapped() 方法:

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

刚开始 runReason 初始化为 INITIALIZE , 所以它会走第一个 case。

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

getNextStage 其实就是对当前的缓存策略进行判断,由于我们的策略为DiskCacheStrategy.ALL ,所以 diskCacheStrategy.decodeCachedResource() 永远为true,即会解析解码的流程即去尝试拿磁盘中解码过的图片资源,所以 State 被赋值为 Stage.RESOURCE_CACHE(State是一个标记)。接着回到runWrapped()方法中,继续执行调用 currentGenerator = getNextGenerator() 拿到当前的解码器为 ResourceCacheGenerator,然后调用runGenerators()方法,这个方法是关键:

runGenerators()

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;
        }
	if ((stage == stage.FINISHED || iscancelled) && !isstarted){
        notifyFailed();
    }
}

这里面维护了一个while循环,不断的通过 调用当前解码器的startNext() 方法取尝试从DiskLruCache中拿到对象,如果拿不到,则通过while循环通过getNextstage(stage)和getNextGenerator()方法拿到下一个解码器再次寻找,当stage == Stage.SOURCE 的时候,会调用reschedule()重新调用DecodeJob的run方法重新进行RunWrapper方法。

startNext()

 @Override
  public boolean startNext() {
    List<Key> sourceIds = helper.getCacheKeys();
   ...
    while (modelLoaders == null || !hasNextModelLoader()) {
      resourceClassIndex++;
      if (resourceClassIndex >= resourceClasses.size()) {
        sourceIdIndex++;
        // 由于没有缓存,最后会在这里退出这个循环
        if (sourceIdIndex >= sourceIds.size()) {
          return false;
        }
        resourceClassIndex = 0;
      }

      Key sourceId = sourceIds.get(sourceIdIndex);
	//获取缓存 key
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      // 尝试从 DiskLruCache 拿数据
      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;
  }

由于第一次肯定是拿不到缓存的,所以 while (modelLoaders == null || !hasNextModelLoader()) 循环会一直运行,直到返回 false。

同理,接着来拿到了DataCacheGenerator之后也是同样,最后,当 stage == Stage.SOURCE 的时候,才会退出,并调用reschedule() 方法

  @Override
  public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }

在 reschedule() 方法中,会把 runReason 的状态改成RunReason.SWITCH_TO_SOURCE_SERVICE ,并重新回调 run 方法,又会调用 runWrapped() 方法,但此时的 runReason 已经变成了 SWITCH_TO_SOURCE_SERVICE,所以它会执行 runGenerators() 方法(代码上面有):

此时当前解码器currentGenerator,并不为null,所以此时会调用SourceGenerator的startNext()方法:

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

首先它会判断 dataToCache 是否为 null,第一次肯定会 null,所以,可以先不管。由于还并没有给sourceCacheGenerator赋值,所以它为null,这里可以直接看 loadData.fetcher.loadData(); 这个方法,loadData() 是个接口,它有很多个实现方法,由于我们这里是网络下载,所以去到 HttpUrlFetcher.loadData() 中:

loadData()

@override
public void loadData(@NonNull Priority priority,
	@NonNull Datacallback<?super Inputstream> callback){
	long startTime = LogTime.getLogTime( );
	try {
		Inputstream result = loadDatawithRedirects(glideur1.toURL()0null,glideur1.getHeaders());
		callback.onDataReady(result);
	}catch (TOException e) {
		if (Log.isLoggable(TAG,Log.DEBUG))
			Log.d(TAG,msg: "Failed to load data for ur1", e);
    }
}

可以看到,它拿到 inputStream 后,通过 onDataReady() 方法回调回去

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

在 SourceGenerator.onDataReady() 中,对 dataToCache 进行赋值;然后又调用 cb.reschedule()方法,从头再来一次,可是这一次dataToCache已经不是null了,所以会执行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);
		if (Log.isLoggable( TAG,Log.VERBOSE))
			Log.v(TAG,msg: "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,cb: this);
}

可以看到,对这个已经解析完的数据,通过 helper.getDiskCache().put() 方法,存到到 DiskLruCache 硬盘缓存中。并通过 loadData.fetcher.clearup() 清除任务,把sourcecacheGenerator赋值为 DataCacheGenerator。
回到SourceGenerator的startNext方法

@override
public boolean startNext() {
	if (dataTocache != null){
		object data = dataTocache;dataTocache = null;
		cacheData(data);
	}
	if (sourceCacheGenerator != null && sourcecacheGenerator.startNext(){
		return true;
	}
	sourcecacheGenerator = null;
	loadData = null:
}

此时 sourceCacheGenerator 不为 null,所以会走 DataCacheGenerator 的startNext() 方法。

在这之前已经将网络请求获取的图片加载到DiskLruCache中了,所以已经拿到了请求的图片资源,之后就会经过一系列的调用,调用到EngineJob的onResourceReady方法,这个方法我们在内存缓存的时候说过,它会把资源存放到弱引用中并且加载图片。

想必通过上面的一堆代码展示,对于硬盘的缓存的理解还是会模糊,所以我们在下面总结一下。

硬盘缓存小结

总结一下硬盘缓存的步骤:

  • 一:根据硬盘策略,选择不同的缓存策略。以下的步骤基于硬盘策略为DiskCacheStrategy.ALL,即缓存解码后的图片,也缓存原始图片。
  • 二:先调用DecodeJob的run方法,进而调用runWrapped方法,根据runReason这个标记,生成不同的解码器。
  • 三:先获取ResourceCaheGenerator,去检查硬盘DiskLruCache中有没有解码后的图片资源,如果没有则拿到下一个解码器DataCacheGenerator,调用其的startNext方法,去检查硬盘DiskLruCache中有没有原始的图片资源。如果还是没有,则最后调用rescgedule方法再次调用DncodeJob的run方法并且更改runReason的值。
  • 四:根据runReason,将SourceGenerator赋值给当前解码器,第一次dataToCache一定为null,所以一定会进行网络请求,去根据URl获取图片资源,然后再回调DecodeJob的run方法
  • 五:同样会走到这一步,这一次因为已经进行过网络的获取,所以dataToCache不为null,即会将图片资源放到硬盘缓存DiskLruCache中,然后再把DataCacheGenerator赋值给SourceGenerator,然后调用SourceGenerator.startNext即DataCacheGenerator的startNext方法。
  • 六:由于此时可以在硬盘中拿到数据了,所以和第三步的执行过程不一样,即会经过一系列步骤会回调到onResourceReady方法将图片显示,并将资源放到弱引用缓存中。

再次结合下面这个图看会更好理解:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值