前言
Android中加载图片的形式有很多种,网上也有很多的知名图片加载库,例如Glide、Picasso、Fresco等等,它们为我们带来的方便就不需再多言了,无论是从加载到缓存还是占位图等等都提供了简易的Api,且实现强大的功能。本系列只针对Glide4.0版本源码进行分析,提高自身阅读源码的能力,同时也是为了了解其中加载的流程以及缓存的原理,本文尽可能地截图说明结合源码解析,如有疏忽之处,还请指教。
关于作者
一个在奋斗路上的Android小生,欢迎关注,互相交流
GitHub:GitHub-ZJYWidget
CSDN博客:IT_ZJYANG
简 书:Android小Y
前情回顾
前几集已经从Glide的最基本用法入手分析了Glide的请求、解析、加载图片的流程。深刻体会到Glide源码结构的复杂,但Glide作为一个优秀的图片加载框架,必然要在缓存上下点功夫,本文主要分析Glide的缓存机制(用过的都能体会到它的缓存给我们带来的丝滑体验,特别是在请求网络资源的场景,缓存机制尤为重要)
剧情(Glide 缓存 有备无患)
平时使用Glide做缓存相关的操作,主要有两个api,一个是是设置skipMemoryCache(boolean)
,表示是否开启内存缓存,另外一个是diskCacheStrategy(DiskCacheStrategy)
表示是否开启硬盘缓存,如下:
Glide3.0以前的用法是:
Glide.with(this).load("http://xxx.xxx.png").skipMemoryCache(false).diskCacheStrategy(DiskCacheStrategy.NONE).into(imageView);
Glide4.0的用法是:
RequestOptions requestOptions = new RequestOptions();
requestOptions.skipMemoryCache(false);
requestOptions.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this).load("http://xxx.xxx.png").apply(requestOptions ).into(imageView);
调用方式有些差别,但核心缓存机制差不多,接下来的分析均以我们以4.0+为准,可以看到简单的一个设置,即可决定是否要开启缓存功能,那么Glide内部究竟是如何对图片做出"备份"的呢?
内存缓存
上一集 Android Glide4.0 源码遨游记(第四集)在讲Glide的into方法的时候,有提到一个关键的核心类:Engine,图片真正请求的地方正是从它的load
方法开始的,而Glide也正是在这里做了核心的缓存操作,我们回头看看那个load
方法的源码:
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 = LogTime.getLogTime();
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);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
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 (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
可以看到,在创建engineJob和decodeJob之前,Glide还做了一些手脚,先是通过
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
创建了一个EngineKey对象,我们点进去buildKey看看:
class EngineKeyFactory {
@SuppressWarnings("rawtypes")
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);
}
}
其实就是new了一个EngineKey对象,并把关于图片的很多加载信息和类型都传了进去,那这个Glide创建这个EngineKey意图何在?不急,我们暂且记住有这么个对象,继续往下看到这么一段:
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;
}
上文我们讲到,onResourceReady就是将资源回调给ImageView去加载,那么这里先是判断active不为空,然后就调用了onResourceReady并且return结束当前方法,那么这个active就很有可能是Glide对图片的一种缓存(暂且这样直观理解),可以看到刚才生成的key值也传了进去,所以我们看下loadFromActiveResources
这个方法里做了什么:
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
可以看到,先是判断isMemoryCacheable
,这个就是我们skipMemoryCache传进来的值的相反值,即:
skipMemoryCache传true,isMemoryCacheable为false
skipMemoryCache传false,isMemoryCacheable为true
所以如果我们设置为不缓存,那么这个条件就会通过,也就是直接执行return null,那么刚才上一步的active对象也就相应地被赋为null,就会继续往下走。
如果设置了启用缓存,那么这个条件就不满足,继续往下,可以看到从activeResources中通过key去拿了个EngineResource,那么activeResources里面存的是什么数据呢?,我们看下ActiveResources类:
public class ActiveResources {
//忽略部分代码
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
@Nullable
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;
}
}
可以看到实际上ActiveResrouces里是存了很多组key-resource的Map集合,并且resource是以弱引用的形式保存,然后get方法是根据一个key去map里面获取对应的资源对象,如果拿不到(即可能已经被系统GC回收),那就clear,返回获取到的EngineResource对象。
回到刚才Engine的load方法中,可以看到在调用loadFromActiveResources
获取不到的情况下,会调用loadFromCache
来获取,那么这个loadFromCache
又是从什么地方获取数据呢?loadFromCahce源码如下:
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;
}
同样是判断isMemoryCacheable,道理同上,就不再复述了,接着它调用了getEngineResourceFromCache
,跟进去看看:
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;
}
可以看到,这里先是调用了一个cache对象的remove,cache是一个MemoryCache接口对象,看下Glide对MemoryCache的定义:
可以看到是一个从内存中添加和移除资源的操作接口,看下它的实现类
LruResourceCache
:
看到继承了LruCache,也就是说,cache实际上是一个根据LruCache算法缓存图片资源的类,刚才把EngineResource从其中remove掉,并且返回了这个被remove掉的EngineResource对象,如果为null,说明Lru缓存中已经没有该对象,如果不为null,则将其返回。
所以
getEngineResourceFromCache
其实就是根据Lru算法从内存中获取图片资源,并且获取到的话就顺便从LruCache中移除掉,接着看回刚才的loadFromCache
方法:
可以看到,假如刚才从LruResourceCache中拿到了缓存,那么就会调用EngineResource的
acquire()
方法:
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;
}
这里只是将acquired+1,那么acquired变量用来干嘛的呢?我们跟踪它被引用的地方,可以看到EngineResource的release()
:
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);
}
}
可以看到,每次调用release
的时候(比如暂停请求或者加载完毕时,这里就不展开讲了,跟踪一下即可),会将acquired
-1,并且当acquired
为0的时候,会调用listener的onResourceReleased
方法,而这个listener正是Engine
,我们回到Engine中看它的实现:
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
可以看到调用了activeResources的deactivate方法(这个方法作用就是将该资源对象从弱引用集合activeResources
中移除),接着,可以看到再将其put进cache,cache我们刚才提到了,是一个基于Lru算法的缓存管理类,所以这里就是将其加进了LruCache缓存中。
也就是说,每次触发
release
就会将acquired
变量-1,一旦它为0时,就会触发Engine
将该缓存从弱引用中移除,并且加进了LruCache缓存中。换句话理解就是,这个资源暂时不用到,Glide把它从弱引用转移到了LruCache中。
而刚才的
loadFromCache
方法里,调用了acquire()
使得 acquired+1,也就是此刻我正要使用这个缓存,做个标记,这样就不会被转移到LruCache中。
联合起来的逻辑就是:先从LruCache中拿缓存,如果拿到了,就从LruCache缓存转移到弱应用缓存池中,并标记一下此刻正在引用。反过来,一旦没有地方正在使用这个资源,就会将其从弱引用中转移到LruCache缓存池中
硬盘缓存
分析完了内存缓存,我们再来看看Glide的硬盘缓存。依然是接着刚才最开始的Engine
的load
方法,在判断了两级内存缓存之后,如果拿不到缓存,就会接着创建EngineJob
和DecodeJob
(这两个的作用见我另外一篇文章Android Glide4.0 源码遨游记(第四集) ),然后接着就会调用进DecodeJob线程的run方法:
@Override
public void run() {
TraceCompat.beginSection("DecodeJob#run");
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (Throwable t) {
if (stage != Stage.ENCODE) {
throwables.add(t);
notifyFailed();
}
if (!isCancelled) {
throw t;
}
} finally {
if (localFetcher != null) {
localFetcher.cleanup();
}
TraceCompat.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:
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进行下一种:
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,看下它是用什么来缓存的,其中部分代码如下:
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
这里通过一个资源的关键信息生成key,然后调用helper.getDiskCache().get(),我们跟进去DiskCache看看:
DiskCache getDiskCache() {
return diskCacheProvider.getDiskCache();
}
interface DiskCacheProvider {
DiskCache getDiskCache();
}
可以看到最终是调用了DiskCacheProvider接口的getDiskCache方法获取一个DiskCache对象,那么这个D对象又是什么来头呢?
可以看到这是一个用来缓存硬盘数据的接口,那么它的实现就是我们要找的最终目标:
里面的就不详细分析下去了,这里主要维护了一个DiskLruCache,Glide就是通过这个来实现硬盘缓存的。
可以看到Glide的硬盘缓存是依靠DiskLruCache来进行缓存的,同样也是Lru算法。
总结
Glide4.0的缓存机制概况来说就是,先从弱引用缓存中获取数据,假如获取不到,就再尝试从LruCache中获取数据,假如从LruCache中获取到数据的话,就会将其从LruCache中转移到弱引用缓存中,这么做的优点是,下次再来拿数据的时候,就可以直接从弱引用中获取。
资源对象用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,然后一旦acquired减为0(没有地方引用该资源),就会将其从弱引用中移除,添加到LruCache中。
使用中的资源会用弱引用来缓存,不在使用的资源会添加到LruCache中来缓存。
在二者都获取不到的情况下会根据硬盘缓存策略通过DiskLruCache去硬盘中获取数据,正是这样优秀的缓存机制,让我们在没有网络的情况下也能有很好的体验。