Glide 4.10源码学习记录(缓存机制)

缓存机制概述

一般来说,缓存经常分为这样的三级

  • 内存缓存:优先加载,速度最快
  • 本地缓存:次优先加载,速度快
  • 网络缓存:最后加载,速度慢,需要网络

一般来说,我们会先通过网络将图片下载到本地,然后加载到内存当中。一旦图片需要再次加载,我们最先先去内存缓存中读取图片。当内存中没有图片缓存的时候,我们再去本地读取。只有当本地也没有图片的时候才会重新向网络进行请求再次加载图片

Glide也沿用这一套机制。不过如果将网络缓存不看作Glide内部的缓存机制的话,我们可以将其划分为两种类型的缓存机制

  • 一种是二级缓存即本地缓存和内存缓存
  • 一种是三级缓存,也就是在内存缓存中再进行划分为LruCache内存缓存(最近最少缓存策略)和WeakReference内存缓存(弱引用缓存策略),而本地缓存因为只有DiskLruCache硬盘缓存(硬盘缓存策略)

具体缓存图示可见


image.png

简单介绍一下这些缓存类型

  • DiskLruCache硬盘缓存: 外部缓存,例如可以将网络下载的图片永久的缓存到手机外部存储中去,并可以将缓存数据取出来使用
  • LruCache内存缓存:优先淘汰那些近期最少使用的缓存图片对象
  • WeakReference内存缓存:“弱引用”,即在引用图片对象的同时仍然允许对该对象进行垃圾回收

其中内存缓存的主要作用是防止应用重复将图片数据读取到内存当中。而硬盘缓存的主要作用是防止应用重复从网络或其他地方下载和读取数据


缓存流程

1. 生成缓存标识key

Engine.java

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,
      Executor callbackExecutor) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource<?> memoryResource;
    synchronized (this) {
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
    ···
    // Avoid calling back while holding the engine lock, doing so makes it easier for callers to
    // deadlock.
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
    return null;
  }

load方法是有很多参数的,里面创建了EngineKey的对象key,使用keyFactory的buildKey方法


EngineKeyFactory.java

class EngineKeyFactory {

  @SuppressWarnings("rawtypes")
  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);
  }
}

返回了一个EngineKey对象,点击查看


EngineKey.java

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

重写了equals和hashCode方法,以此保证EngineKey的唯一性,只有在传入的参数都相同的情况下才被认为是同一个对象


2. 内存缓存

根据文章开头的图片,这一层缓存用到了LruCache和WeakReference两种缓存机制,正在被展示的图片称为ActiveResourses,采用的就是WeakReference这种缓存机制,因此,我们先从这里开始

2.1 WeakReference缓存

回到Engine类的load方法,如果忘了可以往上翻翻,在其同步块里有一个loadFromMemory方法

Engine.java的loadFromMemory

@Nullable
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }

    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
  }

首先根据宽高,图片URL地址等生成key,然后根据key首先调用loadFromActiveResources获取内存的弱引用缓存的图片,如果获取不到弱引用缓存的图片,才调用loadFromCache获取内存的LruCache缓存。先来看看loadFromActiveResources


Engine.java的loadFromActiveResources

@Nullable
  private EngineResource<?> loadFromActiveResources(Key key) {
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }

这里首先会调用ActiveResources的get方法获取到图片资源,然后将EngineResource的引用计数加一,因为此时EngineResource指向了弱引用缓存的图片资源


ActiveResources.java的get

final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

@Nullable
  synchronized EngineResource<?> get(Key key) {
    //从弱引用HashMap中取出对应的弱引用对象
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }
    //如果弱引用中关联的EngineResource对象不存在,即EngineResourse被回收
    EngineResource<?> active = activeRef.get();
    if (active == null) {
      //清理弱引用缓存,恢复EngineResource,并保存到LruCache缓存中  
      cleanupActiveReference(activeRef);
    }
    return active;
  }

在ActiveResources中的get中,会通过activeEngineResources的get方法得到了数据的弱引用对象,而这个activeEngineResources其实就是个HashMap,所以可以根据key值来找到这个弱引用对象。而我们要找的图片资源其实就是这个弱引用对象关联的对象,因此我们需要查看ResourceWeakReference的实现


ResourceWeakReference.java

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.isMemoryCacheable() && isActiveResourceRetentionAllowed
              ? Preconditions.checkNotNull(referent.getResource())
              : null;
      isCacheable = referent.isMemoryCacheable();
    }
···
  }
}

