背景
字节池复用是什么?为什么我们需要用到这个呢?
首先来看一个问题,android里面的垃圾回收机制我们都了解过一些,当内存不足时会执行GC来释放不再被引用的对象,那么问题来了,如果在内存快要到达极限时程序又在那反复申请和回收对象会造成什么问题呢?
答案就是你申请的时候会等待一次GC,而GC本身是有一定耗时的,如果你是在UI线程里面这样做的话很快就会超过规定的执行时间,最终出现ANR。
所以抛出了问题,就需要一个解决方案,即上面提到的字节池复用就是针对图片的一种方案,它是一套框架,会对不需要的图片进行一定的保留,而不是马上回收,等下一个申请创建图片时从复用池里面寻找合适的图片返回,这样不会造成频繁的GC,而且省去了创建图片的时间,提高了效率,一举两得。
实战
既然上面提到了字节池复用的好处,这边就要寻找一个能真正应用的框架了,下面介绍一下Glide里面的图片复用池,首先看一下复用池创建的地方:
@NonNull
Glide build(@NonNull Context context) {
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
…
}
上面是Glide创建单例对象的操作,里面会初始化各种对象,其中就包括图片复用池,可以看到bitmapPool的创建分成了两种情况,其中BitmapPoolAdapter是一个空实现对象,这个是当外部设置给Glide的内存占用大小是0的情况下设置的,因为没给Glide任何内存使用,所以Glide只能按照最基本的操作,即不复用任何图片
重点还是来看一下LruBitmapPool,这个里面才是复用池的核心
public LruBitmapPool(long maxSize) {
this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
}
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;
}
上面是构造函数,可以看到主要创建了一个strategy对象,这个对象是复用池对象,同样这边又分成了两种情况,当android系统是4.4及以上时创建的是SizeConfigStrategy,反之是AttributeStrategy,还是从简单的那个开始说起
@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);
}
上面是AttributeStrategy的put和get方法,可以看到放入回收池的图片是按照宽高和config来存放的,取的时候也是,这样就有一个不足点了,因为我们创建的图片大小相同的概率不是特别高,所以每次从复用池里面寻找指定宽高的图片时很大概率是找不到匹配的,这时候就得重新创建一张了,即失去了复用池的优势
然后我们再来看一下SizeConfigStrategy的实现:
@Override
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
Key key = keyPool.get(size, bitmap.getConfig());
groupedMap.put(key, bitmap);
NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
Integer current = sizes.get(key.size);
sizes.put(key.size, current == null ? 1 : current + 1);
}
@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;
}
可以看到明显的不同,它这边存放和获取复用池的图片根据的是图片的整体大小而不是宽高,这样复用池的优势就体现出来了,只要图片整体大小大于等于你所申请的图片大小就能返回给你拿来复用,这样每次复用的概率就大大提高了
那为什么不都用这个,而出现了AttributeConfigStrategy呢?这是由于系统支持导致的,在SizeConfigStrategy里面我们看到了一个独特的函数:
result.reconfigure(width, height,
result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
这个函数是重点原因,它是android 4.4才开始支持的一个API,功能是能在一块已经分配的内存里面取出一块小于等于总分配内存的区域来作为新图片的区域,这样就不用严格按照宽高来匹配了,然后再来看一下怎样寻找最佳的大小吧:
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);
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下所有保存的图片尺寸列表,然后先从这个列表里面里面查找大于等于指定大小的尺寸,里面的ceilingKey就是用的TreeMap的ceilingKey,会返回大于等于key的值,有找到的话就拿这张图片作为复用图片。
总结
上面就是GlideBitmapPool的一个大概实现流程,现在很多项目里面基本都会引用Glide这个库,所以默认情况下已经维护了一个字节复用池,如果项目里面用到的图片处理比较多的话,这个复用池是不用白不用,不用的话反而会浪费一块内存,更有可能造成内存不足。