Glide的Bitmap的回收机制
Glide提供了Bitmap对象的回收和再利用的机制,可以有效的提高资源的再利用。
Key池
Bitmap是存在map中的,那么存入和读取就需要个key。Glide为我们提供了一个比较好的key的解决方案。
接口定义
BaseKeyPool.java
这个是Key池的定义,整个Key是维护在一个Queue中的。
abstract class BaseKeyPool<T extends Poolable> {
private static final int MAX_SIZE = 20;
private final Queue<T> keyPool = Util.createQueue(MAX_SIZE);
T get() {
T result = keyPool.poll();
if (result == null) {
result = create();
}
return result;
}
public void offer(T key) {
if (keyPool.size() < MAX_SIZE) {
keyPool.offer(key);
}
}
abstract T create();
}
Poolable.java是Key定义了key的唯一操作,即添加到队列中
interface Poolable {
void offer();
}
架构的图:
具体的一个实例
Key:
static final class Key implements Poolable {
private final KeyPool pool;
@Synthetic int size;
Key(KeyPool pool) {
this.pool = pool;
}
public void init(int size) {
this.size = size;
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
return size == other.size;
}
return false;
}
@Override
public int hashCode() {
return size;
}
@Override
public String toString() {
return getBitmapString(size);
}
@Override
public void offer() {
pool.offer(this);
}
}
KeyPool:
static class KeyPool extends BaseKeyPool<Key> {
public Key get(int size) {
Key result = super.get();
result.init(size);
return result;
}
@Override
protected Key create() {
return new Key(this);
}
}
设计上的亮点
这个设计上的亮点是,我并不实际的存储某某Key的某某bitmap对象,而是只是维护着一个key的队列,从尾部插入,需要就从头部取出来。
那不是取出的key不是我存入的key吗?确实不是的,但没关系,从上面的代码可以看出,取出后,根据我传入的参数重新刷新的Key的值,变成我需要的对象。
也就是说,减去了对象的实例化,共用了对象本身。
Pool池的缓存策略
缓存池提供了3中不同存储策略的池子:
SizeStrategy
根据图片的size不同而进行的缓存策略。因此这个缓存策略key的生成是根据size来的。
主要代码:
@Override
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
final Key key = keyPool.get(size);
groupedMap.put(key, bitmap);
Integer current = sortedSizes.get(key.size);
sortedSizes.put(key.size, current == null ? 1 : current + 1);
}
@Override
@Nullable
public Bitmap get(int width, int height, Bitmap.Config config) {
final int size = Util.getBitmapByteSize(width, height, config);
Key key = keyPool.get(size);
Integer possibleSize = sortedSizes.ceilingKey(size);
if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) {
keyPool.offer(key);
key = keyPool.get(possibleSize);
}
final Bitmap result = groupedMap.get(key);
if (result != null) {
result.reconfigure(width, height, config);
decrementBitmapOfSize(possibleSize);
}
return result;
}
这里我们主要关注下bitmap的大小的计算:
public static int getBitmapByteSize(int width, int height, @Nullable Bitmap.Config config) {
return width * height * getBytesPerPixel(config);
}
private static int getBytesPerPixel(@Nullable Bitmap.Config config) {
// A bitmap by decoding a GIF has null "config" in certain environments.
if (config == null) {
config = Bitmap.Config.ARGB_8888;
}
int bytesPerPixel;
switch (config) {
case ALPHA_8:
bytesPerPixel = 1;
break;
case RGB_565:
case ARGB_4444:
bytesPerPixel = 2;
break;
case RGBA_F16:
bytesPerPixel = 8;
break;
case ARGB_8888:
default:
bytesPerPixel = 4;
break;
}
return bytesPerPixel;
}
AttributeStrategy
根据图片的width、height和config不同而进行的缓存策略。因此这个缓存策略key的生成是根据上面的三个参数来共同决定。
它的主要操作的代码:
@Override
public void put(Bitmap bitmap) {
final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
groupedMap.put(key, bitmap);
}
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
final Key key = keyPool.get(width, height, config);
return groupedMap.get(key);
}
SizeConfigStrategy
根据图片的size和config不同而进行的缓存策略。因此这个缓存策略key的生成是根据上面的两个参数来共同决定。
Glide是怎么抉择使用的?
private static LruPoolStrategy getDefaultStrategy() {
final LruPoolStrategy strategy;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
strategy = new SizeConfigStrategy();
} else {
strategy = new AttributeStrategy();
}
return strategy;
}
当然我们是可以自己设定这个策略的。
Pool池
Glide为我们提供了2种池子。
- BitmapPool
- ArrayPool
BitmapPool
接口定义:
public interface BitmapPool {
long getMaxSize();
void setSizeMultiplier(float sizeMultiplier);
void put(Bitmap bitmap);
Bitmap get(int width, int height, Bitmap.Config config);
Bitmap getDirty(int width, int height, Bitmap.Config config);
void clearMemory();
void trimMemory(int level);
}
它的实现者是LruBitmapPool.java
,具体分析:
Pool缓存策略的选择
如上面
缺省的允许缓存的Bitmap.Config
private static Set<Bitmap.Config> getDefaultAllowedConfigs() {
Set<Bitmap.Config> configs = new HashSet<>(Arrays.asList(Bitmap.Config.values()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// GIFs, among other types, end up with a native Bitmap config that doesn't map to a java
// config and is treated as null in java code. On KitKat+ these Bitmaps can be reconfigured
// and are suitable for re-use.
configs.add(null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
configs.remove(Bitmap.Config.HARDWARE);
}
return Collections.unmodifiableSet(configs);
}
put加入数据
@Override
public synchronized void put(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
if (bitmap.isRecycled()) {
throw new IllegalStateException("Cannot pool recycled bitmap");
}
if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize
|| !allowedConfigs.contains(bitmap.getConfig())) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Reject bitmap from pool"
+ ", bitmap: " + strategy.logBitmap(bitmap)
+ ", is mutable: " + bitmap.isMutable()
+ ", is allowed config: " + allowedConfigs.contains(bitmap.getConfig()));
}
bitmap.recycle();
return;
}
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);
tracker.add(bitmap);
puts++;
currentSize += size;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
}
dump();
evict();
}
加入的操作是线程安全的。请注意上面的if判断。
ArrayPool
接口的定义:
public interface ArrayPool {
int STANDARD_BUFFER_SIZE_BYTES = 64 * 1024;
<T> void put(T array, Class<T> arrayClass);
<T> void put(T array);
<T> T get(int size, Class<T> arrayClass);
<T> T getExact(int size, Class<T> arrayClass);
void clearMemory();
void trimMemory(int level);
}
给个结构图,具体代码大同小异:
总结
分析了Glide,那么个人觉得设计一个对象的回收机制主要有下面几点:
- Key的设计
- 缓存的策略
- 具体的缓存池子