美文网首页Android开发
glide内存缓存策略和本地文件缓存策略学习与思考

glide内存缓存策略和本地文件缓存策略学习与思考

作者: enjoycc97 | 来源:发表于2018-11-18 22:04 被阅读6次

    预备知识:

    假设已经熟悉了如下概念,不懂暂时这么理解也可以
    图片展示的Bitmap,Drawable这种最终能被安卓ImageView识别用来加载的对象,glide用抽象Resource表示,也就是内存缓存是一个个Resource对象

    流程加载如下

    Engine.load()分析
    1 加载图片先从内存当前展示缓存activeResources.get(key)读取
    2 加载图片再从内存MemoryCache读取,可能此缓存已经不在界面展示
    3 开启DecodeJob,本质上一个线程,所以会使用线程池加载,
    加载之前判断是否已经存在一个正在运行的加载线程,因为可能存在多个加载一模一样url的情况。
    这个线程内部先判断文件缓存是否存在,否则网络加载
    为方便理解,本文内存2种缓存类型,以展示缓存,不展示缓存区分

    /**
         Check the current set of actively used resources, return the active resource if
     present, and move any newly inactive resources into the memory cache
         Check the memory cache and provide the cached resource if present
         Check the current set of in progress loads and add the cb to the in progress load if one is present.
         Start a new load.
    
       * Active resources are those that have been provided to at least one request and have not yet
       * been released. Once all consumers of a resource have released that resource, the resource then
       * goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to
       * the active resources. If the resource is evicted from the cache, its resources are recycled and
       * re-used if possible and the resource is discarded. There is no strict requirement that
       * consumers release their resources so active resources are held weakly.
      **/
      public <R> LoadStatus load() {
        
        EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
            resourceClass, transcodeClass, options);
    //加载activeResource
        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
          cb.onResourceReady(active, DataSource.MEMORY_CACHE);
          return null;
        }
    //加载MemoryCache
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) {
          cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
          return null;
        }
    //当前已经存在一个 加载线程,直接添加监听即可
        EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
        if (current != null) {
          current.addCallback(cb);
          return new LoadStatus(cb, current);
        }
    //触发一个线程加载
        EngineJob<R> engineJob =
            engineJobFactory.build();
    
        DecodeJob<R> decodeJob =
            decodeJobFactory.build();
    
        jobs.put(key, engineJob);
    
        engineJob.addCallback(cb);
    //线程池加载线程
        engineJob.start(decodeJob);
        return new LoadStatus(cb, engineJob);
      }
    

    内存上的activeResource和MemoryCache区别

    如果当前加载一张图片成功,当前界面展示出来。
    此时Engine监听到完成,触发activeResource添加操作

    public 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.setResourceListener(key, this);
    
          if (resource.isCacheable()) {
            activeResources.activate(key, resource);
          }
    

    如果这个ImageView重新渲染一张图片,那么之前上一张图片资源就需要回收,
    类似这种情况,都会触发从activeResource删除,而往MemoryCache添加
    如RequestBuilder.into(),也就是最常见的Glide.with(context).load(url).into(ImgView);

      private <Y extends Target<TranscodeType>> Y into()
        options = options.autoClone();
        Request request = buildRequest(target, targetListener, options);
        Request previous = target.getRequest();
        if (request.isEquivalentTo(previous)
            && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
          request.recycle();
    ....
            previous.begin();
          }
          return target;
        }
    
        requestManager.clear(target);
        target.setRequest(request);
        requestManager.track(target, request);
        return target;
      }
    

    如上都是先clear再track,requestManager.clear(target);
    RequestManager 调用clear触发Request的clear,track触发Request的begin,
    这样再分析一下SingleRequest对应操作active缓存的删除,对应MemoryCache添加

      public void clear() {
        if (resource != null) {
          releaseResource(resource);
        }
      }
    
    private void releaseResource(Resource<?> resource) {
        engine.release(resource);
        this.resource = null;
      }
    
    void release() {
        if (--acquired == 0) {
    //监听器是Engine
          listener.onResourceReleased(key, this);
        }
      }
    
    @Override
      public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        activeResources.deactivate(cacheKey);
        if (resource.isCacheable()) {
          cache.put(cacheKey, resource);
        } else {
          resourceRecycler.recycle(resource);
        }
      }
    

    本地文件写入缓存时机

    一般内存与本地缓存都没有需要触发网络请求,那么进入SourceGenerator这个处理网络请求的,实际上是SourceFetcher处理,处理完成,走到SourceGenerator监听回调,
    这时候观察一下,思考一下逻辑,网络加载完成,便存入本地
    使用的也是LRU缓存。

      public boolean startNext() {
        if (dataToCache != null) {
          Object data = dataToCache;
          dataToCache = null;
          cacheData(data);
        }
    

    cacheData就是调用LRU文件触发保存文件缓存
    至于这个对象是从哪赋值的,参考如下,走的是回调,那回调是这么触发,一般每个Generator都是走内部Fetcher工作然后回调给自己

    @Override
      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();
        }
      }
    

    网络请求到本地缓存保存流程

    HttpUrlFetcher.java
     @Override
      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());
          callback.onDataReady(result);
        }
    

    这样一样。回调返回的是InputStream,那么就需要琢磨怎么把这个InputStream变成保存文件
    StreamEncode实现将InputStream转成文件。如下

      public boolean encode() {
        byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
        boolean success = false;
        OutputStream os = null;
        try {
          os = new FileOutputStream(file);
          int read;
          while ((read = data.read(buffer)) != -1) {
            os.write(buffer, 0, read);
          }
          os.close();
          success = true;
        } catch (){
         ...}
          byteArrayPool.put(buffer);
        }
        return success;
      }
    

    本地文件读取时机

    内存没有,则触发本地读取。
    如果通过映射关系找到本地已经有这个文件,需要加载这个文件资源。

      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);
        }
      }
    
    
     Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
          cacheFile = helper.getDiskCache().get(originalKey);
          if (cacheFile != null) {
            this.sourceKey = sourceId;
            modelLoaders = helper.getModelLoaders(cacheFile);
            modelLoaderIndex = 0;
          }
    

    LRU读写这里不再展开,只知道使用这种缓存策略

    原图,转图,也不展开了

    原图即是网络下载的图片,
    实际上展示可能需要转码压缩之类的

    总结

    • 内存分为当前已经展示的缓存和界面不展示但是内存会存下的缓存。
    • 耗时的操作放在单独线程操作,有本地文件缓存和网络获取。
    • 如果网络获取触发文件保存。
    • 一个imageview加载完成,触发active缓存添加
    • 一个imageview加载第二张图片,触发active缓存删除,MemoryCache缓存添加

    相关文章

      网友评论

        本文标题:glide内存缓存策略和本地文件缓存策略学习与思考

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