美文网首页
Glide缓存原理

Glide缓存原理

作者: freelifes | 来源:发表于2023-06-24 14:45 被阅读0次

    glide缓存分为内存缓存和磁盘缓存。内存缓存分为活动缓存和cache。磁盘缓存又分为resource和data。本文将围绕加载图片流程介绍glide的缓存。

     Glide.with(this)
              .load(url)
               .skipMemoryCache(true)
                // .transform(CircleCrop())
               // .error(R.drawable.ic_launcher_background)
              .diskCacheStrategy(DiskCacheStrategy.NONE)
              .listener(getListener(index))
              .into(mIv)
    

    1、load

    public <R> LoadStatus load(
          GlideContext glideContext,
          Object model) {
        long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
    
        EngineKey key =
            keyFactory.buildKey(
                model,
                signature,
                width,
                height);
    
        EngineResource<?> memoryResource;
        synchronized (this) {
          memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
    
          if (memoryResource == null) {
            return waitForExistingOrStartNewJob(
                glideContext,
                model,
                signature,
                width,
                height);
          }
        }
      cb.onResourceReady(
            memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
        return null;
      }
    

    1.1、loadFromMemory 先从内从中加载。

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

    可以看到从内存加载又分为活动缓存和 cache
    1.11、活动缓存
    举例: 一个LinearLayout中有10个imageview,通过glide加载。那么这10个图片就缓存在activityResource中。 当点击的条目,删除一个item. 这个ActivityResource缓存并没有被移除。当gc运行时,将这个item回收掉之后了,就会将这个缓存加入到resourceReferenceQueue,然后通过遍历这个引用队列,从活动缓存中移除。
    举例: 当在recyclerview的每个item加载一个imagview。 那么这个活动缓存的最大数目为= 可见数据+ 预加载数目 + 复用池1 ,因为从recyclerview移除之后,不一定就被立即回收了,当从复用池中复用item时,会主动释放上一次item对应的活动缓存。此时活动缓存就减少了。
    活动缓存是何时存入的了?
    1)、从缓存中获取到资源时,加入到活动缓存。

     private EngineResource<?> loadFromCache(Key key) {
        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
          cached.acquire();
          activeResources.activate(key, cached);
        }
        return cached;
      }
    

    2)、从网络和磁盘加载资源完成时,加入到活动缓存。
    活动缓存何时删除的了?
    1)、onDestroy时,request调用clear() 会释放当前对应key的活动缓存。
    2)、不能设置资源给target,释放当前key的活动缓存。
    3)、获取活动缓存,发现该活动缓存EngineResource不存在,就删除key的活动缓存。也就是setIsActiveResourceRetentionAllowed(false)的情况。
    4)、创建ActiveResources对象时,就开启了一个线程,一直轮询这个queue,如果存在的EngineResource,就删除对应的活动缓存。
    1.12、内存缓存
    获取内存时就从内存缓存中移除。

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

    何时加入内存
    从活动缓存移除就加入了内存缓存。内存缓存超过容量后被释放的bitmap等会被加入到bitmapPool中。从活动缓存中移除的要么加入内存缓存,要么加入到bitmapPool中。

    //Engine
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        activeResources.deactivate(cacheKey);
        if (resource.isMemoryCacheable()) {
         //加入内存缓存
          cache.put(cacheKey, resource);
        } else {
       //资源释放,bitmap支持复用则加入到bitmapPool
          resourceRecycler.recycle(resource, /* forceNextFrame= */ false);
        }
      }
    
    
    //BitmapResource
    @Override
      public void recycle() {
        bitmapPool.put(bitmap);
      }
    

    缓存资源的key是通过宽高,加载图片的model、options等生成的。所以宽高变化,可能导致内存中找不到这张图。
    1.2、内存不存在,开启一个job从网络加载加载。

      private <R> Engine.LoadStatus waitForExistingOrStartNewJob(
                GlideContext glideContext,
                Object model,
                Key signature,
                int width,
                int height) {
    
            EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
            if (current != null) {
                current.addCallback(cb, callbackExecutor);
                return new Engine.LoadStatus(cb, current);
            }
    
            EngineJob<R> engineJob =
                    engineJobFactory.build(
                            key,
                            isMemoryCacheable,
                            useUnlimitedSourceExecutorPool,
                            useAnimationPool,
                            onlyRetrieveFromCache);
    
            DecodeJob<R> decodeJob =
                    decodeJobFactory.build(
                            glideContext,
                            model,
                            key,
                            signature,
                            width,
                            height,
                            engineJob);
            jobs.put(key, engineJob);
            engineJob.addCallback(cb, callbackExecutor);
            engineJob.start(decodeJob);
            return new Engine.LoadStatus(cb, engineJob);
        }
    

    可以看到先判断,是否是从从缓存中取获取缓存job。
    如果缓存的job没有找到。则创建一个job。并且执行这个RunableJob。

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

    所以接下来,我们看下这个runnable(DecodeJob)的run方法。

     public void run() {
            DataFetcher<?> localFetcher = currentFetcher;
            try {
                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;
        }
      }
    
     private void runGenerators() {
        currentThread = Thread.currentThread();
        startFetchTime = LogTime.getLogTime();
        boolean isStarted = false;
        while (!isCancelled
            && currentGenerator != null
            && !(isStarted = currentGenerator.startNext())) {
          stage = getNextStage(stage);
          currentGenerator = getNextGenerator();
      }
    
     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;
      }
    
      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);
        }
      }
    

    可以看到,根据当前的状态和diskCacheStrategy磁盘策略的配置,来决定是加载RESOURCE_CACHE、DATA_CACHE、SOURCE。
    RESOURCE_CACHE : 是转换过或者改变过采样率的文件。解码之后的。
    DATA_CACHE:原始的未修改的数据。解码之前的。
    SOURCE :网络文件,源文件。
    1.3、INITIALIZE
    如果配置全部允许,会首先去加载Resource_cache,再加载DATA_CACHE,如果都加载不了,再从网络加载SOURCE。
    ResourceCacheGenerator和DataCacheGenerator会从磁盘缓存中加载数据,这里不看了,看下从Remote加载,SourceGenerator从网络加载数据。

    // SourceGenerator
    public boolean startNext() {
            if (dataToCache != null) {
                Object data = dataToCache;
                boolean isDataInCache = cacheData(data);
                dataToCache = null;
            }
            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;
                    startNextLoad(loadData);
                }
            }
            return started;
        }
    
        private void startNextLoad(final ModelLoader.LoadData<?> toStart) {
            loadData.fetcher.loadData(
                    helper.getPriority(),
                    new DataFetcher.DataCallback<Object>() {
                        @Override
                        public void onDataReady(@Nullable Object data) {
                            if (isCurrentRequest(toStart)) {
                                onDataReadyInternal(toStart, data);
                            }
                        }
    
                        @Override
                        public void onLoadFailed(@NonNull Exception e) {
                            if (isCurrentRequest(toStart)) {
                                onLoadFailedInternal(toStart, e);
                            }
                        }
                    });
        }
    
      void onDataReadyInternal(LoadData<?> loadData, Object data) {
        DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
        if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
          dataToCache = data;
          cb.reschedule();
        } else {
          cb.onDataFetcherReady(
              loadData.sourceKey,
              data,
              loadData.fetcher,
              loadData.fetcher.getDataSource(),
              originalKey);
        }
      }
    

    首先从网络loadData.fetcher.loadData中加载,然后判断isDataCacheable()是否可以缓存data,这个配置是缓存策略中配置的。如果可以缓存,调用cb.reschedule()。
    1.4、SWITCH_TO_SOURCE_SERVICE

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

    这个方法会再次调用run()方法,状态变为SWITCH_TO_SOURCE_SERVICE从网络获取数据切换到从磁盘获取, 还会再次执行要这个类。 此时startNext方法中的dataToCache不为空,并且会调用将数据缓存到磁盘中,然后调用DataCacheGenerator.startNext(),调用磁盘缓存加载,返回磁盘加载的结果。

    // SourceGenerator
     private boolean cacheData(Object dataToCache) throws IOException {
            try {
                DataRewinder<Object> rewinder = helper.getRewinder(dataToCache);
                Object data = rewinder.rewindAndGet();
                Encoder<Object> encoder = helper.getSourceEncoder(data);
                DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, data, helper.getOptions());
                DataCacheKey newOriginalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
                DiskCache diskCache = helper.getDiskCache();
                diskCache.put(newOriginalKey, writer);
    
                if (diskCache.get(newOriginalKey) != null) {
                    originalKey = newOriginalKey;
                    sourceCacheGenerator =
                            new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
                    return true;
                } else {
                    cb.onDataFetcherReady(
                            loadData.sourceKey,
                            rewinder.rewindAndGet(),
                            loadData.fetcher,
                            loadData.fetcher.getDataSource(),
                            loadData.sourceKey);
                }
                return false;
            }
        }
    

    上面就是在SourceGenerator中缓存数据Stream到磁盘过程。
    1.5、DECODE_DATA
    从getNextStage分析完INITIALIZE、SWITCH_TO_SOURCE_SERVICE,接下看下DECODE_DATA状态,从网络请求对应的HttpGlideUrlLoader,返回的输入流数据是ContentLengthInputStream。
    从磁盘缓存对应的FileLoader,返回的输入流是文件输入流。
    对于这些流需要转化成Bitmap、Drawable等。
    decode的过程 就是将InputStream 转化成bitmap的过程。

    //DecodeJob
     private void decodeFromRetrievedData() {
        Resource<R> resource = null;
        try {
          resource = decodeFromData(currentFetcher, currentData, currentDataSource);
        } catch (GlideException e) {
        }
        if (resource != null) {
          notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
        } else {
          runGenerators();
        }
      }
     //DecodePath 
     public Resource<Transcode> decode(
          DataRewinder<DataType> rewinder,
          int width,
          int height,
          @NonNull Options options,
          DecodeCallback<ResourceType> callback)
          throws GlideException {
        Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
        Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
        return transcoder.transcode(transformed, options);
      }
    
    //DecodeJob
    <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
        @SuppressWarnings("unchecked")
        Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
        Transformation<Z> appliedTransformation = null;
        Resource<Z> transformed = decoded;
        if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
          appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
          transformed = appliedTransformation.transform(glideContext, decoded, width, height);
        }
    
        final EncodeStrategy encodeStrategy;
        final ResourceEncoder<Z> encoder;
        if (decodeHelper.isResourceEncoderAvailable(transformed)) {
          encoder = decodeHelper.getResultEncoder(transformed);
          encodeStrategy = encoder.getEncodeStrategy(options);
        } else {
          encoder = null;
          encodeStrategy = EncodeStrategy.NONE;
        }
    
        Resource<Z> result = transformed;
        boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
        if (diskCacheStrategy.isResourceCacheable(
            isFromAlternateCacheKey, dataSource, encodeStrategy)) {
          final Key key;
          switch (encodeStrategy) {
            case SOURCE:
              key = new DataCacheKey(currentSourceKey, signature);
              break;
            case TRANSFORMED:
              key =
                  new ResourceCacheKey(
                      decodeHelper.getArrayPool(),
                      currentSourceKey,
                      signature,
                      width,
                      height,
                      appliedTransformation,
                      resourceSubClass,
                      options);
              break;
            default:
              throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
          }
    
          LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
          deferredEncodeManager.init(key, encoder, lockedResult);
          result = lockedResult;
        }
        return result;
      }
    
    

    上面第一步是decode的流程,将stream转化成bitmap,设置采样率等,transformed,可以看到在解析完成调用了diskCacheStrategy.isResourceCacheable()这个方法主要是为了初始化Resouce_cache相关信息。

    // DecodeJob
    private void notifyEncodeAndRelease(
          Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
        GlideTrace.beginSection("DecodeJob.notifyEncodeAndRelease");
        try {
          Resource<R> result = resource;
          LockedResource<R> lockedResource = null;
          if (deferredEncodeManager.hasResourceToEncode()) {
            lockedResource = LockedResource.obtain(resource);
            result = lockedResource;
          }
          notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey)
          stage = Stage.ENCODE;
          try {
            if (deferredEncodeManager.hasResourceToEncode()) {
              deferredEncodeManager.encode(diskCacheProvider, options);
            }
          } finally {
          onEncodeComplete();
        } finally {
          GlideTrace.endSection();
        }
      }
    
    private void notifyComplete(
          Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
        setNotifiedOrThrow();
        callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey);
      }
    
      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();
          }
        }
    
    

    notifyComplete就是调用callback.onResourceReady这个方法最终就回调到我们给request设置的RequestListener中。
    上面我们知道缓存Resource相关key已经初始化了,接着deferredEncodeManager.encode将resource缓存在本地,整个加载过程就结束。
    上面我们一直提到磁盘缓存策略,那么我们看一下DiskCacheStrategy.ALL的代码.

     public static final DiskCacheStrategy ALL =
          new DiskCacheStrategy() {
            @Override
            public boolean isDataCacheable(DataSource dataSource) {
              return dataSource == DataSource.REMOTE;
            }
    
            @Override
            public boolean isResourceCacheable(
                boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
              return dataSource != DataSource.RESOURCE_DISK_CACHE
                  && dataSource != DataSource.MEMORY_CACHE;
            }
    
            @Override
            public boolean decodeCachedResource() {
              return true;
            }
    
            @Override
            public boolean decodeCachedData() {
              return true;
            }
          };
    

    isDataCacheable 表示可以将原始数据data缓存。
    isResourceCacheable 表示可以将Resouce数据缓存。
    decodeCachedResource 表示可以从Resource 中加载数据。
    decodeCachedData 表示可以从Data中加载数据。

    image.png

    上面这幅图展示了glide缓存加载流程,没有BitmapPool,BitmapPool 只是从磁盘缓存、网络加载图片时,decode图片需要复用的之前图片的内存,从bitmappool中获取。 活动内存释放、cache的大小超过了容量会加入到bitmapPool。

    相关文章

      网友评论

          本文标题:Glide缓存原理

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