本文Glide源码基于4.9,版本下载地址以下:Glide 4.9java
在分析了Glide的图片加载流程后,更加发觉到Glide的强大,因而这篇文章将继续深刻分析Glide的缓存策略。不过今天的文章的源码不少基于上一篇加载流程的基础之上,所以尚未看上一篇的小伙伴,建议先去阅读Glide4.9源码解析-图片加载流程效果会更佳哟!git
Glide有几级缓存?对于这个问题,网上的答案不一,有的认为是五级缓存,也有的认为是三级缓存,但我我的认为是二级缓存,由于我的感受网络加载并不属于缓存(若是有错误,欢迎在评论指出)github
内存缓存-->磁盘缓存-->网络加载算法
Glide的缓存策略大体是这样的:假设同时开启了内存缓存和磁盘缓存,当程序请求获取图片时,首先从内存中获取,若是内存没有就从磁盘中获取,若是磁盘中也没有,那就从网络上获取这张图片。当程序第一次从网络加载图片后,就将图片缓存到内存和磁盘上。缓存
缓存key是实现内存和磁盘缓存的惟一标识markdown
重写equals和hashCode方法,来确保只有key对象的惟一性网络
生成缓存key的地方其实就在于咱们上一篇文章提到的过的Engine的load方法中app
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) { long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0; //建立EngineKey对象 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); ...... return new LoadStatus(cb, engineJob); } 复制代码
这里会调用keyFactory的buildkey方法来建立EngineKey对象,并将load传入的数据(String,URL等),图片的宽高,签名,设置参数等传进去。异步
KeyFactory#buildKey
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); } 复制代码
能够发现KeyFactory的buildKey方法就是简单的返回了一个EngineKey对象。因此咱们来看看这个EngineKey
EngineKey
class EngineKey implements Key { private final Object model; private final int width; private final int height; private final Class<?> resourceClass; private final Class<?> transcodeClass; private final Key signature; private final Map<Class<?>, Transformation<?>> transformations; private final Options options; private int hashCode; EngineKey( Object model, Key signature, int width, int height, Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass, Class<?> transcodeClass, Options options) { this.model = Preconditions.checkNotNull(model); this.signature = Preconditions.checkNotNull(signature, "Signature must not be null"); this.width = width; this.height = height; this.transformations = Preconditions.checkNotNull(transformations); this.resourceClass = Preconditions.checkNotNull(resourceClass, "Resource class must not be null"); this.transcodeClass = Preconditions.checkNotNull(transcodeClass, "Transcode class must not be null"); this.options = Preconditions.checkNotNull(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; } .... } 复制代码
你会发如今EngineKey中重写equals和hashcode方法,这样就能确保只有传入EngineKey的全部参数都相同的状况下才认为是同一个EngineKey对象。
防止应用重复将图片数据读取到内存
缓存原理:弱引用机制和LruCache算法
弱引用机制:当JVM进行垃圾回收时,不管当前的内存是否足够,都会回收掉弱引用关联的对象
LruCache算法:内部采用LinkedHashMap以强引用的方式存储外界的缓存对象,当缓存满时,LruCache会移除较早使用的缓存对象,而后添加新的缓存对象
缓存实现:正在使用的图片使用弱引用机制进行缓存,不在使用中的图片使用LruCache来进行缓存。
Glide默认状况下是开启了内存缓存的,即你不须要作任何处理,只须要经过下面代码正常调用Glide的三部曲便可。
Glide.with(getContext()).load(url).into(imageView);
复制代码
那咱们要关闭内存缓存咋整呢?强大的Glide固然思考了这种问题,Glide提供了很是便捷的API,所以咱们只须要经过调用来RequestOptions.skipMemoryCacheOf()并传入true,表示跳过内存缓存,即禁用内存缓存。
Glide.with(getContext()) .load(url) .apply(RequestOptions.skipMemoryCacheOf(true)) .into(imageView); 复制代码
在前面咱们提到了两种类型的内存缓存,那么Glide是如何协调二者的呢?让咱们继续回到Engine的load方法
Engine#load
public synchronized <R> LoadStatus load( ...) { //建立EngineKey对象 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; } ..... } 复制代码
在上面的方法中咱们能够发现首先根据宽高,图片URL地址等生成key,而后根据key首先调用loadFromActiveResources获取内存的弱引用缓存的图片,若是获取不到弱引用缓存的图片,才调用loadFromCache获取内存的LruCache缓存。所以咱们先来看看Glide对弱引用缓存的操做。
从上面Engine的load方法中,咱们知道获取弱引用缓存会调用Engine的loadFromActiveResources方法
Engine#loadFromActiveResources
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } //重点关注get方法,从弱引用中拿数据 EngineResource<?> active = activeResources.get(key); if (active != null) { //将EngineResource的引用计数加1 active.acquire(); } return active; } 复制代码
这里首先会调用ActiveResources的get方法获取到图片资源,而后将EngineResource的引用计数加一,由于此时EngineResource指向了弱引用缓存的图片资源。
ActiveResources#get
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); synchronized EngineResource<?> get(Key key) { //从弱引用HashMap中取出对应的弱引用对象 ResourceWeakReference activeRef = activeEngineResources.get(key); if (activeRef == null) { return null; } EngineResource<?> active = activeRef.get(); //若是弱引用中关联的EngineResource对象不存在,即EngineResourse被回收 if (active == null) { //清理弱引用缓存,恢复EngineResource,并保存到LruCache缓存中 cleanupActiveReference(activeRef); } return active; } 复制代码
在ActiveResources中的get中,会经过activeEngineResources的get方法获得了数据的弱引用对象,而这个activeEngineResources其实就是个HashMap,因此能够根据key值来找到这个弱引用对象。而咱们要找的图片资源其实就是这个弱引用对象关联的对象,让咱们来看看ResourceWeakReference的实现
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.isCacheable() && isActiveResourceRetentionAllowed ? Preconditions.checkNotNull(referent.getResource()) : null; isCacheable = referent.isCacheable(); } .... } } 复制代码
能够发现这个类其实继承了WeakReference,因此当gc发生时,会回收掉ResourceWeakReference对象关联的EngineResource对象,这个对象封装了咱们所要获取的图片资源。另外这个类还保存了图片资源和图片资源的缓存key,这是为了当关联的EngineResource对象被回收时,能够利用保存的图片资源来恢复EngineResource对象,而后保存到LruCache缓存中并根据key值从HashMap中删除掉关联了被回收的EngineResource对象的弱引用对象。能够看下EngineResourse被回收的状况
ActiveResources#cleanupActiveReference
void cleanupActiveReference(@NonNull ResourceWeakReference ref) { synchronized (listener) { synchronized (this) { //将该弱引用缓存从HashMap中删除 activeEngineResources.remove(ref.key); //判断缓存是否可用 //isCacheable默认状况下为true //当配置中设置了RequestOptions.skipMemoryCacheOf()的值的话: //1.当skipMemoryCacheOf传入true时为false,即关闭内存缓存 //2.当skipMemoryCacheOf传入false时为true,即开启内存缓存 if (!ref.isCacheable || ref.resource == null) { return; } //恢复EngineResource,其中这个对象封装了图片资源 EngineResource<?> newResource = new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false); newResource.setResourceListener(ref.key, listener); //回调,该listener为Engine对象 listener.onResourceReleased(ref.key, newResource); } } } 复制代码
Engine#onResourceReleased
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) { //删除弱引用缓存 activeResources.deactivate(cacheKey); //若是开启了内存缓存 if (resource.isCacheable()) { //将弱引用缓存的数据缓存到LruCache缓存中 cache.put(cacheKey, resource); } else { resourceRecycler.recycle(resource); } } 复制代码
这样当弱引用缓存所关联的图片资源被回收时,会将图片资源保存到LruCache缓存中,从而保证了当获取不到弱引用缓存的图片时,能够获取的到该图片的LruCache缓存。
弱引用缓存的存储体如今了两个地方:
下面将对这两个地方分别进行解剖!
在主线程展现图片前存储
在讲弱引用缓存的存储前,咱们首先要明白这个弱引用缓存保存到图片资源究竟是图片的原始数据(图片输入流)仍是转换后的图片资源,搞明白的话,找到弱引用存储的地方就不是问题了。这里我就再也不细讲如何搞明白这个问题,其中一个思路就是从Engine的load方法中获取到弱引用缓存的操做入手,即回调入手。
//检查内存弱引用缓存是否有目标图片 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { //此时回调的是SingleRequest的onResourceReady方法 cb.onResourceReady(active, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } 复制代码
这个方法其实在上篇文章图片加载流程也提到过,而后追踪下去你就会发现其实最后就是展现这个图片资源,所以咱们能够肯定这个图片资源应该就是转换后的图片,因此存储弱引用缓存应该是在转换图片后的操做。(这里我直接讲出存储所在的位置,若是想本身深究能够看看上篇文章图片加载流程中的“在主线程中显示图片”这个步骤的代码)最后咱们会发如今EngineJob的notifyCallbacksOfResult方法中找到了弱引用缓存入口
EngineJob#notifyCallbacksOfResult
void notifyCallbacksOfResult() { ResourceCallbacksAndExecutors copy; Key localKey; EngineResource<?> localResource; ..... //内部缓存存储的入口 //实际上调用的是Engine的onEngineJobComplete listener.onEngineJobComplete(this, localKey, localResource); for (final ResourceCallbackAndExecutor entry : copy) { //回到主线程展现照片 entry.executor.execute(new CallResourceReady(entry.cb)); } //通知上层删除弱引用缓存数据 decrementPendingCallbacks(); } 复制代码
没错其入口就是咱们在Glide图片加载流程提到过的回到主线程展现照片代码的前面,即回调了Engine的onEngineJobComplete来存储弱引用缓存
Engine#onEngineJobComplete
public synchronized void onEngineJobComplete( EngineJob<?> engineJob, Key key, EngineResource<?> resource) { if (resource != null) { resource.setResourceListener(key, this); //若是开启内存缓存的话,将解析后的图片添加到弱引用缓存 if (resource.isCacheable()) { activeResources.activate(key, resource); } } jobs.removeIfCurrent(key, engineJob); } 复制代码
ActiveResources#activate
synchronized void activate(Key key, EngineResource<?> resource) { //构建弱引用对象 ResourceWeakReference toPut = new ResourceWeakReference( key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed); //将获取到的缓存图片存储到弱引用对象的HashMap中 //key值不重复返回null,key值重复返回旧对象 ResourceWeakReference removed = activeEngineResources.put(key, toPut); if (removed != null) { //若是key值重复,就将以前的弱引用对象的图片资源置为null removed.reset(); } } 复制代码
从这里也能够获得一个结论:正在使用的图片会存储到弱引用缓存中而不是LruCache缓存
获取LruCache缓存时存储
因为这个操做同时也涉及了LruCache的获取,故能够直接看下面对LruCache获取的解析
弱引用缓存的删除其实体如今两处:
JVM进行GC时
当JVM进行GC时,因为弱引用对象的特性,致使了弱引用缓存所关联的对象也会被回收,而后就会删除掉这个弱引用缓存对象,这部分咱们在弱引用缓存获取的时候也分析过,这里再也不进行解析(忘记的能够回头看看前面弱引用获取的分析)。
弱引用缓存对象引用计数为0时
细心的你不知有没有发现,其实在上面对缓存入口的分析时其实已经贴出了弱引用缓存删除的代码语句。不过为了方便阅读,在这里我仍是再次直接贴出来。
EngineJob#notifyCallbacksOfResult
//内部缓存存储的入口 //实际上调用的是Engine的onEngineJobComplete listener.onEngineJobComplete(this, localKey, localResource); for (final ResourceCallbackAndExecutor entry : copy) { //回到主线程展现照片 entry.executor.execute(new CallResourceReady(entry.cb)); } //通知上层删除弱引用缓存数据 decrementPendingCallbacks(); 复制代码
EngineJob#decrementPendingCallbacks
synchronized void decrementPendingCallbacks() { ..... if (decremented == 0) { if (engineResource != null) { //重点关注 engineResource.release(); } release(); } } 复制代码
这里调用了EngineResource的release方法,让咱们来看看
EngineResource#release
void release() { synchronized (listener) { synchronized (this) { if (acquired <= 0) { throw new IllegalStateException("Cannot release a recycled or not yet acquired resource"); } //每次调用release内部引用计数法减一,当为0时,表明没引用,通知上层回收 if (--acquired == 0) { //回调,listener为Engine类型 listener.onResourceReleased(key, this); } } } } 复制代码
在这里使用了著名的判断对象是否存活的算法-引用计数法,每次调用EngineResource对象的release方法,都会令该引用减一,当引用计数为0时,表示已经再也不使用该对象,即图片再也不使用时,就会回调Engine的onResourceReleased方法
Engine#onResourceReleased
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) { //删除弱引用缓存 activeResources.deactivate(cacheKey); //若是开启了内存缓存 if (resource.isCacheable()) { //将弱引用缓存的数据缓存到LruCache缓存中 cache.put(cacheKey, resource); } else { resourceRecycler.recycle(resource); } } 复制代码
跟上面存储弱引用缓存时提到的发生GC的状况同样,最终会删除弱引用缓存,而后将该图片资源添加到LruCache缓存中。从这里也能够验证了咱们上文提到的内存缓存的原理中的缓存实现:正在使用的图片使用弱引用机制进行缓存,不在使用中的图片使用LruCache来进行缓存。
上文咱们提到,获取内存缓存时,若是获取不到弱引用缓存时才会调用loadFromCache获取LruCache缓存。让咱们看看Engine的loadFromCache方法
Engine#loadFromCache
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { //isMemoryCacheable默认状况下为true //当配置中设置了RequestOptions.skipMemoryCacheOf()的值的话: //1.当skipMemoryCacheOf传入true时为false,即关闭内存缓存 //2.当skipMemoryCacheOf传入false时为true,即开启内存缓存 if (!isMemoryCacheable) { return null; } //获取图片缓存,并将该缓存从LruCache缓存中删除 EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) { //将EngineResource的引用计数加1 cached.acquire(); //将内存缓存存入弱引用缓存中 //好处:保护这些图片不会被LruCache算法回收掉 activeResources.activate(key, cached); } return cached; } 复制代码
获取LruCache缓存跟弱引用缓存的获取操做很类似,首先调用了getEngineResourceFromCache来获取图片资源,而后将EngineResource的引用计数加1,而且还会将获取到的图片资源存储到弱引用缓存中。这里咱们只分析getEngineResourceFromCache方法,由于调用ActiveResource的activate存储到弱引用缓存咱们已经在上面弱引用缓存的存储中分析过了。
EngineResource#getEngineResourceFromCache
/** * 做用:获取图片缓存 * 过程:根据缓存key从cache中取值 * 注:此cache对象为Glide构建时建立的LruResourceCache对象,说明使用的是LruCache算法 */ private EngineResource<?> getEngineResourceFromCache(Key key) { //当获取到缓存图片时,从缓存中移除 Resource<?> cached = cache.remove(key); final EngineResource<?> result; if (cached == null) { result = null; } else if (cached instanceof EngineResource) { result = (EngineResource<?>) cached; } else { result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/); } return result; } 复制代码
在上面需注意的是该cache就是LruCache缓存的cache,另外你会发现获取图片缓存居然不是调用cache的get方法,而是cache的remove方法,这就是Glide缓存策略的奇妙之处了。当获取到LruCache缓存的同时会删除掉该LruCache缓存,而后将该缓存存储到弱引用缓存中,这是为了保护这些图片不会被LruCache算法回收掉。
当弱引用缓存删除时,会将缓存存储到LruCache缓存中。(分析能够看弱引用缓存删除操做)
当获取LruCache缓存的同时对该LruCache缓存进行删除操做。(分析能够看LruCache缓存的获取操做)
分析完内存缓存,你会发现弱引用缓存和LruCache缓存真的是环环相扣,密不可分,不少操做都是有关联性的。其流程图以下:
流程图的前提:开启内存缓存,关闭磁盘缓存
防止应用重复的从网络或从其它地方下载和读取数据
使用Glide自定义的DiskLruCache算法
DiskLruCache算法是基于LruCache算法,该算法的应用场景是存储设备的缓存,即磁盘缓存。
磁盘缓存也是默认开启的,默认状况下磁盘缓存的类型为DiskCacheStrategy.AUTOMATIC,固然能够经过代码关闭或者选择其它类型的磁盘缓存
Glide.with(getContext())
.load(url)
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.RESOURCE))
.into(imageView);
复制代码
diskCacheStrategyOf的相关参数说明以下:
参数 | 说明 |
---|---|
DiskCacheStrategy.AUTOMATIC |
这是默认的最优缓存策略: 本地:仅存储转换后的图片(RESOURCE) 网络:仅存储原始图片(DATA)。由于网络获取数据比解析磁盘上的数据要昂贵的多 |
DiskCacheStrategy.NONE | 不开启磁盘缓存 |
DiskCacheStrategy.RESOURCE | 缓存转换事后的图片 |
DiskCacheStrategy.DATA | 缓存原始图片,即原始输入流。它须要通过压缩转换,解析等操做才能最终展现出来 |
DiskCacheStrategy.ALL | 既缓存原始图片,又缓存转换后的图片 |
注:Glide加载图片默认是不会将一张原始图片展现出来的,而是将原始图片通过压缩转换,解析等操做。而后将转换后的图片展现出来。
下列源码解析的前提:开启了磁盘缓存
虽说上面的参数有五种,但其实咱们只须要分析其中两种就能理解其它参数了。没错,接下来咱们将分析RESOURCE类型和DATA类型。在分析前咱们先回顾下Engine的load方法
Engine#load
public synchronized <R> LoadStatus load( .....) { ...... //建立EngineKey对象 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对象,用来开启线程(异步加载图片) 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); } 复制代码
从上面能够发现若是获取不到内存缓存时,会开启线程来加载图片。从上篇文章Glide 4.9源码解析-图片加载流程咱们能够知道,接下来会执行DecodeJob的run方法。
DecodeJob
public void run() { ..... //重点关注,调用runWrapped runWrapped(); } 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); } } 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); } } 复制代码
能够发如今上述的方法中首先要找到对于场景的执行者而后执行任务。而执行者有三个,在上篇文章咱们分析的是无缓存的状况,即网络获取数据的执行者。接下来咱们就得分析获取转换后图片的执行者和获取原始突破的执行者。
因为咱们如今配置的缓存策略为RESOURCE,故对于执行者将是获取转换图片的执行者ResourceCacheGenerator,接下来会执行者会执行任务,让咱们看看runGenerators方法
Engine#runGenerators
private void runGenerators() { currentThread = Thread.currentThread(); startFetchTime = LogTime.getLogTime(); boolean isStarted = false; // 调用 currentGenerator.startNext() 执行了请求操做 //咱们这里主要分析的是无缓存状况,因此这里的currentGenerator应该是ResourceCacheGenerator while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator(); if (stage == Stage.SOURCE) { reschedule(); return; } } 复制代码
由于咱们如今的执行者为ResourceCacheGenerator,因此会调用ResourceCacheGenerator的startNext来进行获取图片。
ResourceCacheGenerator#startNext
public boolean startNext() { .... while (modelLoaders == null || !hasNextModelLoader()) { ..... Key sourceId = sourceIds.get(sourceIdIndex); Class<?> resourceClass = resourceClasses.get(resourceClassIndex); Transformation<?> transformation = helper.getTransformation(resourceClass); //构建获取缓存数据的key,这个key中传入了图片大小,变换等参数 //即根据各类变换的条件获取缓存数据,故这个执行者就是用来获取变换以后的缓存数据 currentKey = new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops helper.getArrayPool(), sourceId, helper.getSignature(), helper.getWidth(), helper.getHeight(), transformation, resourceClass, helper.getOptions()); //从缓存中获取缓存信息 //首先经过getDiskCache获取DiskCache对象 //而后经过key获取到转换事后的资源 cacheFile = helper.getDiskCache().get(currentKey); if (cacheFile != null) { sourceKey = sourceId; //该modeLoaders的类型为File类型的 modelLoaders = helper.getModelLoaders(cacheFile); modelLoaderIndex = 0; } } loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { //获取数据加载器 ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); //构建一个加载器,构建出来的是ByteBufferFileLoader loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions()); if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { started = true; //调用了ByteBufferFileLoader的内部类ByteBufferFetcher的loadData //最后会把结果回调给DecodeJob的onDataFetcherReady loadData.fetcher.loadData(helper.getPriority(), this); } } return started; } 复制代码
能够发如今ResourceCacheGenerator的startNext方法中,首先根据图片的参数,宽高等信息拿到缓存key,而后经过缓存key获取到磁盘上的文件,即磁盘缓存。最后经过加载器的loadData来处理获取到的磁盘缓存,因为磁盘缓存是File类型,根据Glide的注册表registry中能够找到该加载器实际上是ByteBufferFileLoader(具体查找可看上篇博客的分析),故最后会调用ByteBufferFileLoader内部类ByteBufferFetcher的loadData方法来处理磁盘缓存。
ByteBufferFileLoader.ByteBufferFetcher#loadData
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) { ByteBuffer result; try { //获取对应磁盘上的转换事后的数据 result = ByteBufferUtil.fromFile(file); } catch (IOException e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Failed to obtain ByteBuffer for file", e); } callback.onLoadFailed(e); return; } //此callback为ResourceCacheGenerator callback.onDataReady(result); } 复制代码
在loadData中会经过ByteBufferUtil工具类来获取对应磁盘文件的数据,而后经过回调ResourceCacheGenerator的onDataReady方法将数据回调出去。
ResourceCacheGenerator#onDataReady
public void onDataReady(Object data) { //此时的cb为DecodeJob,即调用了DecodeJob的onDataFetcherReady cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey); } 复制代码
进行回调DecodeJob的onDataFetcherReady
DecodeJob#onDataFetcherReady
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) { //将各类值赋值给成员变量 this.currentSourceKey = sourceKey; this.currentData = data; this.currentFetcher = fetcher; this.currentDataSource = dataSource; this.currentAttemptingKey = attemptedKey; if (Thread.currentThread() != currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this); } else { GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData"); try { //解析获取的数据 decodeFromRetrievedData(); } finally { GlideTrace.endSection(); } } } 复制代码
接下来就会对数据进行压缩转换等操做,而后进行展现(在上篇文章已经分析,这里再也不进行后续的分析)。
因为咱们分析的是转换后的图片的存储,故其存储位置应该是在对原始图片压缩转换解析等一系列操做完后进行的,根据上一篇文章的分析,咱们直接看DecodeJob的decodeFromRetrievedData方法
DecodeJob
private void decodeFromRetrievedData() { if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Retrieved data", startFetchTime, "data: " + currentData + ", cache key: " + currentSourceKey + ", fetcher: " + currentFetcher); } Resource<R> resource = null; try { //转换后的图片资源 resource = decodeFromData(currentFetcher, currentData, currentDataSource); } catch (GlideException e) { e.setLoggingDetails(currentAttemptingKey, currentDataSource); throwables.add(e); } if (resource != null) { //通知外界资源获取成功 notifyEncodeAndRelease(resource, currentDataSource); } else { runGenerators(); } } private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) { ..... //图片加载流程时重点关注的地方 notifyComplete(result, dataSource); stage = Stage.ENCODE; try { //是否能够将转换的图片缓存 if (deferredEncodeManager.hasResourceToEncode()) { //磁盘缓存入口 deferredEncodeManager.encode(diskCacheProvider, options); } } finally { if (lockedResource != null) { lockedResource.unlock(); } } onEncodeComplete(); } 复制代码
咱们在通知外界资源获取成功即notifyEncodeAndRelease方法中发现了RESOURCE类型的磁盘缓存的入口。
DecodeJob.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获取到DiskCache,而后调用put方法将图片资源缓存到磁盘上。
因为缓存在了磁盘上,故删除不只仅由代码控制。常见的删除方式以下:
经过上面的分析咱们知道原始图片对应的执行者为DataCacheGenerator,故仍是会调用DataCacheGenerator的startNext方法来获取磁盘缓存
DataCacheGenerator#startNext
public boolean startNext() { while (modelLoaders == null || !hasNextModelLoader()) { ..... Key sourceId = cacheKeys.get(sourceIdIndex); @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") //构建缓存key Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); //获取缓存key的磁盘资源 cacheFile = helper.getDiskCache().get(originalKey); if (cacheFile != null) { this.sourceKey = sourceId; modelLoaders = helper.getModelLoaders(cacheFile); modelLoaderIndex = 0; } } loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions()); if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { started = true; loadData.fetcher.loadData(helper.getPriority(), this); } } return started; } 复制代码
你会发现其实这个startNext方法与刚刚分析的ResourceCacheGenerator的startNext几乎是同样,不一样的是缓存key的构建参数是不同的,由于原始图片的缓存key是不须要图片的宽高,配置,变换等参数。而后接下来的分析与转换图片获取的分析是一致的,这里再也不进行分析。
咱们知道原始图片说白了就是网络获取后获得的原始的输入流,经过上一篇加载流程的分析,咱们知道获取到原始输入流是在HttpUrlFetcher的loadData方法中
HttpUrlFetcher#loadData
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) { long startTime = LogTime.getLogTime(); try { //获取网络图片的输入流 InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); //将inputStream回调出去,回调了SourceGenerator的onDataReady callback.onDataReady(result); } catch (IOException e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Failed to load data for url", e); } callback.onLoadFailed(e); } finally { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)); } } } 复制代码
在获取到原始输入流后,会调用SourceGenerator的onDataReady将输入流回调出去
SourceGenerator#onDataReady
public void onDataReady(Object data) { DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); //若是开启了磁盘缓存 if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { //将网络获取到的原始数据,赋值给dataToCache dataToCache = data; //调用DecodeJob的reschedule,用线程池执行任务,实际上就是再次调用SourceGenerator的startNext cb.reschedule(); } else { //没有开启磁盘缓存 cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); } } 复制代码
因为咱们开启了磁盘缓存,故会将原始数据赋值给dataToCache,而后回调了DecodeJob的reschedule。
DecodeJob#reschedule
public void reschedule() { runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; //此时的callback为EngineJob callback.reschedule(this); } 复制代码
这里继续回调了EngineJob的reschedule方法
EngineJob#reschedule
public void reschedule(DecodeJob<?> job) { //再次切换到网络线程,执行DecodeJob的run方法 getActiveSourceExecutor().execute(job); } 复制代码
这里的job为DecodeJob,而getActiveSourceExecutor()会拿到线程池,因此reschedule方法其实会继续执行DecodeJob的run方法,而后拿到网络获取数据的执行者SourceGenerator,再次执行SourceGenerator的startNext方法(考虑到篇幅,再也不贴其中的流程代码了,详细能够看上篇文章Glide 4.9源码解析-图片加载流程。
不过估计在这里可能有人会疑问,咱们明明开启了磁盘缓存,为何会获取到无缓存,网络获取数据的执行者呢?这是由于咱们在存储原始图片的前提下,确定是磁盘没有缓存,所以会从网络加载图片获得原始图片的输入流,而后回调,回调后固然仍是拿到网络获取数据的执行者SourceGenerator。让咱们再来看看这个startNext方法。
SourceGenerator#startNext
public boolean startNext() { //第二次进入 //如今dataToCache不等于null,为原始图片 if (dataToCache != null) { Object data = dataToCache; dataToCache = null; //放入缓存 cacheData(data); } //当原始图片放入磁盘缓存后,sourceCacheGenerator为DataCacheGenerator //而后继续执行DataCacheGenerator的startNext方法 if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) { return true; } //走到这,说明是没有开启磁盘缓存或获取不到磁盘缓存的状况下 sourceCacheGenerator = null; loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData != null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; loadData.fetcher.loadData(helper.getPriority(), this); } } return started; } 复制代码
经过上面的分析咱们知道此时的dataToCache是不为null的,而是原始图片,因此会调用cacheData方法将原始图片放入到磁盘缓存中。(若是你阅读了Glide图片加载流程的话,就会发现咱们在图片加载流程的时候分析的实际上是下面的代码,即没有开启缓存或获取不到磁盘缓存的状况)这里咱们继续看cacheData方法
SourceGenerator#cacheData
private void cacheData(Object dataToCache) { long startTime = LogTime.getLogTime(); try { Encoder<Object> encoder = helper.getSourceEncoder(dataToCache); DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, dataToCache, helper.getOptions()); //构建缓存key originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature()); //存储原始图片 helper.getDiskCache().put(originalKey, writer); ...... } finally { loadData.fetcher.cleanup(); } //构造一个DataCacheGenerator对象,用来加载刚保存的磁盘缓存 sourceCacheGenerator = new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this); } 复制代码
在cacheData方法中会经过DiskCache的put方法将缓存key,原始图片等存储到磁盘中。而后会构造DataCacheGenerator对象,这时候咱们看回SourceGenerator的startNext方法,因为此时的sourceCacheGenerator已是DataCacheGenerator对象了,因此会调用DataCacheGenerator的startNext方法来获取磁盘缓存中的原始图片。
因为原始图片的缓存也属于磁盘缓存,故跟RESOURCE缓存同样删除不只仅由代码控制,常见删除方式以下:
从上面的分析能够发现Glide中首先会读取转换后的图片的缓存,而后再读取原始图片的缓存。可是存储的时候偏偏相反,首先存储的是原始图片的缓存,再存储转换后的图片,不过获取和存储都受到Glide使用API的设置的影响。其流程图以下:
流程图的前提:关闭内存缓存或获取不到内存缓存,开启磁盘缓存
经过对Glide缓存策略的分析,发现了Glide的缓存机制是多么的复杂,但又是多么的出色啊,因此用起来才会这么流畅和舒服。分析到这,Glide的缓存策略也就讲完了,而对Glide这个强大的图片开源库的源码分析也告一段落了,经过对Glide的图片加载流程和缓存策略的源码解析,让我更加佩服Glide这个强大的开源库。
参考文章: