Glide 4.0 缓存实现原理

Glide的缓存分两个模块,一个是内存缓存,一个是硬盘缓存。

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

内存缓存

Glide的内存缓存的实现是通过LruCache和弱引用

Glide默认开启内存缓存,如果有需要可以自行关闭:

Glide.with(this)
     .load(url)
     .skipMemoryCache(true)
     .into(imageView);

从with()方法跟进

Glide#with() -> Glide#getRetriever() -> Glide#get() -> Glide#checkAndInitializeGlide() -> Glide#initializeGlide() -> GlideBuilder#build()

跟进build()

public Glide build(Context context){
    
    ...
    
    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
    
    ...
    
    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }
    
    ...
}

memoryCache就是Glide使用的内存缓存了,LruResourceCache类继承了LruCache,LruCache算法实现基于LinkHashMap。稍微看看它的部分源码吧。

public synchronized Y put(@NonNull T key, @Nullable Y item) {
    final int itemSize = getSize(item);
    if (itemSize >= maxSize) {
      onItemEvicted(key, item);
      return null;
    }

    if (item != null) {
      currentSize += itemSize;
    }
    @Nullable final Y old = cache.put(key, item);
    if (old != null) {
      currentSize -= getSize(old);

      if (!old.equals(item)) {
        onItemEvicted(key, old);
      }
    }
    
    //每次put一个数据进去,都要检查是否需要进行剪裁瘦身
    evict();

    return old;
  }
  
   protected synchronized void trimToSize(long size) {
    Map.Entry last;
    Iterator> cacheIterator;
    while (currentSize > size) {
      cacheIterator  = cache.entrySet().iterator();
      last = cacheIterator.next();
      final Y toRemove = last.getValue();
      currentSize -= getSize(toRemove);
      final T key = last.getKey();
      cacheIterator.remove();
      onItemEvicted(key, toRemove);
    }
  }

  private void evict() {
    trimToSize(maxSize);
  }
  

既然LruCache有这个特性,Glide在使用过程有机会触发到这个“惊喜的”。那怎么补救呢?

我们能想到的问题,设计Glide的大神们也肯定想到了,他们增加了一个弱引用缓存,读取到LruCache的同时,把cache也存到弱引用Map

Map>> activeResources

弱引用缓存的相关内容后面还会再谈到,接下来进一步研究Glide的内存缓存是如何实现的。在Glide#build()方法中,实例化的memoryCache作为Engine的构造器的入参之一,去看看Engine对象做了什么吧。

请仔细看加注释的地方

public  LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class resourceClass,
      Class transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map, Transformation> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    //1.生成缓存key
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    //2.从弱引用读取内存缓存
    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;
    }

    //3.再从LruCache读取缓存
    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);
    }

    EngineJob engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    
    DecodeJob 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);
    
    //4.开启线程池,加载图片
    engineJob.start(decodeJob);

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

也就是说,Glide的图片加载过程中会调用两个方法来获取内存缓存,loadFromCache()和loadFromActiveResources()。这两个方法中一个使用的就是LruCache算法,另一个使用的就是弱引用。我们来看一下它们的源码:

从弱引用读取缓存
 private EngineResource loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }
    return active;
  }

方法的一开始,首先就判断了isMemoryCacheable是不是false,如果是false的话就直接返回null。这是什么意思呢?其实很简单,我们刚刚不是学了一个skipMemoryCache()方法吗?如果在这个方法中传入true,那么这里的isMemoryCacheable就会是false,表示内存缓存已被禁用。

接着通过activeResources.get(key)去获取EngineResource,如果active不为空,执行active.acquire();如果activie为空,就从Map中移除这个数据。

 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;
  }
从LruCache读取缓存
private EngineResource loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

    //1.通过缓存key拿cached
    EngineResource cached = getEngineResourceFromCache(key);
    if (cached != null) {
      //2.使acquired自增
      cached.acquire();
      //3.把cached存到弱引用的HashMap
      activeResources.activate(key, cached);
    }
    return cached;
 }
 
 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;
  }
  
  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();
    }
  }
  
  

loadFromCache()方法的简单来说就是根据缓存key读取LruCache里的cache,如果cache不为空,让EngineResource#acquired变量自增,最后把cache存到弱引用的HashMap。

好的,从内存缓存中读取数据的逻辑大概就是这些了。概括一下来说,就是如果能从内存缓存当中读取到要加载的图片,那么就直接进行回调,如果读取不到的话,才会调用EngineJob开启线程执行后面的图片加载逻辑。

现在我们已经搞明白了内存缓存读取的原理,接下来的问题就是内存缓存是在哪里写入的呢?

你想,最开始的时候肯定是没有任何缓存可以用,只能用EngineJob加载图片。问题的切入点就在于加载完图片的后续操作。

先看EngineJob的start方法

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

