这篇是在郭霖的这个博客的基础上,对内存缓存的一次理解,而且磁盘那里感觉可以归到网上和本地一类的东西,就没过于细看,主要是要研究下内存缓存生命周期的问题。
首先通过博客,可以知道,大概的逻辑都在Engine这个类里面。然后我们先定位到里面的load()方法
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
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 (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
上面就是创建一个key,然后通过这个key来依次查找内存中的缓存,第一个是当前被使用的缓存,第二个是lrucache缓存,然后都没有再创建EngineJob,这里后面会谈及。
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
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) {
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}
第一步就是从ActiveResources的对象中通过key找到对应的缓存,然后返回,没有的话就从cache也就是lrucache中找出,然后在lrucache删除这个缓存,然后加入activeResource中,这里就是为了确保一个,只要是正在使用的缓存,都是加入到ActiveResource中的。
那么这里的核心就是ActiveResource这个类。
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
@SuppressWarnings("WeakerAccess") @Synthetic final Key key;
@SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable;
@Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource;
...
}
void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
private ReferenceQueue<EngineResource<?>> getReferenceQueue() {
if (resourceReferenceQueue == null) {
resourceReferenceQueue = new ReferenceQueue<>();
cleanReferenceQueueThread = new Thread(new Runnable() {
@SuppressWarnings("InfiniteLoopStatement")
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
cleanReferenceQueue();
}
}, "glide-active-resources");
cleanReferenceQueueThread.start();
}
return resourceReferenceQueue;
}
@SuppressWarnings("WeakerAccess")
@Synthetic void cleanReferenceQueue() {
while (!isShutdown) {
try {
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
mainHandler.obtainMessage(MSG_CLEAN_REF, ref).sendToTarget();
// This section for testing only.
DequeuedResourceCallback current = cb;
if (current != null) {
current.onResourceDequeued();
}
// End for testing only.
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
上面就是ActiveResources的核心类了,这里内部主要是维护了一个弱引用的Map来储存对应的缓存数据,然后每次加入这个缓存数据的时候,都会和ReferenceQueue来绑定到一起。
还能看到,当里面有缓存的时候,会自动创建一个后台线程,而且不会中断,这里就调用了ReferenceQueue的remove()方法,这个方法是阻塞方法,当有一个缓存会系统回收,我们就能得到这个弱应用的对象,最后通过一系列操作,调用了 listener.onResourceReleased(ref.key, newResource);
这个方法来将这个回收的对象再处理。
这里有个重点就是,WeakReference绑定的对象在remove的时候,是已经为null了,所以这个get()是得不到缓存的,所以也就是为什么这里要自己有个内部类并继承了WeakReference,是要保留里面的source和key,以生成新的Resource,来继续新的处理。
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
这里就是最终的回调方法,也就是再次删除Map中这个对象,然后加入lrucache中,或者直接回收对象。
至于对应的cache也就是如我上面说的那样是lrucache不过自己改写了一些方法,就没什么好说的了。
class EngineResource<Z> implements Resource<Z> {
private Key key;
private int acquired;
private boolean isRecycled;
private final Resource<Z> resource;
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);
}
}
...
}
下面就是这个缓存的数据EngineResource这个类,至于为啥要看这个类,还是说道Engine的onRedourceReleased这个回调方法(就是从Map中弱引用要删除时的操作)除了刚刚系统自动删除然后我们回调外,就是从Engine这里回调了。
从郭霖的那个博客,也可以知道这两个方法的含义,就是通过acquired来判断这个缓存有多少在使用,acquire()就+1,release()就-1,当他为0的时候说明此时已经没有内存在使用,应该删除缓存加入lrucache中,所以回调这个方法,也很好理解。
要弄懂缓存的生命周期,下面就是看这个acquire()和release()什么时候,在哪使用。
首先是acquire(),这首先在engine中有2处出现, 都是在我们上面的代码,也就是分别从ActiveResource和lruCache中找到资源时调用,还有两处就是出现在EngineJob类中,定位到那里
void handleResultOnMainThread() {
// Hold on to resource for duration of request so we don't recycle it in the middle of
// notifying if it synchronously released by one of the callbacks.
engineResource.acquire();
listener.onEngineJobComplete(this, key, engineResource);
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource, dataSource);
}
}
// Our request is complete, so we can release the resource.
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
@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);
}
}
jobs.removeIfCurrent(key, engineJob);
}
嗯,就这两处,这里是EngineJob的这个方法里,就是数据从磁盘或者别处加载好了之后的处理,第一处的注释也很好的说明了为啥要用,不过要和release()相配合,所以是考虑在数据处理阶段,防止某处调用release直接被回收,所以,先调用,然后回调之Engine的这个方法,也就是加入缓存之中,然后就是根据几个请求来调用几次acquire()。
所以这个EngineResource的acquire()就用在如下的几处:
- 从ActiveResources被查找到
- 从lrucache中被查找到
- 从网络或者磁盘中加载成功
能很好的看出就是有一处使用它的时候,就通过acquire(),使得EngineResource的内部的acquire+1,也就是被使用数+1,也不从acqiure被回收,而且这3处就是engine中load的3步加载步骤。
下面就是它的release()方法,除去刚刚在engineJob中看到的保证加载。然后就是出现在Engine的release方法里,那里只有一句resource.release()方法,就不贴代码了,直接去找哪里使用了这个engine的release()方法。
然后定位到SingleRequest的releaseResource()中。
这下就很清楚了,当图片加载的一个请求主动要求释放资源的时候,通过一系列操作,最后release(),使用数-1。
所以最后总结一下这个数据在弱引用Map中的生命周期:
- 正常情况下:从lrucache或者磁盘,网络中加载成功时,加入至Map中,代表开始,当所有用到该数据的request也就是请求都主动释放这个资源,然后从里面移除,加入到lrucache之中,代表结束。
- 特殊情况下:开始情况一样象,最终结束时,不是通过release,是系统的gc线程,然后扫描到这个对就进行回收,然后加入到lruchche之中,代表结束。
- 特殊情况下:在磁盘,网络中加载的过程中,也就是刚加入Map中,没来的及调用后面请求的回调方法和acquire(),直接就被回收了,这里的话我就不清楚会怎么样。
最后还有一个软引用是softReference,和weakReference,两者区别是gc扫描到的话,weakReference一定被回收,而softReference是当系统内存不足时才被回收,因为这个gilde是被回收了还能加入到lrucache之中,所以是采用了weakReference,当不想太麻烦,直接使用softReference一层缓存也是一种方法。
参考: