文章目录
- 一、缓存简介
- 二、缓存用法
- 内存缓存方式
- 磁盘缓存方式
- 三、缓存KEY
- 四、内存缓存
- 内存缓存流程
- 五、磁盘缓存
- 磁盘缓存流程
Android Glide图片加载框架系列文章
Android Glide图片加载框架(一)基本用法
Android Glide图片加载框架(二)源码解析之with()
Android Glide图片加载框架(二)源码解析之load()
Android Glide图片加载框架(二)源码解析之into()
Android Glide图片加载框架(三)缓存机制
一、缓存简介
Glide的缓存设计可以说是非常先进的,考虑的场景也很周全。在缓存这一功能上,Glide又将它分成了两个模块,一个是 内存缓存
,一个是 磁盘缓存
这两个缓存模块的作用各不相同,
内存缓存
磁盘缓存
内存缓存
和 硬盘缓存
二、缓存用法
内存缓存方式
RequestOptions options = new RequestOptions();
// 禁止内存缓存
options.skipMemoryCache(true);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
磁盘缓存方式
RequestOptions options = new RequestOptions();
// 磁盘不缓存
options.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
可以设置5种模式:
DiskCacheStrategy.NONE:
DiskCacheStrategy.DATA:
DiskCacheStrategy.RESOURCE:
DiskCacheStrategy.ALL:
DiskCacheStrategy.AUTOMATIC:
三、缓存KEY
既然是缓存功能,就必然会有用于进行缓存的Key。那么Glide的缓存Key是怎么生成的呢?我不得不说,Glide的缓存Key生成规则非常繁琐,决定缓存Key的参数竟然有8个之多。不过繁琐归繁琐,至少逻辑还是比较简单的,我们先来看一下Glide缓存Key的生成逻辑。
生成缓存Key的代码在 Engine
类的 load()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public <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) {
Util.assertMainThread();
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
...
}
...
}
第27行可见,决定缓存Key的条件非常多,即使你用override()方法改变了一下图片的width或者height,也会生成一个完全不同的缓存Key。
缓存key是一个 EngineKey
对象,该类重写了 equals()
和 hashCode()
方法,保证只有传入EngineKey的所有参数都相同的情况下才认为是同一个 EngineKey
class EngineKey implements Key {
...
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;
}
...
}
四、内存缓存
默认情况下,Glide自动就是开启内存缓存的
而Glide最为人性化的是,你甚至不需要编写任何额外的代码就能自动享受到这个极为便利的内存缓存功能,因为Glide默认就已经将它开启了。
那么既然已经默认开启了这个功能,还有什么可讲的用法呢?只有一点,如果你有什么特殊的原因需要禁用内存缓存功能,Glide对此提供了接口:
RequestOptions options = new RequestOptions();
options.skipMemoryCache(true);
Glide.with(this)
.load(url)
.apply(options)
.into(img);
可以看到,只需要调用skipMemoryCache()方法并传入true,就表示禁用掉Glide的内存缓存功能
。
接下来就让我们就通过阅读源码来分析一下Glide的内存缓存功能是如何实现的。
内存缓存使用弱引用和LruCache算法结合完成的
,弱引用来缓存的是正在使用中的图片。图片封装类Resources内部有个计数器判断是该图片否正在使用。
内存缓存流程
读:
存:
渲染完图片:
上篇提到,Engine
在加载流程的中的入口方法是 load
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public <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) {
Util.assertMainThread();
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
// 生成缓存key
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 (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
// 从LruCache获取缓存图片
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
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);
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
...
}
上面是从内存缓存中读取图片的主流程:
- 生成缓存的key。
- 从弱引用获取图片。
- 弱引用没取到,在从LruCache获取缓存图片。
- 内存缓存取不到,进入异步处理。
我们具体看取图片的两个方法 loadFromActiveResources()
和 loadFromCache()
loadFromActiveResources
loadFromCache
我们来看一下它们的源码:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
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;
}
...
}
loadFromActiveResources()
- 首先就判断
isMemoryCacheable
是不是false
,如果是false的话就直接返回null。这就是skipMemoryCache()
- 然后从
activeResources
loadFromCache()
- 首先就判断
isMemoryCacheable
是不是false
,如果是false的话就直接返回null。这就是skipMemoryCache()
- 然后调用
getEngineResourceFromCache()
方法来获取缓存。在这个方法中,会从中获取图片缓存LruResourceCache
- 当我们从
LruResourceCache
中获取到缓存图片之后会将它从缓存中移除,将缓存图片存储到activeResources
这样我们把从内存读取图片缓存的流程搞清了,那是什么时候存储的呢。想想什么时候合适?是不是应该在异步处理获取到图片后,再缓存到内存?
EngineJob
获取到图片后 会回调Engine的 onEngineJobComplete()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
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);
}
...
}
在 onEngineJobComplete()
那我们来看 EngineResource
class EngineResource<Z> implements Resource<Z> {
...
private int acquired;
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);
}
}
...
}
可以看出当引用计数acquired变量为0,就是没有在使用了,然后调用了 listener.onResourceReleased(key, this);
这个 listener
就是 Engine
对象,我们来看下它的 onResourceReleased()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
...
}
做了三件事:
- 从弱引用删除图片缓存
- 是否支持缓存,缓存到LruCache缓存
- 不支持缓存直接调用垃圾回收,回收图片
到这里内存缓存的读和存的流程就介绍完了,根据源码回头看看我们之前列的Glide内存缓存流程,就清晰很多了。
五、磁盘缓存
磁盘缓存流程
读:
存:
注:
在判断了两级内存缓存之后,如果拿不到缓存,就会接着创建 EngineJob
和 DecodeJob
,然后接着就会调用进 DecodeJob
线程的 run()
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
...
@Override
public void run() {
// This should be much more fine grained, but since Java's thread pool implementation silently
// swallows all otherwise fatal exceptions, this will at least make it obvious to developers
// that something is failing.
GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);
// Methods in the try statement can invalidate currentFetcher, so set a local variable here to
// ensure that the fetcher is cleaned up either way.
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (Throwable t) {
// Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our
// usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We
// are however ensuring that our callbacks are always notified when a load fails. Without this
// notification, uncaught throwables never notify the corresponding callbacks, which can cause
// loads to silently hang forever, a case that's especially bad for users using Futures on
// background threads.
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "DecodeJob threw unexpectedly"
+ ", isCancelled: " + isCancelled
+ ", stage: " + stage, t);
}
// When we're encoding we've already notified our callback and it isn't safe to do so again.
if (stage != Stage.ENCODE) {
throwables.add(t);
notifyFailed();
}
if (!isCancelled) {
throw t;
}
} finally {
// Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call
// close in all cases anyway.
if (localFetcher != null) {
localFetcher.cleanup();
}
GlideTrace.endSection();
}
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
...
}
run()
中主要还是调用的 runWrapper()
方法,继而调用 runGenerator()
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
...
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}
...
}
这里调用了一个循环获取解析生成器 Generator
的方法,而解析生成器有多个实现类:ResourcesCacheGenerator
、SourceGenerator
、DataCacheGenerator
,它们负责各种硬盘缓存策略下的缓存管理,所以这里关键的条件在于 currentGenerator.startNext()
循环获取每个Generator能否获取到缓存,获取不到就通过 getNextGenerator()
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
...
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
...
}
所以我们看看 ResourceCacheGenerator.startNext()
class ResourceCacheGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback<Object> {
...
public boolean startNext() {
...
while (modelLoaders == null || !hasNextModelLoader()) {
...
Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
...
return started;
}
...
}
这里通过一个资源的关键信息生成key,然后调用 helper.getDiskCache().get()
,我们跟进去 DiskCache
final class DecodeHelper<Transcode> {
...
DiskCache getDiskCache() {
return diskCacheProvider.getDiskCache();
}
...
}
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
...
interface DiskCacheProvider {
DiskCache getDiskCache();
}
...
}
可以看到最终是调用了 DiskCacheProvider
接口的 getDiskCache()
方法获取一个 DiskCache
public interface DiskCache {
...
}
可以看到这是一个用来缓存硬盘数据的接口,那么它的实现就是我们要找的最终目标:
public class DiskLruCacheWrapper implements DiskCache {
...
private DiskLruCache diskLruCache;
...
}
里面的就不详细分析下去了,这里主要维护了一个 DiskLruCache
可以看到Glide的硬盘缓存是依靠DiskLruCache来进行缓存的,同样也是Lru算法。