简介
本文主要是初步了解下 Glide 的缓存方式及实现,主要分为以下几个部分:
- Glide 的缓存方式介绍
- 内存与磁盘缓存原理
- 缓存算法的具体实现
Glide 的缓存方式介绍
Glide 缓存方式主要有两种,即内存缓存和磁盘缓存。内存缓存方面,根据数据是否当前正在使用,又可以细分为 Active 和 非 Active。磁盘缓存方面,根据是否进行过形变,又可以细分为 Resource Cache「指经过形变后的缓存」 和 Data Cache「指源数据缓存」。这里先简单了解内存和磁盘缓存。
//GlideBuilder.java
@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,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
animationExecutor,
isActiveResourceRetentionAllowed);
}
//省略部分代码...
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptionsFactory,
defaultTransitionOptions,
defaultRequestListeners,
experiments);
}
/*
memeory cache 的实现是 LruResourceCache,
在创建后会作为参数分别传入 Engine 和 Glide 对象。
disk cache factory 的实现是 InternalCacheDiskCacheFactory,
在创建后只作为参数传入 Engine 对象。
通过工厂最终创建的默认磁盘缓存是 DiskLruCacheWrapper。
*/
内存与磁盘缓存原理
从 LruResourceCache 和 DiskLruCacheWrapper 的实现上来说,其缓存原理都采用了 LRU 算法,结合源码实现具体点来看,
首先来看下内存缓存原理,
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
//MemoryCache 定义了内存缓存的行为,具体哪些行为呢?
/*
1.缓存操作:put(Key key, Resource<?> resource)
2.删除操作:remove(Key key)
3.清空操作:clearMemory()
*/
//LruCache<K, V> 是 LRU 的实现类,内部通过 LinkedHashMap 实现缓存数据的维护,
//该类已经实现了 put, get, remove, clear 等操作,作为子类,
//要实现 getSize(Y item) 方法来指定单个缓存数据的大小。LruCache 这个类的方法实现是关键。
//作为 LruResourceCache 本身,剩下需要做的事情就不多了。
}
再来看下磁盘缓存原理,
public class DiskLruCacheWrapper implements DiskCache {
//DiskCache 定义了磁盘缓存的行为,同 MemeoryCache 类似,它也定义了一些操作方法
/*
1.缓存操作:put(Key key, Writer writer)
2.删除操作:delete(Key key)
3.清空操作:clear()
4.获取操作:get(Key key),这步在 MemoryCache 里没有定义,但在 LruCache 里是定义并实现的。
除了上面的行为操作,该接口还定义了两个接口,
一个是用来创建磁盘缓存的工厂接口,一个是用来缓存读写操作的 Writer。
*/
//同 LruCache 类似,这个是磁盘缓存的 LRU 实现类。
private DiskLruCache diskLruCache;
private synchronized DiskLruCache getDiskCache() throws IOException {
if (diskLruCache == null) {
//diskLruCache 对象通过 open 方法创建
diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
}
return diskLruCache;
}
}
DiskLruCache 会比内存缓存的 LruCache 复杂些,表现在:1.涉及到线程池操作,2.涉及到文件读写操作。但虽说有差别,核心点(用 LinkedHashMap 实现缓存集合)是一样的。
缓存算法的具体实现
在分析 Glide 缓存算法实现之前,先简单了解下 LRU 算法。
LRU(Least Recently Used)该算法是一种页面置换算法,对每个页面增加一个用来记录使用时间的字段,在存储空间满的情况下,根据比较使用时间字段,删除最近最少使用的页面,从而用来存放新页面。
从原理上可知:1.存储空间是受限的,也就是说有个存储空间初始值。2.需要一个数据结构能满足内容替换,也就是找出最近最少使用的元素。
LruCache 的 put 方法实现分析,
public class LruCache<T, Y> {
private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);
public LruCache(long size) {
//构造方法里做的就是初始化存储空间元素数量的多少(也就是总共能放多少页,放满了就要置换了)
//注意!!!这个 size 是存储空间总大小,单位是和 getSize 是一致的
/*
Glide 用 MemorySizeCalculator 的 memoryCacheSize 字段对其进行的赋值,这个值的计算
会根据设备屏幕尺寸有所不同。单位是 byte
*/
this.initialMaxSize = size;
this.maxSize = size;
}
@Nullable
public synchronized Y put(@NonNull T key, @Nullable Y item) {
//通过 getSize 方法获取到 item 元素的空间大小,以 byte 为单位
final int itemSize = getSize(item);
if (itemSize >= maxSize) {
//这个的实现交给了子类
onItemEvicted(key, item);
return null;
}
if (item != null) {
currentSize += itemSize;
}
//利用 LinkedHashMap 类型的 cache 存入 item 值
//根据返回值判断 cache 中是否有旧的元素需要去除
@Nullable Entry<Y> old = cache.put(key,
item == null ? null : new Entry<>(item, itemSize));
if (old != null) {
currentSize -= old.size;
if (!old.value.equals(item)) {
onItemEvicted(key, old.value);
}
}
//检验空间是否足够,不够的话进行元素空间置换
evict();
return old != null ? old.value : null;
}
private void evict() {
trimToSize(maxSize);
}
protected synchronized void trimToSize(long size) {
Map.Entry<T, Entry<Y>> last;
Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;
while (currentSize > size) {
cacheIterator = cache.entrySet().iterator();
//这里直接拿到的 cache 里的第一个元素,
//因为是 LinkedHashMap,这样一来最近最少使用的就是第一个
//循环释放,直到空间足够为止
last = cacheIterator.next();
final Entry<Y> toRemove = last.getValue();
currentSize -= toRemove.size;
final T key = last.getKey();
cacheIterator.remove();
onItemEvicted(key, toRemove.value);
}
}
//用于包装存储元素的静态类,包含具体的元素及其空间大小
static final class Entry<Y> {
final Y value;
final int size;
@Synthetic
Entry(Y value, int size) {
this.value = value;
this.size = size;
}
}
}
DiskLruCache 的 put 方法实现分析,
public final class DiskLruCache implements Closeable {
//在通过 InternalCacheDiskCacheFactory 创建时指定了默认大小 250MB
private long maxSize;
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(0, 0.75f, true);
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean committed;
public File getFile(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.getDirtyFile(index);
directory.mkdirs();
return dirtyFile;
}
}
}
}
//在磁盘缓存处理上,行为操作是通过 DiskLruCacheWrapper 对象操作的,来看下 DiskLruCacheWrapper 类
//DiskLruCacheWrapper
public class DiskLruCacheWrapper implements DiskCache {
private DiskLruCache diskLruCache;
@Override
public void put(Key key, Writer writer) {
//SafeKeyGenerator 确保能获取到一个 safeKey
String safeKey = safeKeyGenerator.getSafeKey(key);
try {
try {
DiskLruCache diskCache = getDiskCache();
//通过 diskCache 获取 safeKey 对应的值
Value current = diskCache.get(safeKey);
if (current != null) {
return;
}
//获取 safeKey 对应的编辑器
DiskLruCache.Editor editor = diskCache.edit(safeKey);
try {
File file = editor.getFile(0);
//通过 Writer 将文件数据写入到 file 中
if (writer.write(file)) {
//如果写入成功,就执行 commit
editor.commit();
}
}
}
}
}
DiskLruCache 比 LruCache 的实现会复杂一些,暂且先做简单了解,后续再进行深入分析。