Glide源码浅析(五)缓存策略,加载策略

请求的缓存获取

在RequestBuilder的into中,会创建一个request对象,创建完之后,会通过Target来获取一个Request,如果两个request相同,则直接启动Target所关联的请求,两者不相同那么将新请求保存到RequestTracker中,并启动

  • 如果同一个Imageview加载同一张图片,由于图片属性设置等问题,会造成请求的不同
  • 通过into来启动是唯一一个暂时不受fragment声明周期影响的请求,当然如果走到了onStart请求还没有结束,则会暂停请求

图片资源的缓存策略

我们先是根据好多的属性生成了一个key,然后根据这个key去缓存中去查找对应的资源,如果没有那么就开启线程根据属性变量从磁盘修改过的资源或是原始资源中查找,如果还没有,那么就开启网络去下载

在Engine中的load()中,我们是直接获取缓存中储存的图片
Engine#load():

  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,resourceClass, transcodeClass, options);
  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;
  }

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

  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  if (current != null) {
    current.addCallback(cb);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Added to existing load", startTime, key);
    }
    return new LoadStatus(cb, current);
  }

我们根据key先通过loadFromActiveResources()中去获取

弱引用计数缓存

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

通过activieResources.get()获取资源,如果不为空则调用acquire()
ActivieResources中维护着一个Map集合Map<Key, ResourceWeakReference>,关于ResourceWeakReference
ActiveResources#ResourceWeakReference#

 @VisibleForTesting
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
  @SuppressWarnings("WeakerAccess") @Synthetic final Key key;
  @SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable;

  @Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource;

  @Synthetic
  @SuppressWarnings("WeakerAccess")
  ResourceWeakReference(
      @NonNull Key key,
      @NonNull EngineResource<?> referent,
      @NonNull ReferenceQueue<? super EngineResource<?>> queue,
      boolean isActiveResourceRetentionAllowed) {
    super(referent, queue);
    this.key = Preconditions.checkNotNull(key);
    this.resource =
        referent.isCacheable() && isActiveResourceRetentionAllowed
            ? Preconditions.checkNotNull(referent.getResource()) : null;
    isCacheable = referent.isCacheable();
  }

  void reset() {
    resource = null;
    clear();
  }
}

ResourceWeakReference继承于WeakFeference,构造器接收到一个四个参数,在前面map中储存的key值,ResourceWeakReference的储存类型EnginResource对象,ReferenceQueue以及一个布尔类型,有三个成员变量,key值、EnginResource的核心属性resource,以及一个缓存是否可用的标识。ResourceWeakReference中的对象被回收后,会将ResourceWeakReference本身加入到ReferenceQueue中

acquire() release()

上面说了此级缓存为弱引用计数缓存,计数计算的是此资源被引用的次数,比如一个界面五个控件引用同一个资源,那么这个资源的计数为5
acquire()有四个地方调用到了

  • 首先是文章开头说的,从软引用缓存中获取到了资源,然后会计数+1
  • 其次从内存缓存LruCache中获取成功,因为从LruCache中获取是删除获取,所以获取成功之后会加入到弱引用中,然后计数+1
  • 再者就是开启线程从磁盘或者网络获取资源成功之后,会根据请求的的数量,也可以理解为需要此图片的Target控件的数量+1 代码如下
    EngineJob#handleResultOnMainThread():
  engineResource.acquire();
  listener.onEngineJobComplete(this, key, engineResource);

  //noinspection ForLoopReplaceableByForEach to improve perf
  for (int i = 0, size = cbs.size(); i < size; i++) {
    ResourceCallback cb = cbs.get(i);
    if (!isInIgnoredCallbacks(cb)) {
      engineResource.acquire();
      cb.onResourceReady(engineResource, dataSource);
    }
  }
  // Our request is complete, so we can release the resource.
  engineResource.release();

每根据target生产一个请求就会产生一个cb,同一个界面如果多个控件需要这个资源,那么可能就会产生多个cb,最终会根据每个cbs的数量来+n(多以列表页为例)

