作者: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缓存 可以查看郭神的另外一篇文章:LruCache 。6从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(参数)
方法设置。
- 硬盘缓存策略
- 总结
- 引用简书上另外一篇文章Glide4.9缓存策略的图片
- 至于为什么要有弱引用缓存?
郭神的看法是: LruCache算法的实现,你会发现它其实是用一个Set来缓存对象的,每次内存超出缓存设定触发trim操作的时候,其实是对这个Set进行遍历,然后移除缓存。但是我们都知道Set是无序的,因此遍历的时候有可能会把正在使用的缓存给误伤了,我还在用着它呢就给移出去了。因此这个弱引用可能是对正在使用中的图片的一种保护,使用的时候先从LruCache里面移出去,用完了再把它重新加到缓存里面。
2.常见的与Glide缓存策略相关的问题
声明 下面仅收集一些大体的解决思路,尚未实际操作过,如有纰漏,还望指出。
- 省流量模式
- 情景: 省流量模式的应用情景就是减少不必要图片的加载。
- 解决办法: 我们可以在
RequestOptions
中加入onlyRetrieveFromCache(true)
的选项。那么图片就只会从缓存中读取,如果没有缓存则不加载图片,从而达到减少流量消耗的目的。
- 头像没有及时更新的问题
- 情景: 开发一款有头像的APP,我们修改了头像并且更新到了服务端,可是当我们点击查看大图时加载出来的还是原来的头像。
- 解决办法: 这是Glide强大的缓存带来的副作用,我们可以在
RequestOptions
中加入.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
的选项。那么缓存的功能就会全部关闭,从而使得每次都是从服务端加载,所以头像会是最新。
- URL重定向
- 情景: 没有实际遇到过,但大体的意思应该是同一个URL在不同的时间可能会指向不同的资源,所以同样需要实时更新。
- 解决办法: 同上。
- 通过URL获取到该资源在本地的缓存
- 解决办法: Glide获取某个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原创七言律诗,转载请注明出处