转载请注明出处:
源码分析glide中三层存储机制并与常规三层缓存机制对比
地址:http://blog.csdn.net/qq_22744433/article/details/78412786
目录
常规三层缓存机制
三级缓存的流程
强引用->软引用->硬盘缓存
当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,如果LruCache中没有,则去SoftReference中寻找(软引用适合当cache,当内存吃紧的时候才会被回收。而weakReference在每次system.gc()就会被回收)(当LruCache存储紧张时,会把最近最少使用的数据放到SoftReference中),如果SoftReference中有,则从SoftReference中取出图片使用,同时将图片重新放回到LruCache中,如果SoftReference中也没有图片,则去硬盘缓存中中寻找,如果有则取出来使用,同时将图片添加到LruCache中,如果没有,则连接网络从网上下载图片。图片下载完成后,将图片保存到硬盘缓存中,然后放到LruCache中。(参考这里)
强引用
强引用不能是随便的一个map数组,因为还要处理最近不用的数据。一般用的是LruCache(这是内存存储,以前一直以为这是disk硬盘存储,囧,那个叫DiskLruCache),里面持有一个LinkedHashMap,需要将持有的对象进行按时间排序,这样才能实现Lru算法(Least Recently Used),也就是当达到最大的存储限制时,把最近最少使用的移除。使用Lrucache需要实现的最主要的方法(参考这里):
- entryRemoved(boolean evicted, String key, T oldValue, T newValue)这个作用是,当存储满了以后,需要移除一个最近不使用的。也就是这个oldValue,我们可以把这个oldValue存到本地或者变成弱引用。或者直接oldValue==null(因为LruCache只是把最近不使用的那个移出map,并没有置为null即没有移除内存,这个如果需要的话得自己处理)
- create(String key)这个就是当用户从内存中获取一个value的时候,没有找到,可以在这里生成一个,之后会放cache中。不过我们一般会用put进行放置,所以这里不实现也ok。
- int sizeOf(K key, V value)//这个方法要特别注意,跟我们实例化 LruCache 的 maxSize 要呼应,怎么做到呼应呢,比如 maxSize 的大小为缓存的个数,这里就是 return 1就 ok,如果是内存的大小,如果5M,这个就不能是个数 了,这是应该是每个缓存 value 的 size 大小,如果是 Bitmap,这应该是 bitmap.getByteCount();
软引用
当图片被LruCache移除的时候,我们需要手动将这张图片添加到软引用map(SoftReference)中,也就是在刚才提到的entryRemoved()中做这件事。我们需要在项目中维护一个由SoftReference组成的集合,其中存储被LruCache移除出来的图片。软引用的一个好处是当系统空间紧张的时候,软引用可以随时销毁(当然前提是这个内存对象除了软引用之外没有其他引用),因此软引用是不会影响系统运行的,换句话说,如果系统因为某个原因OOM了,那么这个原因肯定不是软引用引起的。例如:
private Map<String, SoftReference<Bitmap>> cacheMap=new HashMap<>();
@Override // 当有图片从LruCache中移除时,将其放进软引用集合中
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null) {
SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
cacheMap.put(key, softReference);
}
glide中三层缓存机制
glide作为一个优秀的图片加载开源库,使用的就是三级存储机制。下面就详细说下(稍微和一般的三层缓存机制不一样):
三层存储的机制在Engine中实现的。先说下Engine是什么?Engine这一层负责加载时做管理内存缓存的逻辑。持有MemoryCache、Map[Key, WeakReference]。通过load()来加载图片,加载前后会做内存存储的逻辑。如果内存缓存中没有,那么才会使用EngineJob这一层来进行异步获取硬盘资源或网络资源。EngineJob类似一个异步线程或observable。Engine是一个全局唯一的,通过Glide.getEngine()来获取。
那么
在Engine类的load()方法前面有这么一段注释:
<p>
* The flow for any request is as follows:
* <ul>
* <li>Check the memory cache and provide the cached resource if present</li>
* <li>Check the current set of actively used resources and return the active resource if present</li>
* <li>Check the current set of in progress loads and add the cb to the in progress load if present</li>
* <li>Start a new load</li>
* </ul>
* </p>
*
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.
active resources存在的形式是弱引用:
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
MemoryCache是强引用的内存缓存,其实现类:
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache
说到这里,我们具体说下Engine类的load()方法:
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
这两句话是获取key,获取key会使用width,height,transform等,注意这里还会使用fetcher.getId()。这个和图片的url有关,可以自定义,所以可以进行动态url的存储。详细可以看这里.
接下来就是加载内存缓存了:
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
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;
}
@SuppressWarnings("unchecked")
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, true /*isCacheable*/);
}
return result;
}
大致获取缓存图片的流程是:如果Lruche中有,那么根据key把对应的EngineResource取出,并从Lruche中删除。当EngineResource从Lruche删除时,会马上回调一个onResourceRemoved()接口方法,这个方法在Engine中实现:
@Override
public void onResourceRemoved(final Resource<?> resource) {
Util.assertMainThread();
resourceRecycler.recycle(resource);
}
在resourceRecycler.recycle(resource);中使用handler将回收的任务给到了主线程,即在主线程对资源进行回收。当然在资源回收时,需要进行判断,看是不是还有其他地方对资源进行了引用,如果有,那么就不进行回收;没有没有,那么再进行回收。看下EngineResource:
@Override
public void recycle() {
if (acquired > 0) {//是否还有引用
throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
}
if (isRecycled) {
throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
}
isRecycled = true;
resource.recycle();
}
从前面的loadFromCache()代码可以看出,当把资源从Lruche中取出以后,会执行:
if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
资源引用会加1(因为现在的场景是获取的缓存图片会被使用,所以资源引用加1),并且把资源放到弱引用map中。所以这种情况下,前面的资源回收并不会执行。如果当需求清楚资源时(比如页面关闭,请求cancle),那么会调用EngineResource的release()方法将资源引用减1。如果引用变成0后,会调用一个onResourceReleased()接口方法,其在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);
}
}
会把资源从activeResources弱引用数组中清除,然后把资源放到Lruche中,然后将资源进行回收。
当资源异步加载成功后(网络/diskLrucache),除了会放到diskLrucache中(网络请求),资源还会放到哪里呢?
@SuppressWarnings("unchecked")
@Override
public void onEngineJobComplete(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.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
// TODO: should this check that the engine job is still current?
jobs.remove(key);
}
可以看到资源是放到activeResources中了。
我们总结一下:需要一个图片资源,假设Lruche中相应的资源图片,那么就就返回相应资源,同时从Lruche中清除,放到activeResources中。activeResources map是盛放正在使用的资源,以弱引用的形式存在。同时资源内部有被引用的记录。如果资源没有引用记录了,那么再放回Lruche中,同时从activeResources中清除。需要一个图片资源如果Lruche中没有,就从activeResources中找,找到后相应资源的引用加1。如果Lruche和activeResources中没有,那么进行资源异步请求(网络/diskLrucache),请求成功后,资源放到diskLrucache和activeResources中。
核心思想就是:使用一个弱引用map activeResources来盛放项目中正在使用的资源。Lruche中不含有正在使用的资源。资源内部有个计数器来显示自己是不是还有被引用的情况(当然这里说的被项目中使用/引用不包括被Lruche/activeResources引用)。把正在使用的资源和没有被使用的资源分开有什么好处呢?假如突然某一个时刻,我想清空内存的Lruche,我直接就可以清空它,因为Lruche中的资源都没有被项目中使用,这时候我直接让Lruche所有资源置为null,那么所有资源就被清空了,可以安全的执行bitmap.recycle()。而常规的三层缓存机制中的Lruche有可能有些资源还被项目中使用,这时候不能直接把bitmap.recycle()。项目中还有对某些资源的引用,所以即使Lruche中的资源置为null。资源也不会清空。activeResources为什么使用弱引用。首先不用担心正在被项目使用的资源因为gc而被回收。引用项目中使用/引用了(是强引用),那么gc时是不会回收这些资源的。只有所有强引用都不存在了且只有弱引用,那么才会gc时回收。所以这里activeResources使用弱引用是因为有些资源没有被项目使用了但是也没有被重新放到Lruche中,就会在gc时回收,不过这样的资源回收是少数场景。资源回收的大部分发生场景还是根据Lruche的Lru算法(最近最少使用)来清除相应资源。