再来说释放release

  • 首先是通过RequestManager#clear()清除某个拥有加载任务的Imageview时,如果此时的ImageView已经请求加载完成,那么会清除此Imageivew所关联的Request以及对应的图片资源
    RequestManager requestManager = Glide.with(mContext);
    requestManager.load(url).apply(new RequestOptions().error(R.mipmap.user_head)).into(mPersonZCodeImg);
    requestManager.clear(mPersonZCodeImg);
    
  • 还有就是RequestManager本身实现了生命周期的接口,所以当Fragment销毁(onDestroy)/界面不可见(onStop)的时候,会清除当前Fragment所创建的所有Request,然后会释放每个Request所持有的资源。
  • 当然还有如果资源获取成功了,但是由于请求取消或者获取的资源不匹配会将该资源释放,对应acquire中的第三种情况

总结一下就是获取资源成功的时候会+1,获取失败、或者当前界面不可见或销毁以及主动清除控件关联的时候会释放-1

上面的是一些拓展,回到ResourceWeakReference中,我们通过ActiveResources从Map集合中去获取ResourceWeakReference,进而去获取ResourceWeakReference中的资源,如果获取成功则直接返回,获取失败则从内存缓存Lrucache中去获取
注意的是,ActiviveResources中的Map集合的对象添加有两种情况,一是在从LruCache中获取成功之后填入进去的,二是开启线程从磁盘或是网络加载资源成功之后,会通过EngineJob#handleResultOnMainThred()将资源加入到Map集合中;而LruCache中的对象则是从ActiviveResources中获取并加入的
从LruCache中获取成功之后,会将获得的的资源通过ActiveRsources#activate()将资源加入到弱引用以及集合中
ActiveResources#activate():


  void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key,
            resource,
            getReferenceQueue(),
            isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

代码很简单,我们来看getReferenceQueue():

  private ReferenceQueue<EngineResource<?>> getReferenceQueue() {
    if (resourceReferenceQueue == null) {
      resourceReferenceQueue = new ReferenceQueue<>();
      cleanReferenceQueueThread = new Thread(new Runnable() {
        @SuppressWarnings("InfiniteLoopStatement")
        @Override
        public void run() {
          Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
          cleanReferenceQueue();
        }
      }, "glide-active-resources");
      cleanReferenceQueueThread.start();
    }
    return resourceReferenceQueue;
  }

创建一个ResourceReferenceQueue,并开启一个后台线程,调用cleanReferenceQueue :

  @SuppressWarnings("WeakerAccess")
  @Synthetic void cleanReferenceQueue() {
    while (!isShutdown) {
      try {
        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
        mainHandler.obtainMessage(MSG_CLEAN_REF, ref).sendToTarget();

        // This section for testing only.
        DequeuedResourceCallback current = cb;
        if (current != null) {
          current.onResourceDequeued();
        }
        // End for testing only.
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

里面是一个死循环,不断地从resourceReferenceQueue中获取数据,(只有被回收的WeakReference才会进入resourceReferenceQueue,所以只要队列中有数据,说明此资源已经被GC了),然后发送一条消息 最终调用cleanupActiveFeference()

  @SuppressWarnings("WeakerAccess")
@Synthetic void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
  Util.assertMainThread();
  activeEngineResources.remove(ref.key);

  if (!ref.isCacheable || ref.resource == null) {
    return;
  }
  EngineResource<?> newResource =
      new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
  newResource.setResourceListener(ref.key, listener);
  listener.onResourceReleased(ref.key, newResource);
}

首先将此资源对应的弱引用对象从Map中删除,然后去释放资源
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);
  }
}

首先判断此资源是否可以被缓存,如果可以则放入到LruCache中,如果不可以 则直接回收掉

内存缓存LruCache

Engine#load():

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

代码也很简单,里面有一个属性cache,这个属性就是我们在glideBuilder中初始化然后传递给Glide的memoryCache对象, memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());

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

上面就是我们获取内存中图片资源的代码,获取成功之后会加入到activeResources中,这是LruCache中资源的获取;资源的添加上面也已经说过,只有一个入口(其实是有两个入口,另一入口我看懂 /手动流汗),就是弱引用缓存中的资源在计数为0时,会从缓存中删除并并添加到LruCache中
Engine#onRsourceReleased():

  @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

