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算法是如何进行的。

如图所示

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

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

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

我们先写一个小Demo 看看。

    public static void main(String[] args) {

        LinkedHashMap 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 toEvict =  map.entrySet().iterator().next();
        System.out.println("First: "+toEvict.getValue());
        
        for (Map.Entry 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 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、三级缓存关系

如图所示

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

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

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

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

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

2.1 验证第一条支线

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

DecodeJob.class

private void notifyEncodeAndRelease(Resource 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 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方法

如图所示

进入对应方法。

  @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  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,
      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  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,
      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 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 implements ModelLoader {

...略
  @Override
  public LoadData 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 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三级缓存原理以及对应的关系。

相关推荐

  • 【2021 最新版】Android studio全套教程+Android(安卓)开发入门到精通(项目实战篇)_哔哩哔哩_bilibili

  • 【2021最新版】Kotlin语言教程——Kotlin入门到精通全系列_哔哩哔哩_bilibili

  • Android流行框架零基础入门到精通全套教程/热修复/Glide/插件化/Retrofit/OKHTTP/Gson/组件化/Jetpack/IOC/高德地图_哔哩哔哩_bilibili

  • Android开发进阶学习—设计思想解读开源框架 · 已更新至104集(持续更新中~)_哔哩哔哩_bilibili

  • 【实战教学】Android开发—jetpack入门到精通_哔哩哔哩_bilibili

  • 价值100W+Android实战项目大全/高级UI/灵动的锦鲤/QQ空间热修复/插件化框架/组件化框架设计/网络访问框架/RXJava/IOC/MVVM/NDK_哔哩哔哩_bilibili

  • Android音视频开发:音视频基础知识到直播推流实战系列教程_哔哩哔哩_bilibili

  • Android项目实战-从0开始手把手实现组件化路由SDK项目实战_哔哩哔哩_bilibili

  • 【Android开发教程】一节课解剖Retrofit源码内核_哔哩哔哩_bilibili

本文转自 https://juejin.cn/post/7018130176785514504,如有侵权,请联系删除。

你可能感兴趣的:(Android 架构之Glide源码解读(下))