缓存分为磁盘和内存两种,上一篇讲了磁盘缓存,其中提到了图片缓存到本地后,在 EngineJob 中切回主线程,handleResultOnMainThread() 中会把图片缓存到内存中,同时把图片回调到上一层,传给view,这里注意下保存到内存中的方法 listener.onEngineJobComplete(key, engineResource)
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
jobs.remove(key);
}
Glide 的内存分为 HashMap软引用 + LruCache 两种,这里的 activeResources 就是软引用缓存。内存为什么分两种,有什么好处?如果单独使用 LruCache,假如新图片所占内存比较大,甚至超过了 LruCache 的最大值,或者说 LruCache 中有一些图片,剩余的内存不多了,新图片添加后,就会新图片添加不到 LruCache 中,或者回收 LruCache 之前的图片,如果回收的图片正在UI上显示着,效果就不好了。
先分析从内存中取图片,再分析往内存中存图片。Glide 加载图片,会先从 LruCache 中获取,如果找到了,会把图片从 LruCache 中移除,放入软引用中;如果 LruCache 中没找到,继续从软引用中找;Engine 中 load() 方法,是获取图片的入口
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
...
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached);
return null;
}
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active);
return null;
}
...
}
这里可以看到,一系列view的参数及配置数据组成了一个key,经过 loadFromCache() 和 loadFromActiveResources() 两层获取,loadFromCache() 是从 LruCache 中获取,loadFromActiveResources() 是从软引用中获取,分别看下代码
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
final EngineResource result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource) cached;
} else {
result = new EngineResource(cached, true );
}
return result;
}
isMemoryCacheable 这个值是控制是否使用内存的开关,在 Glide 加载图片时 skipMemoryCache(boolean skip) 设置的,这里 isMemoryCacheable=!skip; 注意 getEngineResourceFromCache() 方法第一行代码,这里是 cache.remove(key),是移除,然后添加到 activeResources 中,并调用了 cached.acquire() 方法,计数加一,表示有一个地方使用。再看看 loadFromActiveResources()
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = null;
WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
if (activeRef != null) {
active = activeRef.get();
if (active != null) {
active.acquire();
} else {
activeResources.remove(key);
}
}
return active;
}
这里是直接从软引用中获取,如果有值,也是 active.acquire() 计数加一;可能有同学有疑惑了,LruCache 中如果有值会添加到软引用中,如果没有值,再来内存中查找,为什么这么设计?假如有两个相同大小的 ImageView 在两个不同的页面加载相同的url图片,一个先加载了,根据开头的逻辑,知道服务端返回的图片会添加到软引用中,第二个加载url时,此时先检查LruCache中没有,然后检查软引用,会在它里面找到图片返回。看到这,是否有疑惑,LruCache 移除了图片放到
软引用中,那软引用释放了内存,LruCache 中也没了,还得从磁盘中重新获取?google 厉害着呢,不会犯这个低级错误,软引用包裹的图片,会在释放资源被回收时,重新把图片放入 LruCache 缓存中。这就得说说如何缓存内存了。
软引用 Map<Key, WeakReference<EngineResource<?>>> activeResources = new HashMap<Key, WeakReference<EngineResource<?>>>(); 可以看出 WeakReference里面存储的是 EngineResource 类型,往 activeResources 中添加数据时发现 activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue())),ResourceWeakReference 继承了 WeakReference,添加图片的同时,也往里面传了一个 ReferenceQueue 队列
private static class ResourceWeakReference extends WeakReference<EngineResource<?>> {
private final Key key;
public ResourceWeakReference(Key key, EngineResource<?> r, ReferenceQueue<? super EngineResource<?>> q) {
super(r, q);
this.key = key;
}
}
private ReferenceQueue<EngineResource<?>> getReferenceQueue() {
if (resourceReferenceQueue == null) {
resourceReferenceQueue = new ReferenceQueue<EngineResource<?>>();
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new RefQueueIdleHandler(activeResources, resourceReferenceQueue));
}
return resourceReferenceQueue;
}
并且 ReferenceQueue 被创建的时候,又和 Looper 的队列产生了关联,先说 ResourceWeakReference 和 ReferenceQueue 的关系,它们两个会产生关联,ResourceWeakReference 构造方法中的形参 r 和 q 关联了起来,如果 r 对象被软引用释放了,那么 activeResources 集合中对应的 value 即 ResourceWeakReference 会被添加到 q 集合中,我写两个简单的 java 例子
private static void test1(){
ReferenceQueue<String> resourceReferenceQueue = new ReferenceQueue<>();
String s1 = new String("s1");
String s2 = new String("s2");
String s3 = new String("s3");
HashMap<String, WeakReference<String>> hashMap = new HashMap<>();
hashMap.put("a", new ResourceWeakReference("a", s1, resourceReferenceQueue));
hashMap.put("b", new ResourceWeakReference("b", s2, resourceReferenceQueue));
hashMap.put("c", new ResourceWeakReference("c", s3, resourceReferenceQueue));
System.out.println(hashMap +" " + resourceReferenceQueue.poll());
System.out.println("**************" );
s1 = null;
s3 = null;
System.gc();
System.gc();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(resourceReferenceQueue.poll());
System.out.println(resourceReferenceQueue.poll());
System.out.println(resourceReferenceQueue.poll());
System.out.println("**************" );
for (String s : hashMap.keySet()){
System.out.println(s + " " + hashMap.get(s) + " " + hashMap.get(s).get());
}
}
private static class ResourceWeakReference extends WeakReference<String> {
private final String key;
// q 里面的对象,实际是外层的 ResourceWeakReference
public ResourceWeakReference(String key, String r, ReferenceQueue<String> q) {
super(r, q);
this.key = key;
}
}
创建 s1、s2、s3 三个对象,添加到软引用集合中,然后切断其中两个值的引用,此时强制gc,看打印的日志
{a=com.example.cn.desigin.test.RxTest$ResourceWeakReference@404b9385, b=com.example.cn.desigin.test.RxTest$ResourceWeakReference@6d311334,
c=com.example.cn.desigin.test.RxTest$ResourceWeakReference@682a0b20} null
**************
com.example.cn.desigin.test.RxTest$ResourceWeakReference@682a0b20
com.example.cn.desigin.test.RxTest$ResourceWeakReference@404b9385
null
**************
a com.example.cn.desigin.test.RxTest$ResourceWeakReference@404b9385 null
b com.example.cn.desigin.test.RxTest$ResourceWeakReference@6d311334 s2
c com.example.cn.desigin.test.RxTest$ResourceWeakReference@682a0b20 null
从打印的日志,第一行是集合中元素的值和队列中是否有值,最后三行是for循环,遍历Map集合中的value值。
getReferenceQueue() 方法中,往 Looper 队列中添加了 MessageQueue.IdleHandler 回调,queueIdle() 返回值是 true,说明只要UI一旦有变动,绘制完毕后就会触发这个回调,queue 它里面装的都是被释放的资源,在这里把图片从软引用中移除了
private static class RefQueueIdleHandler implements MessageQueue.IdleHandler {
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
private final ReferenceQueue<EngineResource<?>> queue;
public RefQueueIdleHandler(Map<Key, WeakReference<EngineResource<?>>> activeResources,
ReferenceQueue<EngineResource<?>> queue) {
this.activeResources = activeResources;
this.queue = queue;
}
@Override
public boolean queueIdle() {
ResourceWeakReference ref = (ResourceWeakReference) queue.poll();
if (ref != null) {
activeResources.remove(ref.key);
}
return true;
}
}
这里只是清除,那么怎么把软引用中的图片重新添加到LruCache中呢? EngineResource 有 acquire() 和 release() 方法,acquired 是int值,这两个方法分别是计数加一和减一,使用图片加一,页面关闭就减一,当 acquired 为0时,就会触发 listener.onResourceReleased(key, this) 回调,对应的是 Engine 中的
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
在这里,重新加入 LruCache 中。 acquire() 方法在获取图片后回调给上一层时调用,那么 release() 呢? Engine 中有个 release(Resource resource) 方法调用了 EngineResource 的 release() 方法,那么 Engine 中方法又是哪里调用呢? 是在 GenericRequest 中,这个是准备请求信息的类中有几处调用,这里要注意clear()方法,它有好几处调用,有一处是 RequestManager 中,当Activity关闭页面,随着 Activity 的声明周期变化,会执行RequestManager的 onDestroy() 方法
@Override
public void onDestroy() {
requestTracker.clearRequests();
}
//RequestTracker
public void clearRequests() {
for (Request request : Util.getSnapshot(requests)) {
request.clear();
}
pendingRequests.clear();
}
request 就是 GenericRequest,看到这也明白了Glide是如何控制图片的声明周期了。
从缓存中取图片,重要的便是标识 EngineKey key,它是在 Engine 的 load() 方法中创建,里面包含 id、width、height 等信息,Glide根据尺寸缓存图片也是由这里控制的,我们看看id是什么,它是由 fetcher.getId() 返回的,直接一步到位,看 HttpUrlFetcher 中的 getId()
@Override
public String getId() {
return glideUrl.getCacheKey();
}
// GlideUrl
public String getCacheKey() {
return stringUrl != null ? stringUrl : url.toString();
}
我们传进去一个String类型的url,会先转换为Uri,然后在 UriLoader 中 new GlideUrl(model.toString()) 转换为 GlideUrl 对象,这里id对应的就是一开始传进来的url的值,这也就是缓存是以 url 为准的。 一般我们的url都是固定不变的,但也有些例外,比如把图片上传到三方的云端,有些云服务器为了提高图片的查找效率,会在图片后面拼接一些自己的参数,关键这些参数是可变的,这就导致多个url对应同一张图片,如果按照Glide现在的逻辑,就会造成浪费,缓存效果很差。
怎么办呢?既然知道了图片的加载流程与id的获取方式,那么我们自己直接定义个 GlideUrl 的子类来封装url,重写 getCacheKey() 方法,在它里面对url做处理
static class OGlideUrl extends GlideUrl {
String url;
public OGlideUrl(String url) {
super(url);
this.url = url;
}
@Override
public String getCacheKey() {
//去除Glide缓存key中多余的参数,这里是举个例子,以自己实际业务为主
if (url.contains("?")) {
return url.substring(0, url.lastIndexOf("?"));
} else {
return url;
}
}
}
Glide.with(this).load(new OGlideUrl("http://static.v5kf.com/images/v5_chat/eval/pic.jpg")).into(iv);
有些童鞋可能就纳闷了,Glide 的构造方法中没有注册 OGlideUrl 类型的请求方法,这个需要我们自定义 GlideModule 中 registerComponents() 方法中注册 glide.register(OGlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
吗?答案是不需要,因为 Glide.with(this).load() 方法,调用
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
...
return optionsApplier.apply(
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}
注意方法中第一行代码,进一步调用的是 GenericLoaderFactory 中的 getFactory(Class<T> modelClass, Class<Y> resourceClass) 方法,此时 modelClass 是 OGlideUrl.class 类型,
private <T, Y> ModelLoaderFactory<T, Y> getFactory(Class<T> modelClass, Class<Y> resourceClass) {
...
if (result == null) {
for (Class<? super T> registeredModelClass : modelClassToResourceFactories.keySet()) {
if (registeredModelClass.isAssignableFrom(modelClass)) {
Map<Class, ModelLoaderFactory> currentResourceToFactories = modelClassToResourceFactories.get(registeredModelClass);
if (currentResourceToFactories != null) {
result = currentResourceToFactories.get(resourceClass);
if (result != null) {
break;
}
}
}
}
}
return result;
}
for循环中,注意 registeredModelClass.isAssignableFrom(modelClass) 这行代码,意思是 modelClass 是 registeredModelClass 对象本身或子类,都为true,看到这就明白了,这里传 Glide.with(this).load(OGlideUrl) 和 Glide.with(this).load(GlideUrl) 对应的都是 ModelLoaderFactory 是相同的,所以自定义对象继承 GlideUrl 即可,其他的不用操作。