Glide源码分析五——缓存相关
DataSource(数据源)
数据源有5种类型:
- LOCAL:本地数据,例如本地图片文件,也可能是通过ContentProvider共享的远程数据,比如在ContentProvider中进行网络请求。
- REMOTE:远程数据,例如通过网络请求数据。
- DATA_DISK_CACHE:缓存在磁盘的原始数据。
- RESOURCE_DISK_CACHE:缓存在磁盘的解码、变换后的数据。
- MEMORY_CACHE:存储在内存中的数据。
DiskCacheStrategy(磁盘缓存策略)
我们知道,在使用Glide进行请求时,可以设置磁盘缓存策略。之后在DataFetcherGenerator请求数据成功后,会根据磁盘缓存策略DiskCacheStrategy和LoadData的Fetcher对应的DataSource,来决定是否缓存数据。
主要有:
- ALL(原始数据和处理后的数据都缓存)
- NONE(都不缓存)
- DATA(只缓存原始数据)
- RESOURCE(只缓存处理后的数据,即经过缩放等转换后的数据)
- AUTOMATIC(它会尝试对本地和远程图片使用最佳的策略。
当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。默认使用这种磁盘缓存策略)
// DiskCacheStrategy.java
public abstract class DiskCacheStrategy {
// 原始数据是否可以缓存
public abstract boolean isDataCacheable(DataSource dataSource);
// 解码(变换)后的数据是否可以缓存
public abstract boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);
// 是否可以解码之前缓存的解码(变换)后的数据
public abstract boolean decodeCachedResource();
// 是否可以解码之前缓存的原始数据
public abstract boolean decodeCachedData();
public static final DiskCacheStrategy ALL =
new DiskCacheStrategy() {
// 如果是远程数据,则可以缓存成原始数据
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}
// 如果是本地数据、远程数据和缓存在磁盘中的原始数据,则可以缓存成解码、变换后的数据RESOURCE_DISK_CACHE
// 因为缓存在磁盘中的解码、变换后的数据RESOURCE_DISK_CACHE没有必要再次缓存,
// 而内存中如果有数据的话,说明之前已经通过其他4种方式加载过资源了,如果可以缓存的话已经缓存了,也就没必要缓存内存数据了
@Override
public boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return dataSource != DataSource.RESOURCE_DISK_CACHE
&& dataSource != DataSource.MEMORY_CACHE;
}
// 可以解码缓存在磁盘中的解码、变换后的数据
@Override
public boolean decodeCachedResource() {
return true;
}
// 可以解码缓存在磁盘中的原始数据
@Override
public boolean decodeCachedData() {
return true;
}
};
public static final DiskCacheStrategy AUTOMATIC =
new DiskCacheStrategy() {
// 如果是远程数据,则可以缓存成原始数据
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}
// 如果是缓存在磁盘的原始数据或 需要transform的本地数据,则可以缓存成解码、变换后的数据
@Override
public boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
|| dataSource == DataSource.LOCAL)
&& encodeStrategy == EncodeStrategy.TRANSFORMED;
}
// 可以解码缓存在磁盘中的解码、变换后的数据
@Override
public boolean decodeCachedResource() {
return true;
}
// 可以解码缓存在磁盘中的原始数据
@Override
public boolean decodeCachedData() {
return true;
}
};
}
BitmapPool
BitmapPool.png
Android 2.3.3(API 级别 10)及以下的版本:Bitmap内存在Native层,使用Bitmap对象的recycle方法回收内存,无法复用。
从Android3.0开始到8.0以前,bitmap对象存储在java堆内存中,java虚拟机会自动回收内存。
在8.0以后Bitmap内存在Native层,所以我们要手动释放bitmap。
在 Android 4.4(API 级别 19)之前(3.0-4.3)复用bitmap的前提是:
- 被解码的图像必须是 JPEG 或 PNG 格式
- 被复用的图像宽高必须等于 解码后的图像宽高
- 解码图像的 BitmapFactory.Options.inSampleSize 设置为 1 , 也就是不能缩放
- 被复用的图像的像素格式 Config ( 如 RGB_565 ) 会覆盖设置的 BitmapFactory.Options.inPreferredConfig 参数
在 4.4 之后只要 inBitmap 的大小比目标 Bitmap 大即可,且解码图像的BitmapFactory.Options.inSampleSize 可以大于1。
BitmapPool是一个Bitmap对象池,用于复用已有的未recycle的Bitmap对象。
它主要有两个子类:
- BitmapPoolAdapter,空实现
- LruBitmapPool,基于LruPoolStrategy策略来缓存对象。
GlideBuilder在创建Glide时,根据memorySizeCalculator.getBitmapPoolSize()
获取设备推荐的bitmap缓存池大小(字节为单位),如果大于0,则使用LruBitmapPool,否则使用BitmapPoolAdapter。
我们也可以自己实现BitmapPool,设置给Glide。
MemoryCache(二级内存缓存)
MemoryCache.png主要有两个子类:
- MemoryCacheAdapter:空实现
- LruResourceCache:默认实现,继承自LruCache,实现了MemoryCache,内部使用LinkedHashMap来实现LRU。
LruResourceCache的key是一个自定义的Key类对象,维护了width、height、url等信息。
LruResourceCache的value是一个Resource<?>包装类,因为Glide支持多种类型的结果,例如Bitmap、Drawable等。
对于低内存设备,默认的缓存大小是应用内存(系统为每个应用分配的近似内存值)的33%,非低内存设备则是40%。ps:这里的缓存包括:Resouce的缓存、Bitmap对象池、数组对象池,这三种都采用了Lru算法进行缓存。
我们也可以自己实现MemoryCache类设置给Glide。
ActiveResources(一级内存缓存)
ActiveResources.png正在被使用的资源会被放入ActiveResources。
// ActiveResources.java
final class ActiveResources {
// 是否允许正在使用的资源被保留(如果是的话,在ResourceWeakReference中会强引用该资源,release时需要手动置空)
private final boolean isActiveResourceRetentionAllowed;
// 用于将resourceReferenceQueue中被回收的ResourceWeakReference从activeEngineResources中移除
private final Executor monitorClearedResourcesExecutor;
// 正在被使用的资源
@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
// 当ResourceWeakReference引用的资源被回收时,ResourceWeakReference会被放入该队列
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
// 资源释放监听
private ResourceListener listener;
// 是否中断,是的话,monitorClearedResourcesExecutor将会停止工作
private volatile boolean isShutdown;
ActiveResources(
boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {
@Override
public void run() {
// 执行清理工作,将resourceReferenceQueue中被回收的ResourceWeakReference从activeEngineResources中移除
cleanReferenceQueue();
}
});
}
// 将正在使用的资源包装成ResourceWeakReference,并放入activeEngineResources
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
// 如果该key之前已经有对应的资源了,则清理旧资源
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
// 将key对应的ResourceWeakReference从activeEngineResources中移除,并清理旧资源
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
// 将ResourceWeakReference从activeEngineResources中移除,如果ResourceWeakReference强引用了资源,则回调通知Engine放入MemoryCache中
void cleanupActiveReference(ResourceWeakReference ref) {
synchronized (this) {
// 将key对应的ResourceWeakReference从activeEngineResources中移除
activeEngineResources.remove(ref.key);
// 如果资源没有被强引用则return
if (!ref.isCacheable || ref.resource == null) {
return;
}
}
// 如果资源被强引用了,则回调通知Engine放入MemoryCache中
EngineResource<?> newResource =
new EngineResource<>(
ref.resource, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ false, ref.key, listener);
listener.onResourceReleased(ref.key, newResource);
}
// 执行清理工作,将resourceReferenceQueue中被回收的ResourceWeakReference从activeEngineResources中移除
void cleanReferenceQueue() {
while (!isShutdown) {
// 从队列中获取被回收的资源,当队列没数据时会阻塞在这里
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
// 清理ResourceWeakReference及其引用资源
cleanupActiveReference(ref);
// ... 回调通知ResourceWeakReference从resourceReferenceQueue中移除了
}
}
// 关闭清理资源的线程池
void shutdown() {
isShutdown = true;
if (monitorClearedResourcesExecutor instanceof ExecutorService) {
ExecutorService service = (ExecutorService) monitorClearedResourcesExecutor;
Executors.shutdownAndAwaitTermination(service);
}
}
// 弱引用类,用于引用正在使用的资源,当资源被回收时会被放入resourceReferenceQueue,之后会在线程池中将它从activeEngineResources中移除,如果被引用的资源是可缓存在内存中的,则会强引用资源,在被清理时,会回调到Engine中放入MemoryCache中
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
// 资源的key
final Key key;
// 资源是否可以缓存在内存中
final boolean isCacheable;
// 被引用的资源(如果资源是可缓存在内存中的,则不为null)
Resource<?> resource;
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.isMemoryCacheable() && isActiveResourceRetentionAllowed
? Preconditions.checkNotNull(referent.getResource())
: null;
isCacheable = referent.isMemoryCacheable();
}
// 清理资源
void reset() {
resource = null;
clear();
}
}
}
通过上面代码的注释,可以知道:
- 正在使用的资源会被转成弱引用ResourceWeakReference,保存在ActiveResouces的activeEngineResources中。
- 当资源被jvm回收时,ResourceWeakReference会被放入队列中。
- ActiveResources中有个线程池,会循环从队列中取被回收资源的ResourceWeakReference,将它从activeEngineResources中移除。如果ResourceWeakReference.resource不为null(条件:isActiveResourceRetentionAllowed为true,且该资源可以缓存在内存中),则会回调到Engine中将资源加入MemoryCache。