LruBitmapPool
Glide实现bitmap缓存的实现类。其缓存功能实现的核心依赖于两个类:SizeConfigStrategy和AttributeStrategy,在API 19之前,使用AttributeStrategy,之后使用SizeConfigStrategy。两种strategy在缓存的功能实现上是一致的,SizeConfigStrategy只是在获取缓存的时候,不严格要求缓存中的获取的图片大小一定要和获取缓存时指定的大小一致,缓存获取结果可能会比指定的大一些,因为在API 19后,通过Bitmap的reconfig就可以将其重新配置成要求的图片,即使底层的存储空间可能大于图片的实际大小。AttributeStrategy则不同,它在获取缓存的时候,必须要求图片的size和config必须严格匹配才算是缓存命中成功。
LruBitmapPool在缓存和获取缓存的时候还是用了BitmapTracker来对缓存操作做干预或者校验,例如它的一个实现就是:
private static class ThrowingBitmapTracker implements BitmapTracker {
private final Set<Bitmap> bitmaps = Collections.synchronizedSet(new HashSet<Bitmap>());
@Override
public void add(Bitmap bitmap) {
if (bitmaps.contains(bitmap)) { // 相同的bitmap不能重复添加
throw new IllegalStateException(
"Can't add already added bitmap: " + bitmap + " [" + bitmap.getWidth() + "x" + bitmap
.getHeight() + "]");
}
bitmaps.add(bitmap);
}
@Override
public void remove(Bitmap bitmap) {
if (!bitmaps.contains(bitmap)) {
throw new IllegalStateException("Cannot remove bitmap not in tracker");
}
bitmaps.remove(bitmap);
}
}
它要求,相同的图片不能多次进行缓存。
当然,LruPoolStrategy还支持扩展实现类似于SizeConfigStrategy的缓存机制。
SizeConfigStrategy
SizeConfigStrategy是一种bitmap缓存策略的实现,其实现了LruPoolStrategy接口,该接口的代码如下:
interface LruPoolStrategy {
/**
* 增加一个缓存
* @param bitmap
*/
void put(Bitmap bitmap);
/**
* 从缓存中获取指定尺寸和配置的图片
* @param width
* @param height
* @param config
* @return
*/
@Nullable
Bitmap get(int width, int height, Bitmap.Config config);
/**
* 删除一个最不常用的缓存
* @return
*/
@Nullable
Bitmap removeLast();
/**
* 得到表示这个bitmap信息的字符串
* @param bitmap
* @return
*/
String logBitmap(Bitmap bitmap);
/**
* 得到表示这个bitmap信息的字符串
* @param width
* @param height
* @param config
* @return
*/
String logBitmap(int width, int height, Bitmap.Config config);
/**
* 获取图片需要占用的空间
* @param bitmap
* @return
*/
int getSize(Bitmap bitmap);
}
SizeConfigStrategy在实现LRU时候,以bitmap的大小和config作为key,其中,构造key的静态内部类的关键代码为:
/**
* 由于存在很多图片大小相同且图片配置类型也一样的图片(这是使用Glide的前提假设),为了更加快速地构建
* 图片缓存的key,将最近常用的Key缓存起来。避免每次都使用size和config来创建一个新的对象。
*
*/
static class KeyPool extends BaseKeyPool<Key> {
public Key get(int size, Bitmap.Config config) {
Key result = get();
result.init(size, config);
return result;
}
@Override
protected Key create() {
return new Key(this);
}
}
// Visible for testing.
/**
* 将bitmap的大小和配置封装,作为后面缓存存取的key.
*
*/
static final class Key implements Poolable {
private final KeyPool pool;
@Synthetic int size;
private Bitmap.Config config;
public Key(KeyPool pool) {
this.pool = pool;
}
// Visible for testing.
Key(KeyPool pool, int size, Bitmap.Config config) {
this(pool);
init(size, config);
}
public void init(int size, Bitmap.Config config) {
this.size = size;
this.config = config;
}
@Override
public void offer() {
pool.offer(this);
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
return size == other.size
&& Util.bothNullOrEqual(config, other.config); // size和config都相等才算相等
}
return false;
}
@Override
public int hashCode() {
int result = size;
result = 31 * result + (config != null ? config.hashCode() : 0);
return result;
}
}
为了避免每存取缓存信时候都构造一个新的key,因此这里使用了KeyPool来对最近使用过前20个的由bitmap的size和config构成的key做缓存,增加内存利用率。
SizeConfigStrategy实现的关键是其中的两个关键的数据结构groupedMap和sortedSizes
groupedMap
groupedMap是GroupedLinkedMap的实例,GroupedLinkedMap内部使用了一个名为head的链表,链表的key是由bitmap size和config构成的Key,value是一个由bitmap构成的链表。这样GroupedLinkedMap中的每个元素就相当于是一个组,这个组中的bitmap具有相同的size和config,对应的存储类实现就是GroupedLinkedMap中的LinkedEntry。同时,为了加快查找速度,GroupedLinkedMap中还有一个keyToEntry的Hashmap,将key和链表中的LinkedEntry对应起来。
在GroupedLinkedMap的Put和get方法中,会将操作元素对应所在的LinkedEntry在head链表中往前移动,由于链表的移动成本很低,因存取效率很高。
sortedSizes
sortedSizes实际上是groupedMap的一个概要信息,他不做缓存,只表明SizeConfigStrategy中的GroupedLinkedMap中指定size和config的bitmap有多少。它是一个Hashmap,其中key是bitmap config,value是TreeMap,TreeMap的key为bitmap size,value为bitmap的数量,这样一来,首先通过config能够查询到缓存中bitmap config为指定值的bitmap有哪些大小的,然后每个大小后面的数据由表明了这种config和size的bitmap在内存中还有多少,所以缓存中的bitmap信息也就一目了然。
特别要注意的是SizeConfigStrategy在从缓存中获取bitmap的方法:
@Override
@Nullable
public Bitmap get(int width, int height, Bitmap.Config config) {
int size = Util.getBitmapByteSize(width, height, config);
Key bestKey = findBestKey(size, config);
Bitmap result = groupedMap.get(bestKey); // 从缓存中获取并删除
if (result != null) {
// Decrement must be called before reconfigure.
decrementBitmapOfSize(bestKey.size, result); // 已经命中一个缓存了,缓存记录信息需要减一
// 在底层图片存储不变的情况下将缓存配置成要求的图片
result.reconfigure(width, height,
result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
}
return result;
}
首先,根据width height 和 config的要求计算出bitmap的size,然后根据size和config得到需要用来进行查询的key,注意findBestKey方法的实现是这样的:
/**
* 根据大小和配置从缓存中查找相似图片对应的key
* @param size
* @param config
* @return
*/
private Key findBestKey(int size, Bitmap.Config config) {
Key result = keyPool.get(size, config);
for (Bitmap.Config possibleConfig : getInConfigs(config)) {
NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
Integer possibleSize = sizesForPossibleConfig.ceilingKey(size); // 和size最接近的大小
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
// 不一定非要完全相等
if (possibleSize != size
|| (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
keyPool.offer(result); // 表示最近访问过这个尺寸和配置的图片
result = keyPool.get(possibleSize, possibleConfig);
}
break;
}
}
return result;
}
/**
* 获取一种config对应有哪些不同大小图片的概要信息
* @param config
* @return
*/
private NavigableMap<Integer, Integer> getSizesForConfig(Bitmap.Config config) {
NavigableMap<Integer, Integer> sizes = sortedSizes.get(config);
if (sizes == null) {
sizes = new TreeMap<>();
sortedSizes.put(config, sizes);
}
return sizes;
}
注意这里在查找bitmap的时候并没有严格按照config和size一一对应的方法来查找,而是使用了TreeMap中NavigableMap接口ceilingKey来查找config严格匹配,但是size大于等于指定值的图片。这样从缓存中查找的bitmap能保证config和指定的bitmap一致,但是底层分配的内存大小大于等于新图片需要的大小,这样经过bitmap的reconfigure方法后,就一定能够得到长宽和config都满足要求的bitmap。