任务调度管理类EngineJob

当从内存中获取资源失败的时候,就开始开启线程从磁盘或是网络加载资源了,其中的EngineJob则负责任务的添加和删除,以及请求成功的回调等管理操作

EngineJob#load():

public <R> LoadStatus load(){
    ...
    ...
 EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    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);
    engineJob.start(decodeJob);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);

上面代码是load方法的后半部分,也是从缓存加载资源失败之后的代码,首先会从jobs里面尝试获取EnginJob对象,获取成功则添加监听并返回,获取失败则新建EnginJob对象并加入jobs然后再添加监听最终返回。
EngineJob负责将加载成功的资源整合成请求所需要的资源,并分配到各个请求或是需要此资源请求类中(这里指SingleRequest 实现了ResourceCallBack接口);EngineJob包含有磁盘和网络加载任务,以及解码管理类DecodeJob,当然最重要的是还包含有当前请求所请求成功的资源对象,所以上面的在从集合中获取到EngineJob之后,就直接返回了,之后会通过EngineJob直接获取到资源,所以也算是一层缓存,当然这里不是重点。
注意两点,一个是为EngineJob添加一个回调,这个回调是EngineJob将原始资源处理完之后分配给相应的请求的,EngineJob对这个请求有一个集合专门保存,所以就可以多个请求共同请求同一资源,然后再逐一分配,这里的监听的具体实现是SingleRequest。
之后就是将解码管理类加入到EngineJob,最后调用start开始加载资源

  public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

首先判断使用哪种任务,如果允许从磁盘缓存解码资源,则使用disCacheExecutor,如果不允许则判断是使用动画执行任务animationExecutor还是源文件执行任务sourceExecutor;区别就是磁盘执行器最大只允许一个线程执行,动画最多允许两个线程执行,而源资源执行器则允许四个线程执行,当然无论哪个最终都会调用decodeJob的run方法

资源加载策略

通过decodeJob的run方法最终会调用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);
    }
  }

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

在runWrapped()中首先判断runReason,此时值为INITIALIZE,然后获取下一状态值,在获取下一状态值的方法getNextState中,会根据磁盘缓存的策略判断来指定Stage值

磁盘缓存策略 DiskCacheStrategy

全部缓存
磁盘缓存的策略总共有五种,第一种是全缓存策略,即图片源文件以及根据配置修正后的图片文件全部缓存

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

第一个方法可以理解为是否缓存此资源,true代表此资源是从非本地设备获取而是从远程服务器获取的原始文件资源可以被缓存
第二个方法可以理解为是否缓存此资源,true代表此资源时从非本地设备获取而是从远程服务器获取并经过处理的资源可以被缓存
第三个方法可以理解为是否可以解码缓存中的经过处理的资源
第四个方法可以理解为是否可以解码缓存中未经过处理的资源

不使用磁盘缓存

  public static final DiskCacheStrategy NONE = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return false;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return false;
    }

    @Override
    public boolean decodeCachedResource() {
      return false;
    }

    @Override
    public boolean decodeCachedData() {
      return false;
    }
  };

这个没什么好说的

只缓存原始资源

  public static final DiskCacheStrategy DATA = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource != DataSource.DATA_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return false;
    }

    @Override
    public boolean decodeCachedResource() {
      return false;
    }

    @Override
    public boolean decodeCachedData() {
      return true;
    }
  };

可以看出无论是对处理过的资源的缓存还是解码都是false,而原始资源则相应的返回true

只缓存处理过的图片资源

  public static final DiskCacheStrategy RESOURCE = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return false;
    }

    @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 false;
    }
  };

与上一种情况正好相反,只对处理过的资源进行缓存和解码,原始资源则返回false

磁盘缓存策略在RequestOptions中可以设置,是针对每一条请求任务的。

基于数据源的auto缓存

  public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
          || dataSource == DataSource.LOCAL)
          && encodeStrategy == EncodeStrategy.TRANSFORMED;
    }

    @Override
    public boolean decodeCachedResource() {
      return true;
    }

    @Override
    public boolean decodeCachedData() {
      return true;
    }
  };

