Android 架构之Glide源码解读(下)

小知识,大挑战!本文正在参与“  程序员必备小知识  ”创作活动

本文同时参与 「掘力星计划」  ,赢取创作大礼包,挑战创作激励金

前言

在前两篇中,主要进行了 Glide 三部曲 with、load、into 对应的源码解析。在本篇中将会对Glide的三级缓存以及三层缓存实现的逻辑进行详解。

在解读三级缓存之前,我们要先知道到底是哪三级缓存,然后再熟悉其原理,最后再将其原理带入源码中解读,将会事半功倍。

为了更好的了解Glide,建议没看过前两篇的小伙伴可以去看看,当然我这每一篇都是独立的,就算没看过前两篇,本篇内容依然能够看懂。

前两篇文章地址

# Android 架构之Glide源码解读(上)

# Android 架构之Glide源码解读(中)

1、三级缓存介绍

所谓的三级缓存,无非就是:

  • 活动缓存 (ActiveCache)
  • 内存缓存 (LruCache/MemoryCache)
  • 磁盘缓存 (DiskLruCache)

接下来将会依次介绍这三级缓存。

这里的活动缓存,你可以吧它理解成一个Map集合,它是暂时性的,用完可丢的那种。需要它的时候就调用对应的Get/Put,不需要的时候就可以随时Clear掉。

而内存缓存(LruCache/MemoryCache)与磁盘缓存 (DiskLruCache),它们都是通过LinkedHashMap,进行了Lru算法,只不过它们的区别是一个存在内存里面生命周期和App运行时相互绑定,一个存在SD卡里可随着App的存在(未卸载)永久储存。

接下来我们来看看Lru算法是如何进行的。

QQ截图20211012144601.png

如图所示

我们先设置了 LinkedHashMap 可保存的最大值为 3,同时依次向里面添加了3个元素,1作为最先添加的在最底部,3作为最后添加的在顶部。

当我们添加新的元素 4的时候,由于最多只能保存3个,所以作为1最先添加的元素要被移除掉,将最新添加的元素放在最顶部,2就因此放在了最底部。

在这个时候,我们在程序里又开始使用了2 的时候,因为2还在集合里面,所以将2的位置移动到了最上面,其他元素位置依次往下降一格。

我们先写一个小Demo 看看。

    public static void main(String[] args) {

        LinkedHashMap<String, Integer> map = new LinkedHashMap<>(0, 0.75f, true);
        map.put("one", 1);
        map.put("two", 2);
        map.put("three", 3);
        map.put("four", 4);
        map.put("five", 5);
        map.get("three");
        Map.Entry<String, Integer> toEvict =  map.entrySet().iterator().next();
        System.out.println("First: "+toEvict.getValue());
        
        for (Map.Entry<String, Integer> stringIntegerEntry : map.entrySet()) {
            System.out.println("element: "+stringIntegerEntry.getValue());
        }
        
    }
复制代码

代码解读

这里定义了一个 LinkedHashMap 集合,然后往里面添加数据,接着使用了里面的某个元素对象,最后打印了集合最先添加的元素以及集合里面每一个元素。来看看运行效果。

运行效果

First: 1
element: 1
element: 2
element: 4
element: 5
element: 3
复制代码

从这里面可以看出,我们按1-5的顺序依次添加在集合里,当我们调用map.get("three"); 的时候,就已经改变了原有集合里面的顺序。这里我们还没有设置MaxSize,那我们继续看看Lru的部分源码。

public void trimToSize(int maxSize) {
    while (true) {
        K key;
        V value;
        synchronized (this) {
            if (size < 0 || (map.isEmpty() && size != 0)) {
                throw new IllegalStateException(
                        getClass().getName() + ".sizeOf() is reporting inconsistent results!");
            }
			
			//判断是否达到最大值
            if (size <= maxSize || map.isEmpty()) {
                break;
            }
			//到这里表示 以及达到最大值,那么就要进行达到最大值的逻辑处理
			//拿到集合中第一个元素(也就是最先添加的元素)
            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
            key = toEvict.getKey();
            value = toEvict.getValue();
            map.remove(key);
            // 拿到键值对,计算出在容量中的相对长度,然后减去。
            size -= safeSizeOf(key, value);
            // 添加一次收回次数
            evictionCount++;
        }
        /*
         * 将最后一次删除的最少访问数据回调出去
         */
        entryRemoved(true, key, value, null);
    }
}
复制代码

源码解读

