如果没有缓存,在大量的网络请求从远程获取图片时会造成网络流量的浪费,尤其是面对高清大图的加载更是如此,为了节省带宽,也为了减少用户等待的时间,合理的缓存方式必不可少,这也是Glide图片框架的强大之处。另外Glide的缓存机制可以说是非常高频的问题,Glide有几级缓存?Glide读取缓存的顺序和时机是什么?Glide存放缓存的顺序和时机又是什么?
1.Glide中缓存概念简述
Glide中的缓存分为两部分,内存缓存和硬盘缓存。
1.1 内存缓存
内存缓存又分为两级,一级是LruCache缓存,一级是弱引用缓存。
内存缓存的作用:防止应用重复将图片数据读取到内存当中。
LruCache缓存:不在使用中的图片使用LruCache来进行缓存。
弱引用缓存:把正在使用中的图片使用弱引用来进行缓存
为什么设计弱引用缓存?
因为内存缓存使用LRU算法,当你使用Gilde加载并显示第一张图片时,后面又加载了很多图片,同时你的第一张图片还在用。这个时候内存缓存根据LRU算法可能会删除你正在使用的第一张照片。这样的后果就是你正在使用的照片找不到,后果就是程序崩溃。
1.2 硬盘缓存
硬盘缓存的作用:防止应用重复从网络或其他地方重复下载和读取数据;
2.缓存源码流程
memory cache和disk cache在Glide创建的时候也被创建了,并传给了Engine引擎类,Glide创建的代码在GlideBuilder.build(Context)方法。
@NonNull
Glide build(@NonNull Context context) {
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
...);
}
return new Glide(
...
memoryCache,
...);
}
1. 内存缓存--memoryCache
通过代码可以看到 memoryCache 被放入 Engine 和 Glide 实例中。在Engine中利用memoryCache进行存取操作,Glide 实例中的memoryCache是用来在内存紧张的时候,通知memoryCache释放内存。
Glide如何监听内存紧张?
当应用内存不足的时候,会回调
ComponentCallbacks2
接口的void onTrimMemory(@TrimMemoryLevel int level);
或者ComponentCallbacks
接口的void onLowMemory();
;ComGlide实现了ComponentCallbacks2
接口(ComponentCallback2继承了ComponentCallback),在Glide创建完成后,通过```applicationContext.registerComponentCallbacks(glide)``将其注册进Application,因此Glide 实例可以监听内存紧张的信号。
@Override
public void onLowMemory() {
clearMemory();
}
public void clearMemory() {
// Engine asserts this anyway when removing resources, fail faster and consistently
Util.assertMainThread();
// memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
memoryCache.clearMemory();
bitmapPool.clearMemory();
arrayPool.clearMemory();
}
// Glide
@Override
public void onTrimMemory(int level) {
trimMemory(level);
}
public void trimMemory(int level) {
// Engine asserts this anyway when removing resources, fail faster and consistently
Util.assertMainThread();
for (RequestManager manager : managers) {
manager.onTrimMemory(level);
}
// memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
memoryCache.trimMemory(level);
bitmapPool.trimMemory(level);
arrayPool.trimMemory(level);
}
在onlowMemory 中Glide会将一些缓存的内存进行清除,方便进行内存回收,当onTrimMemory被调用的时候,如果level是系统资源紧张,Glide会将Lru缓存和BitMap重用池相关的内容进行回收。如果是其他的原因调用onTrimMemory,Glide会将缓存的内容减小到配置缓存最大内容的1/2。
memoryCache是一个使用LRU(least recently used)算法实现的内存缓存类LruResourceCache,继承至LruCache类,并实现了MemoryCache接口。LruCache定义了LRU算法实现相关的操作,而MemoryCache定义的是内存缓存相关的操作。
LruCache源码分析
public class LruCache<K, V> {
// 数据最终存在 LinkedHashMap 中
private final LinkedHashMap<K, V> map;
...
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
// 创建一个LinkedHashMap,accessOrder 传true
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
...
LruCache 构造方法里创建一个LinkedHashMap,accessOrder 参数传true,表示按照访问顺序排序,数据存储基于LinkedHashMap。
LinkedHashMap在HashMap的数据结构基础上,添加了双向链表的数据结构,再加上对 LinkedHashMap 的数据操作上锁,就实现了LruCache的缓存策略,即最少最近访问。
当调用 put()方法时,就会在集合中添加元素,并调用
trimToSize()判断缓存是否已满,如果满了就用 LinkedHashMap 的迭代器删除队尾元素,即近期最少访问的元素。
当调用 get()方法访问缓存对象时,就会调用 LinkedHashMap 的 get()方法获得对应集合元素,同时会更新该元素到队头。
2.磁盘缓存
diskCacheFactory是创建DiskCache的Factory,DiskCache接口定义。
DiskCache.Factory的默认实现为InternalCacheDiskCacheFactory
。在InternalCacheDiskCacheFactory
中会默认会创建一个250M的缓存目录,其路径
/data/data/{package}/cache/image_manager_disk_cache/。
继续看其父类DiskLruCacheFactory的代码:
public class DiskLruCacheFactory implements DiskCache.Factory {
// 省略部分代码
@Override
public DiskCache build() {
File cacheDir = cacheDirectoryGetter.getCacheDirectory();
if (cacheDir == null) {
return null;
}
if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) {
return null;
}
return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
}
}
DiskLruCacheFactory.build()方法会返回一个DiskLruCacheWrapper类的实例,看下DiskLruCacheWrapper的实现。
public class DiskLruCacheWrapper implements DiskCache {
private static final String TAG = "DiskLruCacheWrapper";
private static final int APP_VERSION = 1;
private static final int VALUE_COUNT = 1;
private static DiskLruCacheWrapper wrapper;
private final SafeKeyGenerator safeKeyGenerator;
private final File directory;
private final long maxSize;
private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();
private DiskLruCache diskLruCache;
@SuppressWarnings("deprecation")
public static DiskCache create(File directory, long maxSize) {
return new DiskLruCacheWrapper(directory, maxSize);
}
@Deprecated
@SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"})
protected DiskLruCacheWrapper(File directory, long maxSize) {
this.directory = directory;
this.maxSize = maxSize;
this.safeKeyGenerator = new SafeKeyGenerator();
}
private synchronized DiskLruCache getDiskCache() throws IOException {
if (diskLruCache == null) {
diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
}
return diskLruCache;
}
@Override
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
File result = null;
try {
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
...
}
return result;
}
@Override
public void put(Key key, Writer writer) {
String safeKey = safeKeyGenerator.getSafeKey(key);
writeLocker.acquire(safeKey);
try {
try {
DiskLruCache diskCache = getDiskCache();
Value current = diskCache.get(safeKey);
...
DiskLruCache.Editor editor = diskCache.edit(safeKey);
...
try {
File file = editor.getFile(0);
if (writer.write(file)) {
editor.commit();
}
} finally {
editor.abortUnlessCommitted();
}
} catch (IOException e) {
...
}
} finally {
writeLocker.release(safeKey);
}
}
...
}
里面包装了一个DiskLruCache,该类主要是为DiskLruCache提供了一个根据Key生成safeKey的SafeKeyGenerator以及写锁DiskCacheWriteLocker。
回到GlideBuilder.build(Context)中,diskCacheFactory会被传进Engine中,在Engine的构造方法中会被包装成为一个LazyDiskCacheProvider
,
在被需要的时候调用getDiskCache()方法,这样就会调用factory的build()方法返回一个DiskCache。代码如下:
# Engine.java
Engine(
MemoryCache cache,
DiskCache.Factory diskCacheFactory,
GlideExecutor diskCacheExecutor,
GlideExecutor sourceExecutor,
GlideExecutor sourceUnlimitedExecutor,
GlideExecutor animationExecutor,
Jobs jobs,
EngineKeyFactory keyFactory,
ActiveResources activeResources,
EngineJobFactory engineJobFactory,
DecodeJobFactory decodeJobFactory,
ResourceRecycler resourceRecycler,
boolean isActiveResourceRetentionAllowed) {
this.cache = cache;
this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);
...
// 省略部分代码
}
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
// 省略部分代码
...
@Override
public DiskCache getDiskCache() {
if (diskCache == null) {
synchronized (this) {
if (diskCache == null) {
diskCache = factory.build();
}
if (diskCache == null) {
diskCache = new DiskCacheAdapter();
}
}
}
return diskCache;
}
}
LazyDiskCacheProvider会在Engine后面的初始化流程中作为入参传到DecodeJobFactory的构造器。在DecodeJobFactory创建DecodeJob时也会作为入参会传进去,DecodeJob中会以全局变量保存此LazyDiskCacheProvider,在资源加载完毕并展示后,会进行缓存的存储。同时,DecodeJob也会在DecodeHelper初始化时,将此DiskCacheProvider设置进去,供ResourceCacheGenerator、DataCacheGenerator读取缓存,供SourceGenerator写入缓存。
3. ActiveResources
ActiveResources在Engine的构造器中被创建,在ActiveResources的构造器中会启动一个后台优先级级别(THREAD_PRIORITY_BACKGROUND)的线程,在该线程中会调用cleanReferenceQueue()方法一直循环清除ReferenceQueue中的将要被GC的Resource。
ActiveResources(
boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {
@Override
public void run() {
cleanReferenceQueue();
}
});
}
先来看看ActiveResources的activate方法(保存)、deactivate方法(删除)的方法。
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();
}
}
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
activate方法会将参数封装成为一个ResourceWeakReference,然后放入map中,如果对应的key之前有值,那么调用之前值的reset方法进行清除。deactivate方法先在map中移除,然后调用resource的reset方法进行清除。ResourceWeakReference继承WeakReference,内部只是保存了Resource的一些属性。
图片资源是什么时候从ActiveResource中缓存至内存的?
ResourceWeakReference继承WeakReference,内部只是保存了Resource的一些属性。
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) {
// focus
super(referent, queue);
this.key = Preconditions.checkNotNull(key);
this.resource =
referent.isCacheable() && isActiveResourceRetentionAllowed
? Preconditions.checkNotNull(referent.getResource()) : null;
isCacheable = referent.isCacheable();
}
}
其构造方法中调用了super(referent, queue),这样做可以让将要被GC的对象放入到ReferenceQueue中。而ActiveResources.cleanReferenceQueue()
方法会一直尝试从queue中获取将要被GC的resource,然后调用cleanupActiveReference
方法将resource从activeEngineResources
中移除。cleanupActiveReference方法代码如下:
#ActiveResource.java
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
synchronized (listener) {
synchronized (this) {
// 移除active资源
activeEngineResources.remove(ref.key);
if (!ref.isCacheable || ref.resource == null) {
return;
}
// 构造新的 Resource
EngineResource<?> newResource =
new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
newResource.setResourceListener(ref.key, listener);
// 回调Engine的onResourceReleased方法
// 这会导致此资源从active变成memory cache状态
listener.onResourceReleased(ref.key, newResource);
}
}
}
Engine实现了EngineResource.ResourceListener,此处的listener就是Engine,最终会回调Engine.onResourceReleased。
#Engine.java
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
如果资源可以被缓存,则缓存到 memory cache,否则对资源进行回收。
也就是说当前图片资源未在使用中时,会先从ActiveResource的弱引用缓存中将其移除,然后将该资源添加到内存缓存中。
缓存的存取过程
内存缓存
现在我们来分析下缓存的存取流程,加载开始入口从Engine.load()开始,先看下对这个方法的注释:
- 会先检查(Active Resources),如果有就直接返回,Active Resources没有被引用的资源会放入Memory Cache,如果Active Resources没有,会往下走。
- 检查Memory Cache中是否有需要的资源,如果有就返回,Memory Cache中没有就继续往下走。
- 检查当前在运行中的job中是否有该资源的下载,有就在现有的job中直接添加callback返回,不重复下载,当然前提是计算得到的key是一致的,如果还是没有,就会构造一个新的job开始新的工作。
public synchronized <R> LoadStatus load(...) {
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// focus1
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
// focus2
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
// focus3
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(...);
DecodeJob<R> decodeJob =
decodeJobFactory.build(...);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
// focus 4
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
先看到 focus 1,这一步会从 ActiveResources 中加载资源,首先判断是否使用内存缓存,否的话返回null;否则到 ActiveResources 中取数据:
// Engine.java
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
接着回到Engine.load()中继续看到focus 2,如果在cache中找到就是remove掉,然后返回EngineResource,其中需要EngineResource进行acquire一下,这个后面再看,然后会把资源移到ActiveResources中,也就是上面提到的缓存:
// Engine.java
private final MemoryCache cache;
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;
}
其中cache是MemoryCache接口的实现,如果没设置,默认在build的时候是LruResourceCache, 也就是熟悉的LRU Cache:
// GlideBuilder.java
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
关于LruCache,前面已经分析过了。
回到Engine.load方法,从缓存加载成功后的回调cb.onResourceReady(cached, DataSource.MEMORY_CACHE);可以看到:active状态的资源和memory cache状态的资源都是DataSource.MEMORY_CACHE,并且加载的资源都是 EngineResource 对象,该对象内部采用了引用计数去判断资源是否被释放,如果引用计数为0,那么会调用listener.onResourceReleased(key, this)方法通知外界此资源已经释放了。这里的listener是ResourceListener类型的接口,只有一个onResourceReleased(Key key, EngineResource resource)方法,Engine实现了该接口,此处的listener就是Engine。在Engine.onResourceReleased方法中会判断资源是否可缓存,可缓存则将此资源放入memory cache中,否则回收掉该资源,代码如下:
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
// 从activeResources中移除
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
// 存入 MemoryCache
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
思路再拉到Engine.load()的流程中,第一次加载的时候activeResources和memoryCache中都没有缓存的,判断当前是否有EngineJob在运行,,如果job已经在运行了,那么直接添加一个回调后返回LoadStatus,这个可以允许用户取消任务:
// Engine.java
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);
}
// LoadStatus
public static class LoadStatus {
private final EngineJob<?> engineJob;
private final ResourceCallback cb;
LoadStatus(ResourceCallback cb, EngineJob<?> engineJob) {
this.cb = cb;
this.engineJob = engineJob;
}
public void cancel() {
engineJob.removeCallback(cb);
}
}
接着往下看到focus 4, 到这里就需要创建后台任务去拉取磁盘文件或者发起网络请求。具体就是通过DecodeJob和EngineJob去加载资源。
DecoceJob实现了Runnable接口,然后会被EngineJob.start方法提交到对应的线程池中去执行。在DecoceJob的run方法中,会依次从
ResourceCacheGenerator
和DataCacheGenerator
中去取缓存数据,当这两者都取不到的情况下,会交给```SourceGenerator````加载网络图片或者本地资源。resource资源和data资源都是磁盘缓存中的资源。
ResourceCacheGenerator 、DataCacheGenerator和SourceGenerator
这里
ResourceCacheGenerator
、DataCacheGenerator
、SourceGenerator
均实现了DataFetcherGenerator
接口。
- ResourceCacheGenerator:从已经完成剪裁或者转换的资源缓存文件中生成DataFetchers;
- DataCacheGenerator:从未修改的原始源数据的缓存文件中生成DataFetchers;
- SourceGenerator:从原始数据中生成DataFetchers;
这三个类中的前两个就是Glide三级缓存中的第二级缓存,文件缓存,前者ResourceCacheGenerator存储的是经过剪裁或转换的图片数据,后者DataCacheGenerator存储的就是未经任何修改的原始数据。正好对应着磁盘缓存策略中的RESOURCE和DATA。
什么是DataFetcher?
就是数据加载器,他有个
loadData
方法用来从资源加载数据,这个方法必须运行在子线程中,如果缓存中没有数据,就会通过这个方法去加载原始数据。
举个例子,比如要通过一个网络url图片地址加载图片,那么需要通过SourceGenerator
生成相应的DataFetcher
通过网络获取数据;从网络上获取到数据流后,不进行任何改变会被存储到本地文件缓存中(这个本地文件默认的目录是context.getCacheDir()方法获取到的目录,文件大小250M),这个存储之前先修改runReason=SWITCH_TO_SOURCE_SERVICE
,然后重启一个线程,通过SourceGenerator
对象去做存储操作,而DataCacheGenerator
就表示获取存储的数据,然后经过解码然后将数据显示到imageview。操作完成后会往内存中再存一份数据,那么ResourceCacheGenerator
就表示获取这些已经经过解码剪裁的缓存文件。
磁盘缓存
先构造两个job,一个是EngineJob,另外一个DecodeJob,其中DecodeJob会根据需要解码的资源来源分成下面几个阶段:
private enum Stage {
/** The initial stage. */
INITIALIZE,
/** Decode from a cached resource. */
RESOURCE_CACHE,
/** Decode from cached source data. */
DATA_CACHE,
/** Decode from retrieved source. */
SOURCE,
/** Encoding transformed resources after a successful load. */
ENCODE,
/** No more viable stages. */
FINISHED,
}
在构造DecodeJob时会把状态置为INITIALIZE。
构造完两个 Job 后会调用 EngineJob.start
(DecodeJob),首先会调用getNextStage来确定下一个阶段,这里面跟DiskCacheStrategy这个传入的磁盘缓存策略有关。
磁盘策略有下面几种:
- ALL: 缓存原始数据和转换后的数据
- NONE: 不缓存
- DATA: 原始数据,未经过解码或者转换
- RESOURCE: 缓存经过解码的数据
- AUTOMATIC(默认): 根据
EncodeStrategy
和DataSource
等条件自动选择合适的缓存方
默认的AUTOMATIC方式是允许解码缓存的RESOURCE:
public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
// 省略部分代码
@Override
public boolean decodeCachedResource() {
return true;
}
@Override
public boolean decodeCachedData() {
return true;
}
};
所以在 getNextStage 会先返回
Stage.RESOURCE_CACHE,然后在start中会返回diskCacheExecutor,然后开始执行DecodeJob:
// EngineJob.java
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
//到这里之前一直都在主线程,下面这句代码就会切换到子线程
executor.execute(decodeJob);
}
// DecodeJob.java
boolean willDecodeFromCache() {
Stage firstStage = getNextStage(Stage.INITIALIZE);
return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
DecodeJob会回调run()开始执行, run()中调用runWrapped执行工作,这里runReason还是RunReason.INITIALIZE ,根据前面的分析指导这里会获得一个ResourceCacheGenerator,然后调用runGenerators:
// DecodeJob.java
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);
}
}
在 runGenerators 中,会调用 startNext,目前
currentGenerator是ResourceCacheGenerator, 那么就是调用它的startNext方法:
@Override
public boolean startNext() {
// list里面只有一个GlideUrl对象
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
// 获得了三个可以到达的registeredResourceClasses
// GifDrawable、Bitmap、BitmapDrawable
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
throw new IllegalStateException(
"Failed to find any load path from " + helper.getModelClass() + " to "
+ helper.getTranscodeClass());
}
// 遍历sourceIds中的每一个key、resourceClasses中每一个class,以及其他的一些值组成key
// 尝试在磁盘缓存中以key找到缓存文件
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}
Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
// PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,
// we only run until the first one succeeds, the loop runs for only a limited
// number of iterations on the order of 10-20 in the worst case.
// 构造key
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
// 查找缓存文件
cacheFile = helper.getDiskCache().get(currentKey);
// 如果找到了缓存文件,循环条件则会为false,退出循环
if (cacheFile != null) {
sourceKey = sourceId;
// 1. 找出注入时以File.class为modelClass的注入代码
// 2. 调用所有注入的factory.build方法得到ModelLoader
// 3 .过滤掉不可能处理model的ModelLoader
// 此时的modelLoaders值为:
// [ByteBufferFileLoader, FileLoader, FileLoader, UnitModelLoader]
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
// 如果找到了缓存文件,hasNextModelLoader()方法则会为true,可以执行循环
// 没有找到缓存文件,则不会进入循环,会直接返回false
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
// 在循环中会依次判断某个ModelLoader能不能加载此文件
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
// 如果某个ModelLoader可以,那么就调用其fetcher进行加载数据
// 加载成功或失败会通知自身
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
找缓存时key的类型为ResourceCacheKey,生成ResourceCacheKey之后会根据key去磁盘缓存中查找cacheFile = helper.getDiskCache().get(currentKey);
helper.getDiskCache()返回DiskCache接口,它的实现类是DiskLruCacheWrapper,看下DiskLruCacheWrapper.get方法。
# DiskLruCacheWrapper.java
@Override
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
...
File result = null;
try {
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
...
}
return result;
}
这里调用SafeKeyGenerator生成了一个String类型的SafeKey,实际上就是对原始key中每个字段都使用SHA-256加密,然后将得到的字节数组转换为16进制的字符串。生成SafeKey后,接着根据SafeKey去DiskCache里面找对应的缓存文件,然后返回文件。
回到ResourceCacheGenerator.startNext
方法中,如果找到了缓存会调用loadData.fetcher.loadData(helper.getPriority(), this);
这里的 fetcher 是 ByteBufferFetcher
,ByteBufferFetcher
的loadData方法中最终会执行callback.onDataReady(result)
这里callback是ResourceCacheGenerator
。
public void onDataReady(Object data) {
cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,
currentKey);
}
ResourceCacheGenerator
的onDataReady
方法又会回调DecodeJob
的onDataFetcherReady
方法进行后续的解码操作。
如果ResourceCacheGenerator没有找到缓存,就会交给DataCacheGenerator继续查找缓存。该类大体流程和ResourceCacheGenerator一样,有点不同的是,DataCacheGenerator的构造器有两个构造器,其中的DataCacheGenerator(List, DecodeHelper, FetcherReadyCallback)
构造器是给SourceGenerator准备的。因为如果没有磁盘缓存,那么从源头加载后,肯定需要进行磁盘缓存操作的。所以,SourceGenerator会将加载后的资源保存到磁盘中,然后转交给DataCacheGenerator从磁盘中取出交给ImageView展示。
看下DataCacheGenerator.startNext:
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
...
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
...
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;
}
这里的originalKey是DataCacheKey类型的,DataCacheKey构造方法如下:
DataCacheKey(Key sourceKey, Key signature)
这里的sourceKey和signature与ResourceCacheKey中的两个变量一致,从这里就可以看出:DataCache缓存的是原始的数据,ResourceCache缓存的是是被解码、转换后的数据。
如果DataCacheGenerator没有取到缓存,那么会交给SourceGenerator从源头加载。
总结一下:
- 首先在ResourceCacheGenerator没有取到缓存,那么它的startNext就会返回false,在runGenerators中就会进入循环体内:
- 接着会重复上面执行getNextStage,由于现在Stage已经是RESOURCE_CACHE,所以接下来会返回DataCacheGenerator,执行逻辑和上面的ResourceCacheGenerator是一样的,如果还是没有找到需要的,进入循环体内。
- 此时getNextStage会根据用于是否设置只从磁盘中获取资源,如果是就会通知失败,回调onLoadFailed;如果不是就设置当前Stage为Stage.SOURCE,接着往下走。
- 状态就会进入循环内部的if条件逻辑里面,调用reschedule。
- 在reschedule把runReason设置成SWITCH_TO_SOURCE_SERVICE,然后通过callback回调。
#DecodeJob.runGenerators()
if (stage == Stage.SOURCE) {
reschedule();
return;
}
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
- DecodeJob中的callback是EngineJob传递过来的,所以现在返回到EngineJob。
-在EngineJob中通过
getActiveSourceExecutor切换到网络线程池中,注意在此之前执行文件缓存的线程池一直都是diskCacheExecutor,执行DecodeJob,下面就准备开始发起网络请求。
#EngineJob.java
@Override
public void reschedule(DecodeJob<?> job) {
// Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
// up.
getActiveSourceExecutor().execute(job);
}
看下SourceGenerator的startNext方法。
@Override
public boolean startNext() {
// 首次运行dataToCache为null
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
// 首次运行sourceCacheGenerator为null
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;
}
这里的DataFetcher其实是HttpUrlFetcher,由它来发起网络请求。
// HttpUrlFetcher.java
@Override
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());
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方法。
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// cb 为 DecodeJob
cb.reschedule();
} else {
// cb 为 DecodeJob
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
先判断获取到的数据是否需要进行磁盘缓存,如果需要磁盘缓存,则经过DecodeJob、EngineJob的调度,重新调用SourceGenerator.startNext方法,此时dataToCache已经被赋值,则会调用cacheData(data);进行磁盘缓存的写入,并转交给DataCacheGenerator完成后续的处理;否则就通知DecodeJob已经加载成功。
DataCacheGenerator前面已经分析过了,就是用来加载本地原始数据的。
先看下SourceGenerator的startNext方法中调用的SourceGenerator.cacheData(data)。
# SourceGenerator.java
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());
//根据data,key,signature 创建缓存key。这个key是DataCacheKey类型的,存储是原始数据
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
//获取硬盘缓存并开始缓存
helper.getDiskCache().put(originalKey, writer);
...
} finally {
loadData.fetcher.cleanup();
}
//赋值
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
cacheData方法先构建了一个DataCacheKey将data写入了磁盘,然后new了一个DataCacheGenerator赋值给sourceCacheGenerator。回到startNext继续向下执行,此时sourceCacheGenerator不为空,就调用其startNext()方法从磁盘中加载刚写入磁盘的数据,并返回true让DecodeJob停止尝试获取数据。此时,从磁盘缓存中读取数据的逻辑已经完成,接下来是写磁盘缓存。
结论
Glide加载网络数据后, 在返回之前,会先缓存一份原始数据 。
磁盘缓存与内存缓存的关联
磁盘缓存结束后,sourceCacheGenerator被赋值,然后返回,回到startNext方法,接着往下走,会执行sourceCacheGenerator的startNext方法,
此方法最终调用loadData.fetcher.loadData(helper.getPriority(), this);这句话。由于data是流的类型,所以看ByteBufferFetcher
package com.bumptech.glide.load.model.ByteBufferFileLoader.ByteBufferFetcher
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
ByteBuffer result;
try {
//读取文件获取结果 是ByteBuffer类型
result = ByteBufferUtil.fromFile(file);
} catch (IOException e) {
callback.onLoadFailed(e);
return;
}
//回调
callback.onDataReady(result);
}
最终会调用到下面的方法
@Override
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;
//如果不是同一个线程,最终会修改runReason为RunReason.DECODE_DATA;并从起一个线程执行decodeFromRetrievedData方法
if (Thread.currentThread() != currentThread) {
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
try {
//如果是同一个线程 直接执行decodeFromRetrievedData方法
decodeFromRetrievedData();
} finally {
}
}
}
decodeFromRetrievedData
中主要调用了notifyEncodeAndRelease
方法,在后一个方法中会执行到notifyComplete
方法,notifyComplete
最终会调用到notifyCallbacksOfResult
方法,该方法调用下面的回调方法:
@Override
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()) {
//将资源缓存到activeResource中,这里就跟前面的”先从内存的二级缓存加载数据“的逻辑对应上了
activeResources.activate(key, resource);
}
jobs.removeIfCurrent(key, engineJob);
}
在这里会将资源放入activeResources中,资源变为active状态。后面会使用Executors.mainThreadExecutor()调用SingleRequest.onResourceReady回调进行资源的显示。在触发回调前后各有一个地方会对engineResource进行acquire()和release()操作,这两个操作分别发生在notifyCallbacksOfResult()方法的incrementPendingCallbacks、decrementPendingCallbacks()调用中。
CallResourceReady的run方法中也会调用engineResource.acquire(),上面的代码调用结束后,engineResource的引用计数为1。engineResource的引用计数会在RequestManager.onDestory方法中最终调用SingleRequest.clear()方法,SingleRequest.clear()内部调用releaseResource()、Engine.release 进行释放,这样引用计数就变为0。引用计数就变为0后会通知Engine将此资源从active状态变成memory cache状态。如果我们再次加载资源时可以从memory cache中加载,那么资源又会从memory cache状态变成active状态。也就是说,在资源第一次显示后,我们关闭页面,资源会由active变成memory cache;然后我们再次进入页面,加载时会命中memory cache,从而又变成active状态。
小结一下
Glide的三级缓存:第一级缓存内存缓存,内存缓存又分为两种存储形式,弱引用缓存和LRU策略的缓存方式;第二级缓存是本地文件缓存,缓存的文件大小默认是250M,本地缓存会分为存储未经修改的源数据和经过剪裁后的数据;第三级缓存就是网络/数据源缓存。
关于缓存及展示数据:数据加载出来之后,会先在缓存到本地文件中,然后再去本地缓存文件中读取数据进行编码,将编码后的数据再进行一次缓存,接着再内存中进行缓存,最后将数据编码后的图片文件发往主线程设置给target。
回答以下几个问题:
Glide网络请求回来后数据直接返回给用户还是先存再返回
不是直接返回给用户,会在SourceGenerator中构造一个DataCacheGenerator来取数据。简单来说就是先缓存到磁盘,再从磁盘里取数据返回给ImageView添加图片
Glide本地文件IO和网络请求是一个线程吗?
明显不是,本地IO通过diskCacheExecutor,而网络IO通过ActiveSourceExecutor
总结
读取内存缓存时,先从弱引用机制的内存缓存读取,再从LruCache算法机制的内存缓存读取
写入内存缓存时,先写入 弱引用机制 的内存缓存,等到图片不再被使用时,再写入到 LruCache算法机制的内存缓存;
读取磁盘缓存时,先读取转换后图片的缓存,再读取原始图片的缓存。