glide缓存机制 android,Android Glide4.0 源码遨游记(第五集——缓存机制)

7e469ebca92c

Glide

前言

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 LoadStatus load(

GlideContext glideContext,

Object model,

Key signature,

int width,

int height,

Class> resourceClass,

Class transcodeClass,

Priority priority,

DiskCacheStrategy diskCacheStrategy,

Map, 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 engineJob =

engineJobFactory.build(

key,

isMemoryCacheable,

useUnlimitedSourceExecutorPool,

useAnimationPool,

onlyRetrieveFromCache);

DecodeJob 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, 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 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的定义:

7e469ebca92c

MemoryCache声明

可以看到是一个从内存中添加和移除资源的操作接口,看下它的实现类LruResourceCache:

7e469ebca92c

LruResourceCache

看到继承了LruCache,也就是说,cache实际上是一个根据LruCache算法缓存图片资源的类,刚才把EngineResource从其中remove掉,并且返回了这个被remove掉的EngineResource对象,如果为null,说明Lru缓存中已经没有该对象,如果不为null,则将其返回。

所以getEngineResourceFromCache其实就是根据Lru算法从内存中获取图片资源,并且获取到的话就顺便从LruCache中移除掉,接着看回刚才的loadFromCache方法:

7e469ebca92c

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对象又是什么来头呢?

7e469ebca92c

DiskCache

可以看到这是一个用来缓存硬盘数据的接口,那么它的实现就是我们要找的最终目标:

7e469ebca92c

DiskLruCache.png

里面的就不详细分析下去了,这里主要维护了一个DiskLruCache,Glide就是通过这个来实现硬盘缓存的。

可以看到Glide的硬盘缓存是依靠DiskLruCache来进行缓存的,同样也是Lru算法。

总结

Glide4.0的缓存机制概况来说就是,先从弱引用缓存中获取数据,假如获取不到,就再尝试从LruCache中获取数据,假如从LruCache中获取到数据的话,就会将其从LruCache中转移到弱引用缓存中,这么做的优点是,下次再来拿数据的时候,就可以直接从弱引用中获取。

资源对象用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,然后一旦acquired减为0(没有地方引用该资源),就会将其从弱引用中移除,添加到LruCache中。

使用中的资源会用弱引用来缓存,不在使用的资源会添加到LruCache中来缓存。

在二者都获取不到的情况下会根据硬盘缓存策略通过DiskLruCache去硬盘中获取数据,正是这样优秀的缓存机制,让我们在没有网络的情况下也能有很好的体验。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值