在开始之前,我们先了解Java中的四种引用和ReferenceQueue,为什么要了解这些知识呢?大家都知道Glide的缓存使用三级缓存,分别是磁盘缓存和两级内存缓存,而Glide的两级内存缓存就是用WeakReference+ReferenceQueue监控GC回收,这里的回收是指JVM在合适的时间就会回收该对象。
Java的四种引用
熟悉Java的同学都知道Java内存管理分为内存分配和内存回收,都不需要我们负责,垃圾回收的机制主要是看对象是否有引用指向该对象。java对象的引用包括 强引用、软引用、弱引用和虚引用
。
-
强引用(StrongReference)
强引用是指有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OOM错误也不会回收这种对象。 -
软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,注意是内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存,软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中,这也就提供给我监控对象是否被回收。 -
弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象,弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中,这也就提供给我监控对象是否被回收。 -
虚引用
虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
这里就举个弱引用的栗子,其他就不展开说明了?
//引用队列
private ReferenceQueue queue = new ReferenceQueue<Person>();
/**
* 监控对象被回收,因为如果被回收就会就如与之关联的队列中
*/
private void monitorClearedResources() {
Log.e("tag", "start monitor");
try {
int n = 0;
WeakReference k;
while ((k = (WeakReference) queue.remove()) != null) {
Log.e("tag", (++n) + "回收了:" + k + " object: " + k.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
new Thread() {
@Override
public void run() {
monitorClearedResources();
}
}.start();
new Thread() {
@Override
public void run() {
while (true)
new WeakReference<Person>(new Person("hahah"), queue);
}
}.start();
上面是一个监控对象回收,因为如果被回收就会就如与之关联的队列中,接着开启线程制造触发GC,并开启线程监控对象回收,没什么好说的,下面就开始介绍Glide缓存。
Glide缓存
Glide的缓存分为了内存缓存和磁盘缓存,而内存缓存又分了两个模块,Glide缓存的设计思想非常好,不知道人家是怎么想到。
- 内存缓存和磁盘缓存的作用各不相同
1、内存缓存主要是防止重复将图片资源读取到内存当中。
2、磁盘缓存主要是防止重复从网络读取数据。
内存缓存和磁盘缓存相互结合才构成了Glide极佳的图片缓存效果,那么接下来我们就分别来分析一下这两种缓存的使用方法以及它们的实现原理,看看我画的时序图,第一次画时序图,欢迎指教。
ClideCache.png
缓存Key
既然是缓存功能,那得有缓存的Key。那么Glide的缓存Key是怎么生成的呢?生成缓存Key的代码在Engine类的load()方法中:
public synchronized <R> LoadStatus load(
GlideContext glideContext, Object model,Key signature, int width,
int height, Class<?> resourceClass,Class<R> transcodeClass,
Priority priority, DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform,
Options options, boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool,
boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor) {
//.....此处省略一万行代码
EngineKey key = keyFactory.buildKey(model, signature, width,height,
transformations,resourceClass, transcodeClass, options);
//.....此处省略一万行代码
}
实际上直接调用 keyFactory.buildKey方法并把一些资源表示相关的参数传进去构建EngineKey ,那我们来分析keyFactory.buildKey和EngineKey 的源码:
EngineKeyFactory .buildKey:
class EngineKeyFactory {
EngineKey buildKey(Object model, Key signature, int width, int height,
Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
Class<?> transcodeClass, Options options) {
return new EngineKey(model, signature, width, height, transformations, resourceClass,
transcodeClass, options);
}
}
可以看到EngineKeyFactory 的代码并不多,仅仅是创建EngineKey实例,工厂方法模式,说实在Glid中用了好多工厂方法模式。
EngineKey :
class EngineKey implements Key {
EngineKey( Object model,Key signature, int width, int height,
Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
Class<?> transcodeClass,Options options) {
// 此处省略一万行代码
}
@Override
public boolean equals(Object o) {
if (o instanceof EngineKey) {
EngineKey other = (EngineKey) o;
return model.equals(other.model)
&& signature.equals(other.signature)
&& height == other.height
&& width == other.width
&& transformations.equals(other.transformations)
&& resourceClass.equals(other.resourceClass)
&& transcodeClass.equals(other.transcodeClass)
&& options.equals(other.options);
}
return false;
}
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = model.hashCode();
hashCode = 31 * hashCode + signature.hashCode();
hashCode = 31 * hashCode + width;
hashCode = 31 * hashCode + height;
hashCode = 31 * hashCode + transformations.hashCode();
hashCode = 31 * hashCode + resourceClass.hashCode();
hashCode = 31 * hashCode + transcodeClass.hashCode();
hashCode = 31 * hashCode + options.hashCode();
}
return hashCode;
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
throw new UnsupportedOperationException();
}
}
可以看到,其实就是重写equals()和hashCode()方法,即只有传入EngineKey的所有参数都相同的情况下才认为是同一个EngineKey对象或者说是同一种资源。
内存缓存
对于内存缓存的实现,大家应该最先想到的是LruCache算法(Least Recently Used),也叫近期最少使用算法。它的主要算法原理就是把最近使用的对象用强引用存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除,不必多说Glide也是使用了LruCache,但是Glide还结合WeakReference+ReferenceQueue机制。
对于Glide加载资源,那就从Engine.load()方法开始:
public synchronized <R> LoadStatus load(...............) {
//构建缓存Key
EngineKey key = keyFactory.buildKey();
// 第一级内存缓存,资源是否正在使用,里面使用WeakReference+ReferenceQueue机制
1 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
// 第二级内存缓存,里面就是LruCache机制
2 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
// 否则开始异步加载,磁盘缓存或者请求额昂罗
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache);
DecodeJob<R> decodeJob = decodeJobFactory.build(glideContext, model, key, signature,
width, height, resourceClass, transcodeClass, priority, diskCacheStrategy,
transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache,
options, engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
// 开始执行任务
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
可以看到,我标志我1调用了 loadFromActiveResources()方法来获取第一级缓存图片,如果获取到就直接调用cb.onResourceReady()方法进行回调。如果没有获取到,则会在我标志我2调用loadFromCache()方法来获取LruCache缓存图片,获取到的话也直接进行回调。只有在两级缓存都没有获取到的情况下,才会继续向下执行,从而开启线程来加载图片的任务。
也就是说,Glide的图片加载过程中会调用loadFromActiveResources()方法和loadFromCache()来获取内存缓存。当然这两个方法中一个使用的就是LruCache算法,另一个使用的就是弱引用机制。我们来看一下它们的源码:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
//.......此处省略一万行代码
/**
这个方法检测资源是否正在使用
*/
@Nullable
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;
}
// 在getEngineResourceFromCache方法中,会将已经存在内存缓存中的资源移除之后会加入activeResources缓存中
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
// 从LruCache中获取并移除资源,可能会返回null
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
//如果返回没有资源,直接返回null
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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
/**
* 这个方法是在当资源从LruCache、disLruCache和网络加载成功,将资源加入正在使用中
*
* @param engineJob
* @param key
* @param resource
*/
public synchronized void onEngineJobComplete(
EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
if (resource != null && resource.isMemoryCacheable()) {
//当资源加载成功,将资源加入正在使用中,如:LruCache、disLruCache和网络
activeResources.activate(key, resource);
}
}
/**
* 这个方法会在ActiveResource类中,正在使用的资源被清理时回调过来。
* 然后重新把资源加入LruCache缓存中,后面会讲ActiveResource的实现
*
* @param cacheKey
* @param resource
*/
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
//.......此处省略一万行代码
}
首先loadFromActiveResources方法检测当前Key资源是否正在使用,如果存在直接 cb.onResourceReady()方法资源加载完成,否则调用loadFromCache从LruCache中获取资源, 然后在getEngineResourceFromCache()方法中,会将已经存在内存缓存中的资源remove之后会加入activeResources缓存中.所以资源在内存中的缓存只能存在一份。
当我们从LruResourceCache中获取到缓存图片之后会将它从缓存中移除,然后将缓存图片存储到activeResources当中。
activeResources就是一个弱引用的HashMap,用来缓存正在使用中的图片,我们可以看到,loadFromActiveResources()方法就是从activeResources这个HashMap当中取值的。 使用activeResources来缓存正在使用中的图片,可以保护这些图片不会被LruCache算法回收掉。 为什么会说可以保护这些图片不会被LruCache算法回收掉呢? 因为在ActiveResources中使用WeakReference+ReferenceQueue机制,监控GC回收,如果GC把资源回收,那么ActiveResources 会被再次加入LruCache内存缓存中,从而起到了保护的作用。
当图片加载完成之后,会在EngineJob当中对调用onResourceReady()方法:
@Override
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
}
notifyCallbacksOfResult();
}
接下来就是notifyCallbacksOfResult()方法
void notifyCallbacksOfResult() {
//......此处省略一万行代码
engineJobListener.onEngineJobComplete(this, localKey, localResource);
//......此处省略一万行代码
}
接着回调到Engine的onEngineJobComplete()方法当中,如下所示:
public synchronized void onEngineJobComplete(
EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null && resource.isMemoryCacheable()) {
//当资源加载成功,将资源加入正在使用中,如:LruCache、disLruCache和网络
1 activeResources.activate(key, resource);
}
jobs.removeIfCurrent(key, engineJob);
}
可以看到我标志1处,调用activeResources的activate方法把回调过来的EngineResourceactivate和Key作为参数一起传入activeResources,就是这里写入缓存,当然在loadFromCache方法中也有写入缓存:
ActiveResources:
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut = new ResourceWeakReference(key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
ActiveResources
上面讲了,当资源加载完成,回调到Engine的onEngineJobComplete(),并调用activeResources的activate方法把回调过来的EngineResourceactivate和Key作为参数一起传入activeResources,那接下来分析ActiveResources:
final class ActiveResources {
private final boolean isActiveResourceRetentionAllowed;
// 监控GC回收资源线程池
private final Executor monitorClearedResourcesExecutor;
//使用强引用存储ResourceWeakReference
@VisibleForTesting
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
//引用队列与ResourceWeakReference配合监听GC回收
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
private ResourceListener listener;
private volatile boolean isShutdown;
@Nullable
private volatile DequeuedResourceCallback cb;
ActiveResources(boolean isActiveResourceRetentionAllowed) {
this(
isActiveResourceRetentionAllowed,
//启动监控线程
java.util.concurrent.Executors.newSingleThreadExecutor(
new ThreadFactory() {
@Override
public Thread newThread(@NonNull final Runnable r) {
return new Thread(
new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
r.run();
}
},
"glide-active-resources");
}
}));
}
@VisibleForTesting
ActiveResources(boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {
@Override
public void run() {
cleanReferenceQueue();
}
});
}
void setListener(ResourceListener listener) {
synchronized (listener) {
synchronized (this) {
this.listener = listener;
}
}
}
/**
* 资源加载完成调用此方法写入缓存
*
* @param key
* @param resource
*/
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
/**
* 移除指定Key的缓存
*
* @param key
*/
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
/**
* 通过Key获取缓存
*
* @param key
* @return
*/
@Nullable
synchronized EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
@SuppressWarnings({"WeakerAccess", "SynchronizeOnNonFinalField"})
@Synthetic
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
// Fixes a deadlock where we normally acquire the Engine lock and then the ActiveResources lock
// but reverse that order in this one particular test. This is definitely a bit of a hack...
synchronized (listener) {
synchronized (this) {
//将Reference移除HashMap强引用
activeEngineResources.remove(ref.key);
if (!ref.isCacheable || ref.resource == null) {
return;
}
//重新构建新的资源
EngineResource<?> newResource = new EngineResource<>(ref.resource,
/*isMemoryCacheable=*/ true,
/*isRecyclable=*/ false,
ref.key,
listener);
// 如果资源被回收,有可能会回调Engine资源会被再次加入LruCache内存缓存中
listener.onResourceReleased(ref.key, newResource);
}
}
}
@SuppressWarnings("WeakerAccess")
@Synthetic
void cleanReferenceQueue() {
while (!isShutdown) {
try {
//remove会阻塞当前线程,知道GC回收,将ResourceWeakReference放入队列
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
cleanupActiveReference(ref);
// This section for testing only.
DequeuedResourceCallback current = cb;
if (current != null) {
current.onResourceDequeued();
}
// End for testing only.
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@VisibleForTesting
void setDequeuedResourceCallback(DequeuedResourceCallback cb) {
this.cb = cb;
}
@VisibleForTesting
interface DequeuedResourceCallback {
void onResourceDequeued();
}
@VisibleForTesting
void shutdown() {
isShutdown = true;
if (monitorClearedResourcesExecutor instanceof ExecutorService) {
ExecutorService service = (ExecutorService) monitorClearedResourcesExecutor;
Executors.shutdownAndAwaitTermination(service);
}
}
@VisibleForTesting
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;
@Synthetic
@SuppressWarnings("WeakerAccess")
ResourceWeakReference(@NonNull Key key, @NonNull EngineResource<?> referent, @NonNull ReferenceQueue<? super EngineResource<?>> queue, boolean isActiveResourceRetentionAllowed) {
super(referent, queue);
this.key = Preconditions.checkNotNull(key);
this.resource = referent.isMemoryCacheable() && isActiveResourceRetentionAllowed ? Preconditions.checkNotNull(referent.getResource()) : null;
isCacheable = referent.isMemoryCacheable();
}
void reset() {
resource = null;
/**
* 调用此方法会将references加入与之关联的ReferencesQueue队列,当然这个方法有可能GC也会调用此方法清除references中的对象置为null,
* 所以这里重写WeakReference,使用一个人域保存资源,如果你通过get方法获取对象必定为null
*
*
*/
clear();
}
}
}
可以看出在ActiveResources中使用Map保存ResourceWeakReference,即:使用强引用存储ResourceWeakReference,而ResourceWeakReference继承WeakReference,那么为什么要重新拓展WeakReference呢?
大家知道文章开头的时候我讲那些知识并不是多余的,在ActiveResources中使用WeakReference+ReferenceQueue机制,监控GC回收,如果GC把资源回收,那么会把WeakReference中的对象置为null,并加入与之关联的ReferenceQueue中,当你通过WeakReference.get()方法返回的是null,所以ResourceWeakReference拓展WeakReference的原因就很明显了,就是使用类的成员变量保存资源对象,如果你通过get方法获取对象必定为null。
磁盘缓存
前面讲过如果两级内存缓存都没有读取到数据,那么Glide就会开启线程去加载资源,不限于网络,也包括了磁盘:
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;// 保存数据的 key
this.currentData = data;// 保存数据实体
this.currentFetcher = fetcher; // 保存数据的获取器
this.currentDataSource = dataSource;// 数据来源: url 为 REMOTE 类型的枚举, 表示从远程获取
this.currentAttemptingKey = attemptedKey;
if (Thread.currentThread() != currentThread) {
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
// 调用 decodeFromRetrievedData 解析获取的数据
decodeFromRetrievedData();
}
}
我们知道Glide在两级内存缓存没有获取到,那么会开启线程读取加载图片,当图片加载成功,必然会回调onDataFetcherReady方法,而后调用decodeFromRetrievedData方法解析数据:
private void decodeFromRetrievedData() {
Resource<R> resource = null;
try {
// 1. 调用了 decodeFromData 获取资源,原始数据
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
// 2. 通知外界资源获取成功了
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
decodeFromRetrievedData方法则是从原始数据中检测是否是原始数据,如果不是则直接返回已经转换过后的资源数据,如果当前data是原始数据,那么Glide就会执行解码之后再返回数据,但是如果是已经做过变换的数据,那么直接调用notifyEncodeAndRelease方法通知上层资源获取成功,我们来看看notifyEncodeAndRelease方法:
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
Resource<R> result = resource;
// 1. 回调上层资源准备好了,可以展示
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
// 2. 将数据缓存到磁盘
if (deferredEncodeManager.hasResourceToEncode()) {
deferredEncodeManager.encode(diskCacheProvider, options);
}
onEncodeComplete();
}
可以看到在notifyEncodeAndRelease方法中,首先就是直接调用 notifyComplete(result, dataSource)方法通知上层资源已经准备好了,接着调用onEngineJobComplete方法写入内存缓存,然后 调用DeferredEncodeManager的encode方法进行编码,将数据写入磁盘,所以这里应该就是磁盘缓存的地方:
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection("DecodeJob.encode");
try {
// 写入磁盘缓存
diskCacheProvider.getDiskCache().put(key, new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
在encode方法中 diskCacheProvider.getDiskCache().put()写入磁盘。
网友评论