Android 【手撕Glide】--Glide缓存机制

作者: 唠嗑008 | 来源:发表于2020-01-07 17:48 被阅读0次

    本文源码解析基于Glide 4.6.1
    不知道大家最开始使用Glide的原因是什么?我的原因很简单就是冲着那句Glide.with(this).load(url).into(imageview)去的,再加上Google的推荐,就一直沿用至今。以前也不太了解它,就知道它使用简洁而且很火,不过最近看了一些它的源码设计,算是找到了使用Glide理由。我目前的缘由如下:
    1、Glide通过高度封装之后,通过外观模式对外提供了非常简洁的API调用,貌似外观模式的很多库都很受欢迎;
    2、Glide自动感知生命周期,很节约资源,不会内存泄漏;
    3、超级强大的缓存机制;
    4、各种图片转换,超级方便。

    Android 【手撕Glide】--Glide缓存机制
    Android 【手撕Glide】--Glide缓存机制(面试)
    Android 【手撕Glide】--Glide是如何关联生命周期的?

    我想只要用过Glide的同学都或多或少听过Glide的缓存机制,比如Glide用了3级缓存;又用了Lrucache、DiskLrucache;Glide缓存图片会缓存多张等等。但还是有很多同学对缓存源码和缓存原理没有一个整体的清晰的思路,本文就是来解决这个问题的,为了高效的学习,本文按照如下思路来讲解Glide缓存机制:

    • Glide缓存简介
    • Glide缓存Key
    • Glide内存缓存的读写
    • Glide磁盘缓存的读写

    Glide缓存简介

    三级缓存or二级缓存?
    在没学习源码之前,我连这个最基本的概念都不确定,以前老是听人说缓存是内存--->磁盘--->网络这样的方式去获取图片资源的,但这就是3级缓存吗?明显不是,这个只是2级缓存;Glide也是按照这种方式获取图片的,但是略有不同,Glide将它的缓存分为2个大的部分,一个是内存缓存,一个是硬盘缓存。其中内存缓存又分为2种,弱引用和Lrucache;磁盘缓存就是DiskLrucache,DiskLrucache算法和Lrucache差不多的,所以现在看起来Glide3级缓存的话应该是WeakReference + Lrucache + DiskLrucache

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

    Glide缓存Key

    缓存是为了解决重复加载问题,那必然要有一个key来区分不同的图片资源。从下面生成key的代码可以看出Glide生成key的方式远比我们想象的要复杂,决定缓存Key的参数有8种,其中包括图片URL、宽、高。

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

    这里可以得出一个结论,几乎任意配置的改变都会导致同一张图片生成多个缓存key。举个例子:同一张图片加载到2个不同大小的ImageView会生成2个缓存图片。至于EngineKey的作用,当然是用于读取/写入缓存图片的时候用到的,别着急,后面的流程你会多次看到的。

    Glide内存缓存的读写

    这里先从内存缓存说起吧,首先Glide默认开启了内存缓存,当然你可以选择手动关闭。注意:只有开启了内存才能使用下面的内存缓存功能。

    skipMemoryCache(true) //关闭内存缓存
    

    前面提到过内存缓存是通过弱引用+LruCache的方式实现的。那内存缓存在哪里实现的呢?还记得刚才在Engine#load()方法中生成缓存Key吗?内存缓存的代码也在这里实现的,下面一起看一下源码:

    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) {
        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;
        }
     
        //...省略
        //4.通过线程池从网络加载图片
       
    

    看一下内存缓存部分的逻辑,首先通过loadFromActiveResources从弱引用读取;如果没有再通过loadFromCache从LruCache读取;2者中的任意一个获取到数据就会调用onResourceReady就是将资源回调给ImageView去加载。

    Engine#loadFromActiveResources():从弱引用读取缓存

    public class Engine  {
      private final ActiveResources activeResources;
    
    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    //没有开启内存缓存就直接返回   
     if (!isMemoryCacheable) {
          return null;
        }
        //弱引用获取缓存图片
        EngineResource<?> active = activeResources.get(key);
        if (active != null) {
          active.acquire();
        }
    
        return active;
      }
    }
    
    #ActiveResources#get()
    class ActiveResources {
        //弱引用的hashmap
        Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
    
        EngineResource<?> get(Key key) {
        //1.从弱引用的map获取图片资源
        ResourceWeakReference activeRef = activeEngineResources.get(key);
        if (activeRef == null) {
          return null;
        }
        //2.最终需要的资源对象
        EngineResource<?> active = activeRef.get();
        if (active == null) {
          cleanupActiveReference(activeRef);
        }
        return active;
      }
    }
    

    这里的逻辑也不复杂,通过弱引用的hashmap来存储资源,Key是缓存key,ResourceWeakReference代表资源,它继承WeakReference。首先从弱引用的map获取图片资源,然后通过弱引用的get()方法获取最终需要的对象,如果activeRef.get();拿不到(可能已经被系统GC回收),那就clear(从弱引用中移除,清除资源等)。

    再回到刚才Engine的load方法中,如果loadFromActiveResources获取不到,会调用loadFromCache来获取。

    Engine#loadFromCache():从LruCache读取缓存

    public class Engine  {
      private final ActiveResources activeResources;
     //Lrucache对象
        private final MemoryCache cache;
    
    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
          return null;
        }
        //从lrucache获取
        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
          cached.acquire();
          //存到弱引用的HashMap
          activeResources.activate(key, cached);
        }
        return cached;
      }
    
    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, true /*isMemoryCacheable*/, true /*isRecyclable*/);
        }
        return result;
      }
    }
    

    逻辑比较简单,通过lrucache获取图片资源,如果获取到的话就会从LruCache中删除这张图片,然后会调用acquire()方法和activate()方法,其中activate()是把取到的数据会存到弱引用中,说白了就是把图片从LruCache转移到弱引用

    EngineResource# acquire()

    class EngineResource<Z> implements Resource<Z> {
      //图片引用计数器 
     private int acquired;
    
    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;
      }
    }
    

    这里只是将acquired+1,那这个acquired变量是什么意思呢?它实际上是图片引用计数器 ,EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,release()方法后面调用的时候会讲到。

    到这里,内存缓存的读取就说完了,下面讲一下内存缓存的写入。很明显缓存的写入是在加载图片之后,所以回到刚才Engine#load()方法

     public <R> LoadStatus load(//一系列参数) {
    
      //...省略
    
      //1.从弱引用读取内存缓存
       loadFromActiveResources()
    
        //2.从LruCache读取缓存
     loadFromCache();
    
        //3.通过EngineJob加载图片
    
        EngineJob<R> engineJob =
            engineJobFactory.build();
    
        DecodeJob<R> decodeJob =
            decodeJobFactory.build();
    
        jobs.put(key, engineJob);
    
        engineJob.addCallback(cb);
        //通过线程池加载图片
        engineJob.start(decodeJob);
    
      }
    

    这里有2个关键的对象,EngineJob和DecodeJob,EngineJob 内部维护了线程池,用来管理资源加载,当资源加载完毕的时候通知回调; DecodeJob 是线程池中的一个任务。最后通过start()方法加载图片,实际上是在DecodeJobrun()方法中完成的,当图片加载完成,最终会回调EngineJob#onResourceReady ()

    #EngineJob
    public void onResourceReady(Resource<R> resource, DataSource dataSource) {
        this.resource = resource;
        this.dataSource = dataSource;
        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;
    }
    

    EngineJob#handleResultOnMainThread ()

    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.
        //1.图片引用计数器+1    
        engineResource.acquire();
        //2.回调到EngineJob处理
        listener.onEngineJobComplete(this, key, engineResource);
    
        //noinspection ForLoopReplaceableByForEach to improve perf
        //3.遍历加载的图片
        for (int i = 0, size = cbs.size(); i < size; i++) {
          ResourceCallback cb = cbs.get(i);
          if (!isInIgnoredCallbacks(cb)) {
            //图片引用计数器+1    
            engineResource.acquire();
            //将资源回调给ImageView去加载
            cb.onResourceReady(engineResource, dataSource);
          }
        }
        // Our request is complete, so we can release the resource.
        //4.释放资源,图片引用计数器-1  
        engineResource.release();
    
        release(false /*isRemovedFromQueue*/);
      }
    

    这里一共有4步:
    1、图片引用计数器+1;
    2、listener.onEngineJobComplete(),这个listener是EngineJobListener接口对象,这里是将结果回调给Engine#onEngineJobComplete()处理;
    3、遍历遍历加载的图片,每加载到一张图片,引用计数器+1 ,并且会将资源回调给ImageView去加载;
    4、释放资源,图片引用计数器-1 。

    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()) {
            //把资源放到弱引用
            activeResources.activate(key, resource);
          }
        }
    
        jobs.removeIfCurrent(key, engineJob);
      }
    

    可以看到,这里把资源放到弱引用,也就是内存缓存的写入了。但是LruCache缓存貌似还没有出现,再回头看看刚才的Engine#onEngineJobComplete()方法,最后还调用了还调用了 engineResource.release()方法来释放资源,还记得之前讲过这个方法吗,在获取内存缓存的时候会调用acquire(),使得acquired+1;而调用release()方法会让acquired -1。

    EngineResource#release()

    class EngineResource<Z> implements Resource<Z> {
      //图片引用计数器 
     private int 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);
        }
      }
    }
    

    调用release()的时机主要是:加载网络图片时暂停请求/加载完毕以及清除资源。release()将acquired-1,并且当acquired==0的时候,会调用listener.onResourceReleased()方法,而这个listener正是Engine。

    Engine#onResourceReleased()

    public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
    
    @Override
      public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        Util.assertMainThread();
        //从弱引用集合activeResources中移除资源
        activeResources.deactivate(cacheKey);
        if (resource.isCacheable()) {
          //放入LruCache缓存
          cache.put(cacheKey, resource);
        } else {
          //回收资源
          resourceRecycler.recycle(resource);
        }
      }
    }
    

    这个onResourceReleased ()方法也不复杂,作用是释放资源,先从弱引用集合activeResources中移除资源,然后再把图片资源放入LruCache缓存。

    注意:在上面的调用EngineJob#handleResultOnMainThread ()去加载图片等时候,如果加载图片成功,那么acquired>=1,说明有图片正在被引用;而等到暂停请求/退出页面的时候再次调用release()时,acquired==0才会去调用onResourceReleased ()把缓存从弱引用转移到Lrucache。

    小结

    这个acquired变量是用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1。当调用loadFromActiveResources()loadFromCache()EngineJob#handleResultOnMainThread()获取图片的时候都会执行acquire()方法;当暂停请求或者加载完毕或者清除资源时会调用release()方法。

    注意:从弱引用取缓存,拿到的话,引用计数+1;从LruCache中拿缓存,拿到的话,引用计数也是+1,同时把LruCache缓存转移到弱应用缓存池中;从EngineJob去加载图片,拿到的话,引用计数也是+1,会把图片放到弱引用。反过来,一旦没有地方正在使用这个资源,就会将其从弱引用中转移到LruCache缓存池中。这也说明了正在使用中的图片使用弱引用来进行缓存,暂时不用的图片使用LruCache来进行缓存的功能。


    Glide磁盘缓存的读写

    Glide5大磁盘缓存策略
    DiskCacheStrategy.DATA: 只缓存原始图片;
    DiskCacheStrategy.RESOURCE:只缓存转换过后的图片;
    DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换过后的图片;对于远程图片,缓存 DATARESOURCE;对于本地图片,只缓存 RESOURCE
    DiskCacheStrategy.NONE:不缓存任何内容;
    DiskCacheStrategy.AUTOMATIC:默认策略,尝试对本地和远程图片使用最佳的策略。当下载网络图片时,使用DATA;对于本地图片,使用RESOURCE

    上面讲内存缓存写入的时候说到过,如果在内存缓存中没获取到数据,就通过DecodeJobEngineJob加载图片。EngineJob 内部维护了线程池,用来管理资源加载,当资源加载完毕的时候通知回调; DecodeJob 是线程池中的一个任务。

    DecodeJob#run()

    public void run() {
       ...
        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);
        }
      }
    

    这里会执行到runWrapper()方法,对于一个新的任务,会执行第一个分支。stage:用来决定 DecodeJob 状态,表示数据的加载状态;currentGenerator:是解析生成器,有多个实现类:ResourcesCacheGeneratorSourceGeneratorDataCacheGenerator,它们负责各种硬盘缓存策略下的缓存管理:

    • ResourceCacheGenerator:管理变换之后的缓存数据;
    • SourceGenerator:管理未经转换的原始缓存数据;
    • SourceGenerator:直接从网络下载解析数据。

    接下来会调用3个方法getNextStage getNextGenerator runGenerators()

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

    别怕啊,写到这里我也很恶心了,本来想要一笔带过的,但是发现网上没什么博客能说清楚这个问题,还是写一下吧。这个地方之所以这么麻烦,是因为缓存策略的原因

    还是回到上面的runWrapped()方法,它先是调用了getNextStage(Stage.INITIALIZE),于是进入getNextStage()第一个分支,根据缓存策略返回Stage,由于我用的是默认的缓存策略,这里decodeCachedResource返回true,于是getNextStage()方法返回Stage.RESOURCE_CACHE;然后执行getNextGenerator方法,根据上一步的stage,这里执行第一个分支,返回ResourceCacheGenerator,这个方法返回的3个Generator对象都是用于加载图片资源的;接着调用runGenerators()方法:它通过while循环来获取那3个解析生成器Generator,循环条件主要是currentGenerator.startNext(),它的实际调用在那3个Generator里面,在方法内部又会获取stage和currentGenerator,当stage == Stage.SOURCE时会跳出循环。如果是第一次从网络加载图片的话,最终数据的加载会交给 SourceGenerator 进行;如果是从磁盘缓存获取的话会根据缓存策略的不同从ResourceCacheGenerator或者DataCacheGenerator获取

    先来看一下ResourceCacheGeneratorstartNext()方法,果不其然,里面先是构建了缓存key,然后从DiskLruCache获取到了缓存图片。

    #ResourceCacheGenerator
      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;
          }
        }
        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( // 1 构建获取缓存信息的键
                  helper.getArrayPool(),
                  sourceId,
                  helper.getSignature(),
                  helper.getWidth(),
                  helper.getHeight(),
                  transformation,
                  resourceClass,
                  helper.getOptions());
          cacheFile = helper.getDiskCache().get(currentKey); // 2 从缓存中获取缓存信息
          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++); // 3 使用文件方式从缓存中读取缓存数据
          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获取的缓存图片,其实DataCacheGenerator也是差不多的。到这里磁盘缓存读取就说完了,下面来看一下磁盘缓存的写入。不用想,是第一次从网络加载图片时写入的,第一次从网络加载时,会调用SourceGenerator#startNext()方法:

    #SourceGenerator
    public boolean startNext() {
        //判断是否有可以用于缓存数据
        if (dataToCache != null) {
          Object data = dataToCache;
          dataToCache = null;
          //调用disklrucache缓存
          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;
            //DataFetcher加载数据
            loadData.fetcher.loadData(helper.getPriority(), this);
          }
        }
        return started;
      }
    

    这里用的是SourceGeneratorstartNext()方法,其它2个Generator的实现是不一样的。先判断是否有可以用于缓存的数据,由于是第一次加载网络图片,所以是没有的,然后通过DataFetcher加载数据,具体来说网络的话是通过HttpUrlFetcher来实现的。

    HttpUrlFetcher#loadData()

    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());
          //在 SourceGenerator 回调
          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));
          }
        }
      }
    

    通过HttpURLConnection来获取InputStream,然后在 SourceGenerator 回调。

    SourceGenerator #onDataReady ()

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

    这里先是保存数据,然后回调到DecodeJob中, 将会根据当前的stage从 run() 方法开始执行一遍,并再次调用SourceGeneratorstartNext() 方法。这次已经存在可以用于缓存的数据了。所以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());
          //DiskLrucache保存图片
          helper.getDiskCache().put(originalKey, writer);
          if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Finished encoding source to cache"
                + ", key: " + originalKey
                + ", data: " + dataToCache
                + ", encoder: " + encoder
                + ", duration: " + LogTime.getElapsedMillis(startTime));
          }
        } finally {
          loadData.fetcher.cleanup();
        }
    
        sourceCacheGenerator =
            new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
      }
    

    这里通过DiskCache对象,实际上是DiskLruCacheWrapper实现类对象在磁盘缓存图片。

    小结
    磁盘缓存是在EngineJob中的DecodeJob任务中完成的,依次通过ResourcesCacheGeneratorSourceGeneratorDataCacheGenerator来获取缓存数据。ResourcesCacheGenerator获取的是转换过的缓存数据;SourceGenerator获取的是未经转换的原始的缓存数据;DataCacheGenerator是通过网络获取图片数据再按照按照缓存策略的不同去缓存不同的图片到磁盘上。

    到这里,总算写完了整体流程分析。不过一大堆,你基本不太可能记得住,下面就给大家总结一下吧:

    总结(干货)

    Glide缓存分为弱引用+ LruCache+ DiskLruCache,其中读取数据的顺序是:弱引用 > LruCache > DiskLruCache>网络;写入缓存的顺序是:网络 --> DiskLruCache--> LruCache-->弱引用

    内存缓存分为弱引用的和 LruCache ,其中正在使用的图片使用弱引用缓存,暂时不使用的图片用 LruCache缓存,这一点是通过 图片引用计数器(acquired变量)来实现的,详情可以看内存缓存的小结。

    磁盘缓存就是通过DiskLruCache实现的,根据缓存策略的不同会获取到不同类型的缓存图片。它的逻辑是:先从转换后的缓存中取;没有的话再从原始的(没有转换过的)缓存中拿数据;再没有的话就从网络加载图片数据,获取到数据之后,再依次缓存到磁盘和弱引用。

    参考:
    Android Glide4.0 源码遨游记(第五集——缓存机制)
    Android Glide4.0 源码遨游记(第四集)
    Glide 系列-3:Glide 缓存的实现原理(4.8.0)
    Glide 系列-2:主流程源码分析(4.8.0)

    相关文章

      网友评论

        本文标题:Android 【手撕Glide】--Glide缓存机制

        本文链接:https://www.haomeiwen.com/subject/eblkactx.html