入参是DecodeJob类,它负责从缓存的数据或从原始源解码资源,并应用转换代码。DecodeJob完成任务后通知回调:

private void notifyComplete(Resource resource, DataSource dataSource) {
    setNotifiedOrThrow();
    callback.onResourceReady(resource, dataSource);
}

EngineJob实现了Callback接口,

@Override
public void onResourceReady(Resource resource, DataSource dataSource) {
    this.resource = resource;
    this.dataSource = dataSource;
    //Handler发送message
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}


@Override
public boolean handleMessage(Message message) {
  EngineJob job = (EngineJob) message.obj;
  switch (message.what) {
    case MSG_COMPLETE:
      job.handleResultOnMainThread();
      break;
    case MSG_EXCEPTION:
      job.handleExceptionOnMainThread();
      break;
    case MSG_CANCELLED:
      job.handleCancelledOnMainThread();
      break;
    default:
      throw new IllegalStateException("Unrecognized message: " + message.what);
  }
  return true;
}

  void handleResultOnMainThread() {
    stateVerifier.throwIfRecycled();
    if (isCancelled) {
      resource.recycle();
      release(false /*isRemovedFromQueue*/);
      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;

    // Hold on to resource for duration of request so we don't recycle it in the middle of
    // notifying if it synchronously released by one of the callbacks.
    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();

    release(false /*isRemovedFromQueue*/);
  }

哪个类实现了EngineJobListener#onEngineJobComplete(),最终答案就在那里了.

请看Engine#onEngineJobComplete()

 @Override
  public void onEngineJobComplete(EngineJob engineJob, Key key, EngineResource resource) {
    Util.assertMainThread();
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null) {
      resource.setResourceListener(key, this);

      if (resource.isCacheable()) {
        //把EngineResource放到弱引用的HashMap
        activeResources.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
 }
 
 //=========== 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();
    }
  }

把EngineResource放到弱引用的HashMap。那么这只是弱引用缓存,还有另外一种LruCache缓存是在哪里写入的呢?这就要介绍一下EngineResource中的一个引用机制了。观察刚才的handleResultOnMainThread()方法,除了调用 engineResource.acquire(); 还调用了 engineResource.release();

 void acquire() {
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
      throw new IllegalThreadStateException("Must call acquire on the main thread");
    }
    ++acquired;
  }

 
  void release() {
    if (acquired <= 0) {
      throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
      throw new IllegalThreadStateException("Must call release on the main thread");
    }
    if (--acquired == 0) {
      listener.onResourceReleased(key, this);
    }
  }

也就是说,当acquired变量大于0的时候,说明图片正在使用中,也就应该放到activeResources弱引用缓存当中。而经过release()之后,如果acquired变量等于0了,说明图片已经不再被使用了,那么此时会调用listener的onResourceReleased()方法来释放资源,这个listener就是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);
    }
  }

可以看到,这里首先会将缓存图片从activeResources中移除,然后再将它put到LruResourceCache当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。

这就是Glide内存缓存的实现原理。

硬盘缓存

硬盘缓存使用的DiskLruCache

回顾之前说的Engine#load()方法,如果在内存缓存中没获取到数据,就要开启线程池加载网络图片。其中DecodeJob承担了复杂的作用

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

execute()方法的参数类型是Runnable,所以断定DecodeJob实现了Runnable接口的run方法

  @Override
  public void run() {
    TraceCompat.beginSection("DecodeJob#run");
    DataFetcher localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      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);
    }
  }
  
  //递归返回Stage.SOURCE
  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方法的current参数是INITIALIZE,所以先在磁盘是否有图片转换处理过的缓存,显然是没有的,在查找是否存在图片的原始数据,很显然,还是没有;此时就返回Stage.SOURCE,开始从网络查找。

接下来看下 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);
    }
  }

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

while方法里的表达式里有currentGenerator.startNext(),它是退出while循环的条件之一。

因为我们研究的是硬盘缓存,这里就看跟cache相关的分支了,先看ResourceCacheGenerator#startNext()

@Override
  public boolean startNext() {

   ...
   
    while (modelLoaders == null || !hasNextModelLoader()) {
    
    ...  
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey);
      ...
    }

    ...

    return started;
  }

我省略了部分代码,这个方法的核心就是根据缓存key,拿到DiskCache里的缓存。

public interface DiskCache {

  /**
   * An interface for lazily creating a disk cache.
   */
  interface Factory {
    /** 250 MB of cache. */
    int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
    String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

    /** Returns a new disk cache, or {@code null} if no disk cache could be created. */
    @Nullable
    DiskCache build();
  }
  
  ...
  }

DiskCache.Factory的实现类是DiskLruCacheFactory,是基于DiskLruCache实现的工具类。

硬盘缓存的实现原理分析到这边就算结束,点到为止。

参考与感谢

Android图片加载框架最全解析(三),深入探究Glide的缓存机制

你可能感兴趣的:(Glide 4.0 缓存实现原理)