Glide -- 缓存策略及常见问题解决总结

作者:opLW
参考:郭神的Glide系列文章Android图片加载框架最全解析(三),深入探究Glide的缓存机制
注意:郭神的文章是Glide3.7版本,最新的Glide可能与文章内容会有不同。

目录

1.Glide的缓存策略
2.常见的与Glide缓存策略相关的问题

1.Glide的缓存策略
  • 内存缓存
    • 内存缓存主要分为两个方面:弱引用缓存和 LruCache缓存。
    • 下面的代码是Glide4.8的,不同点:4.8的是先查看弱引用缓存中有没有,没有就再查看LruCache缓存,而3.7则刚好相反。
    public <R> LoadStatus load(//省略大量参数...) {
      //省略部分代码
      EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
      if (active != null) {
        cb.onResourceReady(active, DataSource.MEMORY_CACHE);
        if (VERBOSE_IS_LOGGABLE) {
          logWithTimeAndKey("Loaded resource from active resources", startTime, key);
        }
        return null;
      }
    
      EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
      if (cached != null) {
        cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
        if (VERBOSE_IS_LOGGABLE) {
          logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        return null;
      }
      //省略部分代码
    }
    
    • 从弱引用缓存,LruCache缓存获取目标资源
     //Engine.java
     private EngineResource<?> loadFromActiveResources(Key key, 
     boolean isMemoryCacheable) {
     	if (!isMemoryCacheable) {
      		 return null;
     	}
     	EngineResource<?> active = activeResources.get(key); // 1
     	if (active != null) {
      		 active.acquire();
    	    }
    	    return active;  
     }
     
     private final ActiveResources activeResources; // 2
     
     //ActiveResources.java
     EngineResource<?> get(Key key) {
     ResourceWeakReference activeRef = activeEngineResources.get(key); // 3
     //省略部分代码
     }
     
     final Map<Key, ResourceWeakReference> activeEngineResources // 4
     = new HashMap<>();
    
     static final class ResourceWeakReference 
     extends WeakReference<EngineResource<?>> { //省略部分代码 }
     
     //Engine.java
     private EngineResource<?> loadFromCache(Key key, 
     boolean isMemoryCacheable) {
     	if (!isMemoryCacheable) {
      	    return null; // 设置不要内存缓存发挥作用的地方
    	    }
    
     	EngineResource<?> cached = getEngineResourceFromCache(key);
     	if (cached != null) {
             cached.acquire();
             activeResources.activate(key, cached); // 5
     	}
         return cached;
     }
     
     private EngineResource<?> getEngineResourceFromCache(Key key) {
     	Resource<?> cached = cache.remove(key); // 6
     	//省略部分代码
     	return result;
     }
     private final MemoryCache cache; //LruCache原理
    

    注意 上述的代码摘抄自多份文件。
    弱引用缓存 1,2,3,4序号可以清晰的看出弱引用缓存是从一个Map中获取,而这个Map存放着弱引用对象,即存放当前正在使用的目标资源。
    LruCache缓存 可以查看郭神的另外一篇文章:LruCache6从LruCache缓存中取得并移除目标资源。5 可知当调用Lrucache缓存获取到目标资源时,会将资源存放到弱引用缓存中。

    • 向弱引用缓存,LruCache缓存添加目标资源
     //Engine.java
     @Synthetic
    void handleResultOnMainThread() {
      //省略部分代码
      engineResource.acquire(); // 7
      listener.onEngineJobComplete(this, key, engineResource)//省略部分代码
      engineResource.release(); // 7
    }
    
    @Override
    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); // 8
        }
      }
    
      jobs.removeIfCurrent(key, engineJob);
    }
    
    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
      if (!isMemoryCacheable) {
        return null;
      }
    
      EngineResource<?> cached = getEngineResourceFromCache(key);
      if (cached != null) {
        cached.acquire();
        activeResources.activate(key, cached); // 9
      }
      return cached;
    }
    
    //ActiveResources.java
    void activate(Key key, EngineResource<?> resource) {
      ResourceWeakReference toPut =
          new ResourceWeakReference(
              key,
              resource,
              getReferenceQueue(),
              isActiveResourceRetentionAllowed);
    
      ResourceWeakReference removed = activeEngineResources.put(key, toPut);  // 10
      if (removed != null) {
        removed.reset();
      }
    }
    

    向弱引用缓存添加内容 8,9调用了弱引用缓存的active方法,而10active方法最终是向弱引用缓存Map添加弱引用对象。
    那么何时调用activite方法呢?handleResultOnMainThread,该方法是在异步加载完图片之后通知主线程更新结果,那么这个时候就会调用onEngineJobComplete,进而调用activite方法。②loadFromCache,该方法是在从LruCache获取内容之后调用的。这两个方法都将刚用到的图片添加到弱引用缓存。
    细心的朋友可能发现了上面还有个7序号,下面我们将由这个7引入向LruCache缓存添加内容的操作

    //EngineResource.java
    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;
    }
    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);
      }
    }
     //Engine.java
     private final MemoryCache cache; //LruCache原理
     
     public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
      Util.assertMainThread();
      activeResources.deactivate(cacheKey);
      if (resource.isCacheable()) {
        cache.put(cacheKey, resource); // 11
      } else {
        resourceRecycler.recycle(resource);
      }
    }
    

    向LruCache缓存添加内容 EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,也就是说,当acquired变量大于0的时候,说明图片正在使用中,也就应该放到activeResources弱引用缓存当中。而经过release()之后,如果acquired变量等于0了,说明图片已经不再被使用了,那么此时会在第24行调用listener的onResourceReleased()方法来释放资源,这个listener就是Engine对象。我们可以看到在11处向LruCache的实例对象cache添加资源。

  • 硬盘缓存
    • 硬盘缓存策略
      • DiskCacheStrategy.NONE: 表示不缓存任何内容。
      • DiskCacheStrategy.RESOURCE: 在资源解码将数据写入磁盘缓存,即经过缩放等转换后的图片资源。
      • DiskCacheStrategy.DATA: 在资源解码将原始数据写入磁盘缓存。
      • DiskCacheStrategy.ALL : 使用DATA和RESOURCE缓存远程数据,仅使用RESOURCE来缓存本地数据。
      • DiskCacheStrategy.AUTOMATIC:它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。默认使用这种缓存策略
      • 通过RequestOptions的.diskCacheStrategy(参数)方法设置。

    硬盘缓存这块在4.8已经大不相同了,看的一脸**,暂时先缓缓后面有机会补上。?

  • 总结
    • 引用简书上另外一篇文章Glide4.9缓存策略的图片在这里插入图片描述
    • 至于为什么要有弱引用缓存?

    郭神的看法是: LruCache算法的实现,你会发现它其实是用一个Set来缓存对象的,每次内存超出缓存设定触发trim操作的时候,其实是对这个Set进行遍历,然后移除缓存。但是我们都知道Set是无序的,因此遍历的时候有可能会把正在使用的缓存给误伤了,我还在用着它呢就给移出去了。因此这个弱引用可能是对正在使用中的图片的一种保护,使用的时候先从LruCache里面移出去,用完了再把它重新加到缓存里面。