从这里可以看出,当我们集合达到最大值的时候,就会获取集合里面第一个(最先最早)的元素,然后将它给移除掉。

到这相信你已经知道了Lru算法的工作原理,那么接下来我们来看看这三级缓存之间的关系。

2、三级缓存关系

缓存时序图.png

如图所示

从这张图,我们可以开出三条支线:(我们先总结,然后依次在源码里验证)

第一条支线:当我们页面开始请求图片时,将会创建当且仅有一个空白的Fragment,并且与当前展示的Activity/Fragment的生命周期相互绑定。我们先假设三级缓存都没有对应的缓存,或者说图片是第一次请求的时候,将会去请求服务器图片地址。拿到图片后先写入磁盘缓存,接着会跳过内存缓存直接写入活动缓存,最后展示图片。

第二条支线:当对应展示图片的Activity/Fragment执行生命周期的onDestory方法时,对应Glide创建的Fragment也会执行相同的onDestory方法。当执行到 Glide的 onDestory方法时,会将当前页面的活动缓存全部添加至内存缓存里面,最后清空活动缓存

第三条支线:当我们页面开始请求图片时,将会创建当且仅有一个空白的Fragment,并且与当前展示的Activity/Fragment的生命周期相互绑定,先去活动缓存里面查询是否有对应缓存,如果有则直接显示对应图片,终止图片请求;否则去内存缓存里面寻找。假如内存缓存里面有对应缓存,那么先将对应的内存缓存添加至对应的活动缓存并展示图片,然后移除内存缓存里面的缓存;假如内存缓存里面没有对应缓存,则进入磁盘缓存里面寻找,如果磁盘缓存里面有对应缓存,那么将对应的缓存,跳过内存缓存,直接添加至活动缓存。如果磁盘缓存里面没有对应缓存,那么执行第一条支线逻辑。

接下来就该验证这三条支线的逻辑了。

2.1 验证第一条支线

既然说的是,三级缓存都没有的情况,那我们直接定位到,图片从服务器请求成功的位置。(参考:Android 架构之Glide源码解读(中)

DecodeJob.class

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
	
	...略

	//通知上一级已完成
    notifyComplete(result, dataSource);

    stage = Stage.ENCODE;
    try {
      if (deferredEncodeManager.hasResourceToEncode()) {
      	//这里进行磁盘缓存
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    onEncodeComplete();
  }
复制代码

源码解析

这里有两个核心代码,一是 调用了notifyComplete 方法;一是 调用了 deferredEncodeManager.encode 方法。

我们先追进 notifyComplete 方法,先看看最终干了什么。 步骤:notifyComplete->callback.onResourceReady->notifyCallbacksOfResult->engineJobListener.onEngineJobComplete,然后我们就能看到

  @Override
  public synchronized void onEngineJobComplete(
      EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null && resource.isMemoryCacheable()) {
      activeResources.activate(key, resource);
    }

    jobs.removeIfCurrent(key, engineJob);
  }
复制代码

源码解析

看到这,我们应该清楚了,在这进行了活动缓存的添加。接下来我们继续回到刚刚的位置。

DecodeJob.class

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
	
	...略

	//通知上一级已完成
    notifyComplete(result, dataSource);

    stage = Stage.ENCODE;
    try {
      if (deferredEncodeManager.hasResourceToEncode()) {
      	//这里进行磁盘缓存
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    onEncodeComplete();
  }
复制代码

源码解析

进入 deferredEncodeManager.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();
      }
    }

复制代码

源码解析

到这,我们验证了第一条支线完全符合逻辑。接下来轮到第二条支线。

2.2 验证第二条支线

第二条支线的起因点 在于onDestory方法,所以我们直接定位到Glide创建的Fragment里面的onDestory方法。

RequestManagerFragment.class

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
    unregisterFragmentWithRoot();
  }
复制代码

源码解析

这里就两句代码,最后那句代码表示要移除相关Fragment操作,我们直接进入lifecycle.onDestroy()

  void onDestroy() {
    isDestroyed = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onDestroy();
    }
  }
复制代码

源码解析

继续追进 onDestory方法

QQ截图20211012170311.png

如图所示

进入对应方法。

  @Override
  public synchronized void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    mainHandler.removeCallbacks(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }
复制代码

源码解析

这里我们继续追进,进入for循环里面的clear方法。过后步骤依次为: untrackOrDelegate->request.clear()->SingleRequest.clear()->engine.release(toRelease)->((EngineResource<?>) resource).release()->onResourceReleased,最后再进入实现这个方法的地方就得到:

  @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isMemoryCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
    }
  }