第一个方法如果当前资源时远程资源则返回true,表示可以缓存原始数据
第二个方法如果此资源是从本地获取的并且可以写进缓存的,那么就返回true
关于解码都可以

如果我们没有设置磁盘策略,默认为最后一种

回到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:
        // 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);
    }
  }

首先确定的是,默认磁盘缓存策略情况下,上面的判断都是true
这样我们就得到了Stege.RESOURCE_CAHE,然后根据新拿到的stage,进入到getnextGenerator()
DecodeJob#SourceGenerator():

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

我们首先拿到了ResourceCacheGenerator对象,之后进入runGenerators(),开始了循环
DecodeJob#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();
    }
  }

这里的currentGenerator就是上面我们获取到ResourceCacheGenerator对象 调用此对象的startNext()
从本地磁盘加载修改后的数据

  @Override
  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;
      }
      throw new IllegalStateException(
          "Failed to find any load path from " + helper.getModelClass() + " to "
              + helper.getTranscodeClass());
    }
    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);
      // PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,
      // we only run until the first one succeeds, the loop runs for only a limited
      // number of iterations on the order of 10-20 in the worst case.
      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;
  }

首先获取到key集要将图片资源需要转换成的对象类型集, 然后进入一个while循环,跳出循环的两种请求,一是从本地磁盘文件找到了对应资源,然后跳出循环,通过ModelLoader加载找到的资源;另一个则是本地磁盘中没有找到对应的资源返回false;
返回false之后,再一次获取下一个stage值DATA_CACHE,以及下一个DataFetcherGenerator对象DataCacheGenerator,然后开始下一次的循环,进入到DataCacheGenerator.startNext()中
加载成功之后会调用ResourceCacheGenerator.onDataReady()->修改runReason = DECODE_DATA DecodeJob.reschedule()->run()->runWrapped()->decodeFromRetrievedData()->EngineJob.onResourceReady()->SingleRequest.onResourceReady() 为taget设置图片资源
DataCacheGenerator#startNext():


  @Override
  public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
      // PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
      // and the actions it performs are much more expensive than a single allocation.
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      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;
  }

这个方法跟ResourceCacheGenerator的差不多,此方法是现货区磁盘缓存的原始资源文件,如果成功则通过ModelLoader加载资源,加载成功然后回调到当前类,如果本地磁盘没有原始文件,那么同样会返回false

加载原始图片成功之后,流程同上,这里不再多说。

同样的道理,我们来看SourceGenerator中的加载

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

这里的代码就很简单了,因为此方法是直接通过图片资源的原地址获取资源的,或者本地的reset文件或是网络文件,最后获取成功,流程同样同上

到这里图片的获取算是完成了,就是磁盘的缓存我们却没有看到,因为磁盘的缓存存入跟这个解码以及磁盘缓存策略有些关系,所以我这里只是简单的说一下
通过上面的代码我们可以看出,图片获取成功之后的流程大致是一致的,其中在经过DecodeJob.decodeFromRetrievedData -> notifyEncodeAndRelease()的时候
DecodeJob#notifyEncodeAndRelease():

  private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
    if (resource instanceof Initializable) {
      ((Initializable) resource).initialize();
    }

    Resource<R> result = resource;
    LockedResource<R> lockedResource = null;
    if (deferredEncodeManager.hasResourceToEncode()) {
      lockedResource = LockedResource.obtain(resource);
      result = lockedResource;
    }

    notifyComplete(result, dataSource);

    stage = Stage.ENCODE;
    try {
      if (deferredEncodeManager.hasResourceToEncode()) {
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    // Call onEncodeComplete outside the finally block so that it's not called if the encode process
    // throws.
    onEncodeComplete();
  }

在通知加载完成会后,下面有一个try-cache
DecodeJob#encode():

    void encode(DiskCacheProvider diskCacheProvider, Options options) {
      TraceCompat.beginSection("DecodeJob.encode");
      try {
        diskCacheProvider.getDiskCache().put(key,
            new DataCacheWriter<>(encoder, toEncode, options));
      } finally {
        toEncode.unlock();
        TraceCompat.endSection();
      }
    }

我们看到将资源写入到了磁盘之中

到这里图片的图片资源的获取、加载、缓存就完成了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容