参考文章:
- Glide源码分析之缓存处理
- Glide缓存机制
理解Glide的缓存机制需要掌握以下几个知识点:
1. 软引用/弱引用与缓存队列;
2. LRU原理;
3. AQS锁: 生产者-消费者模式;
Glide总结:
- 网络数据流数据以后进行解码然后转换成Bitmap, 将Bitmap装载进一个BitmapHolder中, 由于此时Bitmap正在被使用, 所以此时进行强引用缓存;
- 当Bitmap使用完成以后, 将此Bitmap进行弱引用缓存, 并与对应的引用队列进行绑定;
- 当LRU被触发时, 也就是最近最少使用的Bitmap要被回收时, 首先会判断当前BitmapHolder是否为强引用缓存,如果是不进行移除,如果为弱引用缓存, 则进行内存移除;
- 同时开启一个线程, 然后从引用队列中获取弱引用对象, 这里采用的是生产者-消费者模式, 集合采用的是BlockQueue的具体实现类, BlockQueue子类有一个共性就是使用了AQS锁, 而AQS锁就是利用了生产者-消费者模式, 如果能够从中获取到引用, 说明该引用持有的BitmapHolder已经被GC回收, 如果被回收, 则发出消息通知BitmapHolder缓存集合删除该BitmapHolder;
- BitmapHolder之所以这里需要采用强/软引用缓存是因为, LRU会删除最近最少使用的对象, 而在删除时, 如果碰巧遇到该对象正在被使用, 就会报异常;
一、源码解析:
- 如果是第一次加载图片, 即不存在缓存的情况下, 最终在HttpUrlFetcher中进行了加载;
1.1 HttpUrlFetcher.loadData:
public class HttpUrlFetcher implements DataFetcher {
@Override
public void loadData(Priority priority, DataCallback super InputStream> callback) {
long startTime = LogTime.getLogTime();
final InputStream result;
// 获取流数据;
result = loadDataWithRedirects(glideUrl.toURL(), 0 , null , glideUrl.getHeaders());
// 1. 流数据获取成功之后, 对其进行解码;模块<1.4>
// 2. 解码完成便是开始缓存, 显示;模块<二>
callback.onDataReady(result);
}
}
1.2 HttpUrlFetcher.loadDataWithRedirects:
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map headers) throws IOException {
urlConnection = connectionFactory.build(url);
for (Map.Entry headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
urlConnection.setInstanceFollowRedirects(false);
urlConnection.connect();
stream = urlConnection.getInputStream();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100 == 3) {
String redirectUrlString = urlConnection.getHeaderField("Location");
URL redirectUrl = new URL(url, redirectUrlString);
cleanup();
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
}
}
private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection) {
if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
int contentLength = urlConnection.getContentLength();
stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
} else {
stream = urlConnection.getInputStream();
}
return stream;
}
1.4 Downsampler.decode:
- 中间省略一万行代码, 直接跳到解码这里, 详细流程参考(https://blog.csdn.net/guiman/article/details/73217102);
public final class Downsampler {
@SuppressWarnings({"resource", "deprecation"})
public Resource decode(InputStream is, int requestedWidth, int requestedHeight,
Options options, DecodeCallbacks callbacks) {
Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks);
return BitmapResource.obtain(result, bitmapPool);
}
}
public class BitmapResource implements Resource, Initializable {
private final Bitmap bitmap;
private final BitmapPool bitmapPool;
@Nullable
public static BitmapResource obtain(@Nullable Bitmap bitmap, BitmapPool bitmapPool) {
// 将Bitmap封装进BitmapResource中;
return new BitmapResource(bitmap, bitmapPool);
}
}
二、Bitmap缓存
结合模块一可知, 获取的流数据装入Bitmap中, 然后被封装进BitmapResource中;
2.1 EngineJob.handleResultOnMainThread:
- 还是省略一万行代码... 经过n多层回调到这里进行内存缓存;
@Synthetic
void handleResultOnMainThread() {
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
// 1.这里的acquire与下文的release形成对应, 在加载显示图片之前, 通过acquire方式让
// 引用计数+1, 图片加载完成之后, 引用计数-1操作;
// 2.EngineResource内部持有一个Resource, 而该Resource实际指向BitmapDrawableResource, 又持有
// BitmapDrawable, BitmapDrawable内部持有了Bitmap;
engineResource.acquire();
// 1. listener实际指向Engine;
// 2. 对模块<2.2> ~ <2.8>的分析可知, onEngineJobComplete内部完成了Bitmap从弱引用缓存到内存缓存的转变;
listener.onEngineJobComplete(this, key, engineResource);
int size = cbs.size();
for (int i = 0; i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
// 将数据显示在ImageView中;
cb.onResourceReady(engineResource, dataSource);
}
}
// 图片展示之前, EngineResource引用计数+1操作, 加载完成之后-1操作;
engineResource.release();
release(false);
}
2.2 Engine.onEngineJobComplete
@SuppressWarnings("unchecked")
@Overridepublic void onEngineJobComplete(EngineJob> engineJob, Key key, EngineResource> resource) {
if (resource != null) {
// 这里通过setResourceListener触发EngineResource持有Engine的引用, 与模块<2.1>_//2--->对应;
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
// 如果EngineResource支持缓存;模块<2.3>
activeResources.activate(key, resource);
}
}
jobs.removeIfCurrent(key, engineJob);
}
2.3 ActiveResources.activate:
@VisibleForTesting
final Map activeEngineResources = new HashMap<>();
void activate(Key key, EngineResource> resource) {
/**
* 构造一个弱引用对象, 通过getReferenceQueue()传入一个与弱引用关联的队列;模块<2.4>
* 将EngineResource被弱引用持有, 这样就可以知道EngineResource何时被gc回收, 然后就可以将
* 不再使用的EngineResource放入到LruCache中;
*/
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
// 模块<2.5>
getReferenceQueue(),
isActiveResourceRetentionAllowed);
/**
* 一个key对应一个ResourceWeakReference;
*/
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
}
2.4 ResourceWeakReference:
@VisibleForTesting
static final class ResourceWeakReference extends WeakReference> {
final Key key;
final boolean isCacheable;
Resource> resource;
ResourceWeakReference(
Key key,
EngineResource> referent,
ReferenceQueue super EngineResource>> queue...) {
/**
* 其实Glide也无法预测到弱引用何时被gc回收, 但是Glide在构建ReferenceQueue时创建了一个线程,
* 然后在后台一直轮询查询该ReferenceQueue内是否存在EngineResource, 如果存在, 则说明该
* EngineResource被gc回收, 然后就把该EngineResource放入到LruCache中, 进行内存缓存;
*/
super(referent, queue);
...
/**
* 这里的Resource当做Bitmap来理解, 一个Bitmap对应一个EngineResource, 通过构造函数将
* ResourceWeakReference内部的Resource指向EngineResource, 为下文弱引用缓存转为强引
* 用缓存做铺垫;
*/
this.resource = Preconditions.checkNotNull(r.getResource());
this.key = key;
}
}
- 1、如果一个对象只具有软引用, 则内存空间足够, 垃圾回收器就不会回收它, 如果内存空间不足了, 就会回收这些对象的内存.
- 2、软引用可以和一个引用队列(ReferenceQueue)联合使用, 如果软引用所引用的对象被垃圾回收器回收, java虚拟机就会把这个软引用加入到与之关联的引用队列中;
2.5 ActiveResources.getReferenceQueue:
private ReferenceQueue> getReferenceQueue() {
if (resourceReferenceQueue == null) {
resourceReferenceQueue = new ReferenceQueue<>();
cleanReferenceQueueThread = new Thread(new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
ResourceWeakReference ref;
while (!isShutdown) {
/**
* 这里采用的是生产者-消费者模式, 如果resourceReferenceQueue为空, 则当前线程
* 会被挂起, 直到外界线程显示的唤醒, 这里唤醒的条件就是当被弱引用持有的
* EngineResource被gc回收时, ResourceWeakReference被放入到resourceReferenceQueue
* 中, 当前线程被唤醒, 然后从resourceReferenceQueue中读取被gc回收的
* ResourceWeakReference, 有模块<2.4>可知此处的ResourceWeakReference实际
* 持有被回收的EngineResource的Bitmap的实例, 然后通过handler方式发送到主线程中;
*/
ref = (ResourceWeakReference) resourceReferenceQueue.remove();
mainHandler.obtainMessage(MSG_CLEAN_REF, ref).sendToTarget();
DequeuedResourceCallback current = cb;
if (current != null) {
current.onResourceDequeued();
}
}
}
}, "glide-active-resources");
cleanReferenceQueueThread.start();
}
return resourceReferenceQueue;
}
private final Handler mainHandler = new Handler(Looper.getMainLooper(), new Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG_CLEAN_REF) {
/**
* 处理被gc回收的EngineResource;
*/
cleanupActiveReference((ResourceWeakReference) msg.obj); 模块<2.6>
return true;
}
return false;
}
});
2.6 ActiveResources.cleanupActiveReference:
private void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
/**
* 既然已经被gc回收, 弱引用.get() = null, 占了个空位无意义了, 从弱引用集合中删除该key对应的弱引用;
*/
activeEngineResources.remove(ref.key);
/**
* 结合模块<2.1>可知, ref.resource实际指向BitmapDrawable, isCacheable默认为true,
* Glide的思路是先将EngineResource进行弱引用缓存, 如果该弱引用被gc回收, 则将其内部的
* 的Resource即BitmapDrawable进行缓存, 如果ref.resource == null, 也就失去了缓存的意义, 直接返回;
*/
if (!ref.isCacheable || ref.resource == null) {
return;
}
/**
* 如果EngineResource被gc回收, 且其内部的resource还存在, 则重新构建EngineResource, 并进行缓存;
*/
EngineResource> newResource = new EngineResource<>(ref.resource, true, false);
/**
* 1. 构建一个新的EngineResource, 持有listener的引用;
* 2. 然后将EngineResource缓存在Engine.cache中;模块<2.7>
*/
newResource.setResourceListener(ref.key, listener);
listener.onResourceReleased(ref.key, newResource);
}
2.7 Engine.onResourceReleased:
@Override
public void onResourceReleased(Key cacheKey, EngineResource> resource) {
// 1. 将key从ActiveResources中移除;
// 2. ActiveResources充当了一个什么角色?弱引用缓存的载体;
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
// 1. 在GlideBuilder中初始化Engine时传入memoryCache指向LruResourceCache;模块<2.8>
// 2. 通过cache.put完成内存缓存;
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
2.8 LruResourceCache.put:
// LruCache通过内部维持的LinkedHashMap对内存缓存完成LRU机制;
public class LruResourceCache extends LruCache;
public class LruCache {
private final LinkedHashMap cache = new LinkedHashMap<>(100, 0.75f, true);
}
2.9 EngineResource.release:
void release() {
// 结合模块<2.1> ~ <2.8>其实也可以看出EngineResource充当了一个载体, 当数据被加载到ImageView之前,
// EngineResource执行++acquired操作, 表明此时正在执行数据显示的操作, 当数据显示操作完成以后,
// 执行--acquired操作, 当acquired = 0时, 则说明所有的数据显示操作均已完成, 此时再将EngineResource
// 缓存在LruResourceCache中;
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
2.10 Glide为何要引用两级内存缓存?
- 1、Glide通过LruResourceCache完成内存缓存, 依靠LRU机制, 所以就有一个弊端, 当缓存不足时, 会删除最近最少使用的Bitmap, 而假如此时这个Bitmap正在用于ImageView显示呢? 如果被复用或者被移除了岂不是GG了?
- 2、针对这个问题, Glide采用弱引用缓存 + 引用计数的方式. 当系统不在使用该EngineResource时, gc时就会回收该EngineResource, 既然EngineResource不再被引用, 则其内部的Bitmap放到LinkedHashMap队列中, 执行LRU时就不会出现什么问题;
- 3、如果弱引用EngineResource没有被gc回收, 则当EngineResource用于ImageView显示时, 执行acquire++操作, 当ImageView显示结束时, 执行acquire--操作, 当acquire = 0时, 则表明EngineResource没有被任何ImageView使用, 此时手动触发EngineResource从弱引用缓存中移除, 然后强引用存在于LruResourceCache中;
- 4、关于第三条其实是因为同一个EngineResource可能被多个ImageView所使用, 结合下文模块<3.2>;
三、从缓存中读取数据:
- 模块<二>基本上是分析了第一次从网络获取数据之后是如何进行缓存的, 接下来分析如果存在缓存, 则如何使用这些缓存;
3.1 Engine.load:
public LoadStatus load(...) {
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// 从弱引用缓存队列中获取EngineResource;模块<3.2>
EngineResource> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
// 如果存在则使用该EngineResource进行显示;
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
// 1. 如果不存在弱引用, 则尝试从内存中获取EngineResource;模块<3.3>
// 2. 如果LRU中存在, 则将该EngineResource从Lru中移除, 然后添加至activeResources弱引用缓存队列中;
EngineResource> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
}
3.2 Engine.loadFromActiveResources:
@Nullable
private EngineResource> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
/**
* 从这里可以看出, 一个EngineResource可以被多个ImageView所使用, 所以可以通过引用计数的方式判断
* EngineResource被多少个ImageView所引用;
*/
EngineResource> active = activeResources.get(key);
if (active != null) {
// 如果存在, 则其引用计数+1操作;
active.acquire();
}
return active;
}
3.3 Engine.loadFromCache:
private EngineResource> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
// 从LruResourceCache中移除该Key-EngineResouce对;
EngineResource> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
// 将该EngineResource添加至activeResources内的弱引用缓存队列中;
activeResources.activate(key, cached);
}
return cached;
}
@SuppressWarnings("unchecked")
private EngineResource> getEngineResourceFromCache(Key key) {
Resource> cached = cache.remove(key);
...
}