Glide
总结
Glide缓存简介
Glide 缓存机制可以说是设计的非常完美,
在缓存这一功能上,Glide又将它分成了两个模块,一个是内存缓存,一个是硬盘缓存。
以一张表格来说明下 Glide 缓存。
缓存类型 | 缓存代表 | 说明 |
---|---|---|
活动缓存 | ActiveResources | 如果当前对应的图片资源是从内存缓存中获取的,那么会将这个图片存储到活动资源中。 |
内存缓存 | LruResourceCache | 图片解析完成并最近被加载过,则放入内存中 |
磁盘缓存-资源类型 | DiskLruCacheWrapper | 被解码后的图片写入磁盘文件中 |
磁盘缓存-原始数据 | DiskLruCacheWrapper | 网络请求成功后将原始数据在磁盘中缓存 |
在介绍缓存原理之前,先来看一张加载缓存执行顺序,先有个印象。
这两个缓存模块的作用各不相同,内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。
缓存Key
不管是内存缓存还是磁盘缓存,存储的时候肯定需要一个唯一 key 值.
根据注释和代码可以看到传入的参数之多,主要是根据 url ,签名,宽高等,其内部重写了 hashCode,equals,来保证对象的唯一性。
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public synchronized 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,
Executor callbackExecutor) {
....
//1. 生成缓存唯一 key 值,model 就是图片地址
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
....
}
...
}
//生成 key
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);
}
class EngineKey implements Key {
...
@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;
}
...
}
signature、width、height等等10个参数一起传入到EngineKeyFactory的buildKey()方法当中,从而构建出了一个EngineKey对象,这个EngineKey也就是Glide中的缓存Key了。
可见,决定缓存Key的条件非常多,即使你用override()方法改变了一下图片的width或者height,也会生成一个完全不同的缓存Key。
内存缓存
2 个内存缓存(一个 Map + 弱引用,一个 LRU 内存缓存)
开启内存缓存
通过下面代码开启内存缓存,当然 Glide 默认是为我们开启了内存缓存所以不需要我们调用 skipMemoryCache
//在 BaseRequestOptions 成员变量中默认为内存缓存开启。
private boolean isCacheable = true;
//调用层调用
Glide.
with(MainActivity.this.getApplication()).
//开启使用内存缓存
skipMemoryCache(true).
into(imageView);
从内存缓存加载
生成key,先加载活动缓存,如果活动缓存没有在加载内存缓存
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public synchronized LoadStatus load(
....//参数
) {
....
//1. 生成缓存唯一 key 值,model 就是图片地址
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//2. 优先加载内存中的活动缓存 - ActiveResources
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;
}
//3. 如果活动缓存中没有,就加载 LRU 内存缓存中的资源数据。
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;
}
...
}
ActiveResources 活动资源
活动缓存是 Map + WeakReference
来进行维护的,这样做的好处是避免图片资源内存泄漏。
获取活动资源
loadFromActiveResources获取活动缓存,如果获取到,引用计数加1,onResourceReady回调返回上层
Engine.java
public synchronized LoadStatus load(
....//参数
) {
//1. 优先加载内存中的活动缓存 - ActiveResources
EngineResource> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
// 如果找到获取资源,就返回上层
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
...
}
@Nullable
private EngineResource> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
// 通过 活动资源的 get 函数拿到活动资源的缓存
EngineResource> active = activeResources.get(key);
if (active != null) {
//正在使用,引用计数 +1
active.acquire();
}
return active;
}
final class ActiveResources {
//
@VisibleForTesting
final Map activeEngineResources = new HashMap<>();
private final ReferenceQueue> resourceReferenceQueue = new ReferenceQueue<>();
...
//外部调用 get 函数拿到活动资源缓存
@Nullable
synchronized EngineResource> get(Key key) {
//通过 HashMap + WeakReference 的存储结构
//通过 HashMap 的 get 函数拿到活动缓存
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
//继承 WeakReference 弱引用,避免内存泄漏。
@VisibleForTesting
static final class ResourceWeakReference extends WeakReference> {
....
}
内存缓存
Android缓存机制——LruCache
创建缓存对象LruResourceCache
拿到内存缓存的时候,先将内存缓存的当前 key 删除了,如果获取到,引用计数加1,然后添加到活动缓存中 ,onResourceReady回调返回上层
在Engine load函数中
//1. 如果活动缓存中没有,就加载 LRU 内存缓存中的资源数据。
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;
}
// 加载内存中图片资源
private EngineResource> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
//2. 拿到内存缓存,内部将当前的 key 缓存 remove 了
EngineResource> cached = getEngineResourceFromCache(key);
if (cached != null) {
// 3. 如果内存缓存不为空,则引用计数器 +1
cached.acquire();
//4. 添加进活动缓存中
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource> getEngineResourceFromCache(Key key) {
//将当前的 key 缓存 remove
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;
}
资源释放
EngineJob中发出的通知回调,告知Engine onResourceReleased来删除 活动资源。
class EngineJob implements DecodeJob.Callback,
Poolable {
....
@Override
public void onResourceReady(Resource resource, DataSource dataSource) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
}
notifyCallbacksOfResult();
}
@Synthetic
void notifyCallbacksOfResult() {
.....
//回调上层 Engine 任务完成了
listener.onEngineJobComplete(this, localKey, localResource);
//遍历资源回调给 ImageViewTarget ,并显示
for (final ResourceCallbackAndExecutor entry : copy) {
entry.executor.execute(new CallResourceReady(entry.cb));
}
//这里是通知发出上层删除活动资源数据
decrementPendingCallbacks();
}
....
}
//重要的就是这里了
@Synthetic
synchronized void decrementPendingCallbacks() {
if (decremented == 0) {
if (engineResource != null) {
//如果不为空,那么就调用内部 release 函数
engineResource.release();
}
}
}
EngineResource 的 release 函数
class EngineResource implements Resource {
...
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) {
//回调出去,Engine 来接收
listener.onResourceReleased(key, this);
}
}
}
}
...
}
当完全没有使用这样图片的时候,就会把活动资源清理掉
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
。。。
//接收来自 EngineResource 的调用回调
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource> resource) {
//1. 收到当前图片没有引用,清理图片资源
activeResources.deactivate(cacheKey);
//2. 如果开启了内存缓存
if (resource.isCacheable()) {
//3. 将缓存存储到内存缓存中。
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
。。。
}
首先会将缓存图片从 activeResources 中移除,然后再将它 put 到 LruResourceCache 内存缓存当中。
正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用 LruCache 来进行缓存的功能
小结
总结下步骤:
第一次加载没有获取到活动缓存。
接着加载内存资源缓存,先清理掉内存缓存,在添加进行活动缓存。
第二次加载活动缓存已经存在。
当前图片引用为 0 的时候,清理活动资源,并且添加进内存资源。
又回到了第一步,然后就这样环环相扣。
磁盘缓存
缓存表示 | 说明 |
---|---|
DiskCacheStrategy.NONE | 表示不开启磁盘缓存 |
DiskCacheStrategy.RESOURCE | 表示只缓存转换之后的图片。 |
DiskCacheStrategy.ALL | 表示既缓存原始图片,也缓存转换过后的图片。 |
DiskCacheStrategy.DATA | 表示只缓存原始图片 |
DiskCacheStrategy.AUTOMATIC | 根据数据源自动选择磁盘缓存策略(默认选择) |
当我们使用 Glide 去加载一张图片的时候,Glide 默认并不会将原始图片展示出来,而是会对图片进行压缩和转换,总之就是经过种种一系列操作之后得到的图片,就叫转换过后的图片。
Glide 默认情况下在硬盘缓存的就是 DiskCacheStrategy.AUTOMATIC
以下面的代码来开启磁盘缓存:
Glide.
with(MainActivity.this.getApplication()).
//使用磁盘资源缓存功能
diskCacheStrategy(DiskCacheStrategy.RESOURCE).
into(imageView);
DiskCacheStrategy.RESOURCE 资源类型
获取资源数据
如果在活动缓存、内存缓存中没有找数据,那么就重新开启一个 GlideExecutor 线程池在 DecodeJob run 执行新的请求下面我们就直接来看 DecodeJob run 函数,跟着它去找 资源数据的加载:
class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable>,
Poolable {
...
@Override
public void run() {
...
try {
//如果取消就通知加载失败
if (isCancelled) {
notifyFailed();
return;
}
//1. 执行runWrapped
runWrapped();
} catch (CallbackException e) {
...
}
}
...
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
//2. 找到执行的状态
stage = getNextStage(Stage.INITIALIZE);
//3. 找到具体执行器
currentGenerator = getNextGenerator();
//4. 开始执行
runGenerators();
break;
...
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE: //3.1解码后的资源执行器
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE://原始数据执行器
return new DataCacheGenerator(decodeHelper, this);
case SOURCE://新的请求,http 执行器
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
通过上面分析的代码跟注释,我们知道这里是在找具体的执行器,找完了之后注释 4 开始执行,现在我们直接看注释 4。先提一下 注释 3.1 因为外部我们配置的是 RESOURCE 磁盘资源缓存策略,所以直接找到的是 ResourceCacheGenerator 执行器。
private void runGenerators() {
//如果当前任务没有取消,执行器不为空,那么就执行 currentGenerator.startNext() 函数
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
..
}
通过上面代码可知,主要是执行 currentGenerator.startNext() 就句代码,currentGerator 是一个接口,通过注释 3.1 我们知道这里它的实现类是 ResourceCacheGenerator ,那么我们具体看下 ResourceCacheGenerator 的 startNext 函数;
class ResourceCacheGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback
通过上面注释可以得到几点信息
- 首先根据 资源 ID 等一些信息拿到资源缓存 Key
- 通过 key 拿到缓存文件
- 构建一个 ByteBufferFetcher 加载缓存文件
- 加载完成之后回调到 DecodeJob 中。
存储资源数据
先来看下面一段代码:
class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable>,
Poolable {
...
private void notifyEncodeAndRelease(Resource resource, DataSource dataSource) {
....
stage = Stage.ENCODE;
try {
//1. 是否可以将转换后的图片缓存
if (deferredEncodeManager.hasResourceToEncode()) {
//1.1 缓存入口
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
...
}
onEncodeComplete();
}
}
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection("DecodeJob.encode");
try {
//1.2 将 Bitmap 缓存到资源磁盘
diskCacheProvider.getDiskCache().put(key,
new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
通过上面我们知道 http 请求到图片输入流之后经过一系列处理,转换得到目标 Bitmap 资源,最后通过回调到 DecodeJob 进行缓存起来。
清理资源缓存
- 用户主动通过系统来清理
- 卸载软件
- 调用 DisCache.clear();
DiskCacheStrategy.DATA 原始数据类型
获取原始数据
参考上小节DiskCacheStrategy.RESOURCE 获取资源,不同的是把 ResourceCacheGenerator 换成 DataCacheGenerator 加载了。
存储原始数据
这里既然存的是原始数据那么我们直接从 http 请求之后的响应数据开始查看,通过上一篇我们知道是在 HttpUrlFetcher 中请求网络,直接定位到目的地:
public class HttpUrlFetcher implements DataFetcher {
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//1. 通过 loadDataWithRedirects 来进行http 请求,返回 InputStream
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//2. 将请求之后的数据返回出去
callback.onDataReady(result);
} catch (IOException e) {
...
} finally {
...
}
}
}
根据注释可以得知,这里主要用于网络请求,请求响应数据回调给 MultiModelLoader 中。我们看下 它具体实现:
class MultiModelLoader implements ModelLoader {
...
@Override
public void onDataReady(@Nullable Data data) {
//如果数据不为空,那么就回调给 SourceGenerator
if (data != null) {
callback.onDataReady(data);
} else {
startNextOrFail();
}
}
....
}
这里的 callback 指的是 SourceGenerator ,继续跟
class SourceGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback
通过上面注释可以知道 cb.reschedule(); 最后回调到 EngineJob 类,会执行 reschedule(DecodeJob> job) 函数的 getActiveSourceExecutor().execute(job); 用线程池执行任务,最后又回到了 DecodeJob 的 run 函数 拿到执行器DataCacheGenerator ,最终会在 SourceGenerator 的 startNext() 函数,之前流程代码我就不贴了,上面讲了很多次了,相信大家应该记得了,我们直接看 startNext() 函数吧:
class SourceGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback
通过上面代码得知,这里将原始数据写入文件中了。
清理资源缓存
- 用户主动通过系统来清理
- 卸载软件
- 调用 DisCache.clear();
磁盘缓存小节
存储
- 资源缓存是在把图片转换完之后才缓存;
- 原始数据是网络请求成功之后就写入缓存;
获取 - 资源缓存跟原始数据都是在 GlideExecutor 线程池中,Decodejob 中检查获取数据。
复用池
Glide 中复用池也起了一个很大的作用,这里我就不贴代码了,因为这个很好理解,大家可以去 Glide 中的 Downsample 详细了解。在这里我就简单说一下 Glide 中复用池的处理。
在 Glide 中,在每次解析一张图片为 Bitmap 的时候不管是内存缓存还是磁盘缓存,都会从其BitmapPool 中查找一个可被复用的 Bitmap ,之后在将此块的内存缓存起来。
注意:在使用复用池的时候,如果存在能被复用的图片会重复使用该图片的内存。 所以复用并不能减少程序正在使用的内存大小。Bitmap 复用,解决的是减少频繁申请内存带来的性能(抖动、碎片)问题。
参考
Android 图片加载框架 Glide 4.9.0 (一) 从源码的角度分析 Glide 执行流程
Android 图片加载框架 Glide 4.9.0 (二) 从源码的角度分析 Glide 缓存策略
深入探究Glide的缓存机制
Glide——缓存分析整理