可以发现这个类其实继承了WeakReference,所以当gc发生时,会回收掉ResourceWeakReference对象关联的EngineResource对象,这个对象封装了我们所要获取的图片资源。另外这个类还保存了图片资源和图片资源的缓存key,这是为了当关联的EngineResource对象被回收时,可以利用保存的图片资源来恢复EngineResource对象,然后保存到LruCache缓存中并根据key值从HashMap中删除掉关联了被回收的EngineResource对象的弱引用对象。可以看下EngineResourse被回收的情况,即上面get方法里的cleanupActiveReference方法


ActiveResources.java的cleanupActiveReference

void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    synchronized (this) {
      //将该弱引用缓存从HashMap中删除
      activeEngineResources.remove(ref.key);

      if (!ref.isCacheable || ref.resource == null) {
        return;
      }
    }

    EngineResource<?> newResource =
        new EngineResource<>(
            ref.resource, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ false, ref.key, listener);
    listener.onResourceReleased(ref.key, newResource);
  }

开头删除弱引用的缓存,在if语句中判断缓存是否可用。使用EngineResource恢复封装了图片资源的对象,调用listener的回调监听缓存是否被释放


Engine.java的onResourceReleased

public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    //删除弱引用缓存
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

开头删除弱引用缓存,判断是否开启内存缓存,如果开启了就放入LruCache中,没开启就回收空间


2.2 LruCache缓存

上文已经看过代码,获取不到弱引用缓存时,才会获取LruCache缓存,对应调用的方法是loadFromCache

Engine.java的loadFromCache

private EngineResource<?> loadFromCache(Key key) {
    //获取图片缓存,并将该缓存从LruCache缓存中删除
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      //引用计数,防止被算法回收
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }

首先调用了getEngineResourceFromCache来获取图片资源,然后将EngineResource的引用计数加1,并且还会将获取到的图片资源存储到弱引用缓存中。这里我们只分析getEngineResourceFromCache方法,因为调用ActiveResource的activate存储到弱引用缓存我们已经在上面弱引用缓存的存储中分析过了


Engine.java的getEngineResourceFromCache

private EngineResource<?> getEngineResourceFromCache(Key key) {
   //获取到引用时,移出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;
  }

在上面需注意的是该cache就是LruCache缓存的cache,另外你会发现获取图片缓存竟然不是调用cache的get方法,而是cache的remove方法,这就是Glide缓存策略的精妙之处了。当获取到LruCache缓存的同时会删除掉该LruCache缓存,然后将该缓存存储到弱引用缓存中,这是为了保护这些图片不会被LruCache算法回收掉


2.3 小结

image.png

3. 磁盘缓存

目的是为了防止重复从网络请求数据,采用Glide自定义的DiskLruCache缓存机制,使用代码如下

Glide.with(getContext())
        .load(url)
        .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.RESOURCE))
        .into(imageView);

通过diskCacheStrategyOf方法选择,有几种模式

  • DiskCacheStrategy.AUTOMATIC:这是默认的最优缓存策略。本地仅存储转换后的图片(RESOURCE),网络仅存储原始图片(DATA)

  • DiskCacheStrategy.NONE:不开启磁盘缓存

  • DiskCacheStrategy.RESOURCE:缓存转换过后的图片

  • DiskCacheStrategy.DATA:缓存原始图片,即原始输入流。它需要经过压缩转换,解析等操作才能最终展示出来

  • DiskCacheStrategy.ALL:既缓存原始图片,又缓存转换后的图片

所以其实观察一下可以发现主要就是RESOURCE类型和DATA类型,所以先回到Engine的load方法里

Engine.java的load

···
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);
···
    return new LoadStatus(cb, engineJob);
  }

这里会创建EngineJob对象,用于开启线程,所以必然会执行decodeJob的run方法


DecodeJob.java的run

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

在runWrapper方法的switch语句中,会获取任务场景NextStage,并拿到执行者执行任务,执行者有哪些呢?我们点进去看


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

可以发现在上述的方法中首先要找到对于场景的执行者然后执行任务。而执行者有三个,在上文第2部分中,我们分析的是无缓存的情况,即网络获取数据的执行者。接下来我们就得分析获取转换后图片的执行者(RESOURCE)和获取原始图片的执行者(DATA)


3.1 RESOURCE缓存,转换图片

对应于上面的switch语句,RESOURCE对应了ResourceCacheGenerator,拿到执行者后会回到runWrapper方法里进行runGenerators方法

DecodeJob.java的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;
      }
    }
    // 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.
  }