2.常见的与Glide缓存策略相关的问题

声明 下面仅收集一些大体的解决思路,尚未实际操作过,如有纰漏,还望指出。

  • 省流量模式
    • 情景: 省流量模式的应用情景就是减少不必要图片的加载。
    • 解决办法: 我们可以在RequestOptions中加入onlyRetrieveFromCache(true)的选项。那么图片就只会从缓存中读取,如果没有缓存则不加载图片,从而达到减少流量消耗的目的。
  • 头像没有及时更新的问题
    • 情景: 开发一款有头像的APP,我们修改了头像并且更新到了服务端,可是当我们点击查看大图时加载出来的还是原来的头像。
    • 解决办法: 这是Glide强大的缓存带来的副作用,我们可以在RequestOptions中加入.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)的选项。那么缓存的功能就会全部关闭,从而使得每次都是从服务端加载,所以头像会是最新。
  • URL重定向
    • 情景: 没有实际遇到过,但大体的意思应该是同一个URL在不同的时间可能会指向不同的资源,所以同样需要实时更新。
    • 解决办法: 同上。
  • 通过URL获取到该资源在本地的缓存

    该办法是Glide3.7版本的,尚未使用过,暂做一个记录。

  • 降低缓存图片的质量,减少内存消耗
    • 情景: 相册类的App经常需要同时展示大量的图片,这种情况下图片的质量可以低一点,因为加载速度优先于图片的质量。
    • 解决办法: 我们可以设置译码的格式,在RequestOptions中加入.encodeFormat(Bitmap.CompressFormat.WEBP).encodeQuality(10))的选项,①encodeFormat的参数有Bitmap.CompressFormat.PNG,Bitmap.CompressFormat.JPEG,Bitmap.CompressFormat.WEBP(质量从高到低);②encodeQuality设置的是0-100的int类型,一个质量百分比参数,越小质量越低。

万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。

opLW原创七言律诗,转载请注明出处

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值