复制代码

源码解析

这里先是调用了 deactivate 方法,然后进行了是否需要内存缓存判断,如果需要那就存入内存缓存,那我们看看 deactivate 这个方法到底做了啥。

  synchronized void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }
复制代码

源码解析

从这里可以看出,先是调用了方法 deactivate 将活动缓存对应的图片移除掉,然后将移除掉的对应图片缓存添加至内存缓存里。

这里第二条支线也通过源码验证了,接下来该验证第三条支线了。

2.3 验证第三条支线

第三条支线的起因在于网络请求前判断,所以这里需要直接定位到,网络请求前判断的位置。

Engine.class

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

      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.
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
    return null;
  }
复制代码

源码解析

从这里可以看出,这里通过图片的一系列参数(地址、Url、宽高等等)生成了 EngineKey 参数,然后将参数带入了方法loadFromMemory,如果该方法返回的值不为空,就直接返回,接下来进入看看该方法。

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

    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      return active;
    }

    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      return cached;
    }

    return null;
  }
复制代码

源码解析

从这里我们可以得到,如果活动缓存里面没有那么就去取内存缓存,如果内存缓存里面有那么就将内存缓存返回。但是在上面我们总结第三条支线的时候,还提到过当拿到对应内存缓存时,需要将对应缓存添加至活动缓存,并且移除当前内存缓存,这里并没有表现出来,接下来进入loadFromCache 方法看看。

  private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }
复制代码
  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();
    }
  }
复制代码

源码解析

从这里我们可以得出:当获取到内存缓存时,会将对应缓存添加至活动缓存。那么删除对应的内存缓存呢?不急!不是还有个方法 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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }
复制代码

源码解析

果不出所料,移除内存缓存逻辑在这。接下来就差磁盘缓存的判断了。继续回到刚刚那个位置。

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

      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.
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
    return null;
  }
复制代码

源码解析

这里的 memoryResource 为空,表示的是:当前活动缓存以及内存缓存都为空。想看磁盘缓存还得往下走,步骤为:waitForExistingOrStartNewJob-> engineJob.start(decodeJob)->executor.execute(decodeJob) 这里开启了一个线程->DecodeJob->run方法,执行线程->runWrapped()->case INITIALIZE:->runGenerators->currentGenerator.startNext()->SourceGenerator.startNext()->sourceCacheGenerator.startNext()

这里步骤有点多,不知道,你们跟过来没有,这里代码为:

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

      Key sourceId = cacheKeys.get(sourceIdIndex);

      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      //从这里获取对应的磁盘缓存文件  cacheFile  为File类型
      cacheFile = helper.getDiskCache().get(originalKey);
      //如果 cacheFile  磁盘缓存不为空,那么 拿到 对应磁盘缓存的 modelLoaders 
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //将磁盘缓存的 modelLoaders 获取对应磁盘缓存的 modelLoader 
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      //cacheFile 为file类型。
      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;
  }
复制代码

源码解析

从这里可以看出,helper.getDiskCache().get(originalKey) 这句话表示获取对应图片的磁盘缓存,如果返回的 cacheFile 不为空,这说明有对应的磁盘缓存,那么下面while循环里面的 modelLoaders 为磁盘缓存对应的 modelLoaders,因为cacheFile为File类型, 也就是说下面的 buildLoadData 方法,也是调用的 File 类型的,因此进入FileLoader类:

public class FileLoader<Data> implements ModelLoader<File, Data> {

...略
  @Override
  public LoadData<Data> buildLoadData(
      @NonNull File model, int width, int height, @NonNull Options options) {
    return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener));
  }

...略
}
复制代码

源码解析

进入 FileFetcher ,可得

    @Override
    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {
      try {
        data = opener.open(file);
      } catch (FileNotFoundException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to open file", e);
        }
        callback.onLoadFailed(e);
        return;
      }
      callback.onDataReady(data);
    }
复制代码

源码解析

好了,到这里应该都懂了吧,如果磁盘缓存不为空,那么进入 FileLoader类调用对应的文件加载方法。

3、总结

好了到这,这篇文章差不多结束了,相信看到这里的小伙伴应该完全理解了Glide三级缓存原理以及对应的关系。

原创不易,如果对你有帮助,请帮忙点个赞,您的赞将会是我写作最大的动力 :>

猜你喜欢

转载自juejin.im/post/7018130176785514504