在while循环里,根据当前的执行者调用对应类的startNext()方法


ResourceCacheGenerator.java的startNext

public boolean startNext() {
     ···
    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);
      
      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,然后通过缓存key获取到磁盘上的文件,即磁盘缓存。最后通过加载器的loadData来处理获取到的磁盘缓存,由于磁盘缓存是File类型,根据Glide的注册表registry中可以找到该加载器其实是ByteBufferFileLoader,故最后会调用ByteBufferFileLoader内部类ByteBufferFetcher的loadData方法来处理磁盘缓存


ByteBufferFetcher.java的loadData

@Override
    public void loadData(
        @NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
      ByteBuffer result;
      try {
        result = ByteBufferUtil.fromFile(file);
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
        }
        callback.onLoadFailed(e);
        return;
      }

      callback.onDataReady(result);
    }

在loadData中会通过ByteBufferUtil工具类来获取对应磁盘文件的数据,然后通过回调ResourceCacheGenerator的onDataReady方法将数据回调出去


ResourceCacheGenerator.java的onDataReady

@Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(
        sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey);
  }

此时的cb为DecodeJob,即调用了DecodeJob的onDataFetcherReady


DecodeJob.java的onDataFetcherReady

public void onDataFetcherReady(
      Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }

decodeFromRetrievedData方法就是将数据进行压缩转换和展示图片


DecodeJob.java的decodeFromRetrievedData

private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey(
          "Retrieved data",
          startFetchTime,
          "data: "
              + currentData
              + ", cache key: "
              + currentSourceKey
              + ", fetcher: "
              + currentFetcher);
    }
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }


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

如果resource不为null,就会调用notifyEncodeAndRelease,在try语句中的encode方法就是RESOURCE类型磁盘缓存的入口


DecodeJob.java的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();
      }
    }

通过DiskCacheProvider获取到DiskCache,然后调用put方法将图片资源缓存到磁盘上


3.2 DATA缓存,原始图片

我们上面已经知道DATA和RESOURCE只是两种不同类型的执行者,
调用的方法是一致的,只不过参数不同,所以这里不再重复。而原始图片说白了就是网络获取后得到的原始的输入流,获取到原始输入流是在HttpUrlFetcher的loadData方法中,这个类实现了DataFetcher接口

HttpUrlFetcher.java的loadData

@Override
  public void loadData(
      @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

通过输入流创建result,即网络获取的数据,然后通过回调转到onDataReady


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

如果开启了磁盘缓存,就将网络获取到的原始数据赋值给dataToCache,回调了DecodeJob的reschedule


DecodeJob.java的reschedule

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

继续回调了reschedule


public void reschedule(DecodeJob<?> job) {
    //再次切换到网络线程,执行DecodeJob的run方法
    getActiveSourceExecutor().execute(job);
  }

这里的job为DecodeJob,而getActiveSourceExecutor会拿到线程池,所以reschedule方法其实会继续执行DecodeJob的run方法,然后拿到网络获取数据的执行者SourceGenerator,再次执行SourceGenerator的startNext方法。但是我们明明开启了磁盘缓存,为什么会获取到无缓存时网络获取数据的执行者呢?这是因为我们在存储原始图片的前提下,肯定是磁盘没有缓存,因此会从网络加载图片得到原始图片的输入流,然后回调,回调后当然还是拿到网络获取数据的执行者SourceGenerator。让我们再来看看这个startNext方法


SourceGenerator.java的startNext

@Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }
    //当原始图片放入磁盘缓存后,sourceCacheGenerator为DataCacheGenerator
    //然后继续执行DataCacheGenerator的startNext方法
    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;
  }

SOURCE执行者的startNext略有不同,dataCache不为null时放入缓存cacheData


SourceGenerator.java的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);
       ···
    } finally {
      loadData.fetcher.cleanup();
    }

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

在cacheData方法中会通过DiskCache的put方法将缓存key,原始图片等存储到磁盘中。然后会构造DataCacheGenerator对象,这时候我们看回SourceGenerator的startNext方法,由于此时的sourceCacheGenerator已经是DataCacheGenerator对象了,所以会调用DataCacheGenerator的startNext方法来获取磁盘缓存中的原始图片


3.3 小结

从上面的分析可以发现Glide中首先会读取转换后的图片的缓存,然后再读取原始图片的缓存。但是存储的时候恰恰相反,首先存储的是原始图片的缓存,再存储转换后的图片,不过获取和存储都受到Glide使用API的设置的影响


4. 流程图

image.png

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

推荐阅读更多精彩内容