Bitmap的缓存结构设计
2021-05-31 本文已影响0人
Shimmer_
1. 整体思路设计
采用三级缓存结构:内存-磁盘-网络,缓存使用的是LruCache算法,最近最少使用缓存算法
- 内存缓存使用API自带实现的LruCache来满足
- 磁盘缓存使用官方推荐的DiskLruCache来满足
- 内存资源比较珍贵,在LruCache的基础上,增加了复用池及回收队列来提高效率

2. 分块解析
A. 获取结构
这部分结构比较简单,依次取出,为空时进行资源获取,同时对获取的资源进行大小优化、内存复用
/**
* 返回压缩图片
*
* @param context
* @param filePath
* @param maxW
* @param maxH
* @param hasAlpha
* @return
*/
public static Bitmap resizeBitmap(Context context, String filePath, int maxW, int maxH, boolean hasAlpha, Bitmap reusable) {
BitmapFactory.Options options = new BitmapFactory.Options();
// 设置为true后,再去解析,就只解析 out 参数
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
int w = options.outWidth;
int h = options.outHeight;
options.inSampleSize = calcuteInSampleSize(w, h, maxW, maxH);
if (!hasAlpha) {
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
options.inJustDecodeBounds = false;
// 复用, inMutable 为true 表示易变
options.inMutable = true;
options.inBitmap = reusable;
return BitmapFactory.decodeFile(filePath, options);
}
大小优化:按需解析(宽高、是否需要透明度)
内存复用:使用可复用的内存(inBitmap),并将自身设置可变(inMutable)
B. 缓存结构拆分
磁盘缓存并不需要额外的优化,所以此处最主要是对内存资源的利用,这部分的利用分两方面:
-
内存复用:复用池、Bitmap的复用开关
内存资源的开辟消耗是比较大的,对于已经申请的内存资源直接复用起来是比较好的提升手段( 优化释放旧Bitmap内存以及重新申请Bitmap内存导致的性能损耗 );通过复用池,在缓存空间被用完时,对将移除释放的缓存进行复用池复用,在下一次新加入缓存时直接利用这块内存空间
-
内存回收效率提升:复用池、弱引用、阻塞队列
GC在工作时,会扫描到复用池中不被使用的对象,并标记;此时,弱引用会将被标记的对象放入配置的阻塞队列,阻塞队列对该对象进行主动回收(效率高于GC)
3. 功能点解析
关联:缓存+复用池+弱引用+回收队列
//内存缓存 大小一般默认取当前应用内存的1/8
lruCache = new LruCache<String, Bitmap>(SystemUtils.getAppMemoryForByte(context) / 8) {
@Override
protected int sizeOf(String key, Bitmap value) {
return BitmapUtils.getBitmapSize(value);
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue.isMutable()) {
//将移除对象与阻塞队列配置为弱引用对象放入复用池(WeakReference在oldValue标记回收时会被添加到 设置的引用队列中)
reusablePool.add(new WeakReference<Bitmap>(oldValue, getReferenceQueue()));
}else {
oldValue.recycle();
}
}
};
复用池的获取:
/**
* 复用 指使用 重新使用 之前bitmap占用的内存块
* 3.0 之前不能复用
* 3.0-4.4 宽高一样,inSampleSize = 1 自己复用自己
* 4.4 只要小于等于就行了 小的可以使用大的
*
* @param w
* @param h
* @param inSampleSize
* @return
*/
public Bitmap getReusable(int w, int h, int inSampleSize) {
//3.0 以下没有复用功能
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return null;
}
Bitmap reusable = null;
Iterator<WeakReference<Bitmap>> iterator = reusablePool.iterator();
while (iterator.hasNext()) {
Bitmap bitmap = iterator.next().get();
if (bitmap != null) {
if (checkInBitmap(bitmap, w, h, inSampleSize)) {
reusable = bitmap;
iterator.remove();
break;
}
} else {
iterator.remove();
}
}
return reusable;
}
/**
* 校验bitmap 是否满足复用条件
*
* @param bitmap
* @param w
* @param h
* @param inSampleSize
* @return
*/
private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
}
if (inSampleSize > 1) {
w /= inSampleSize;
h /= inSampleSize;
}
int byteCount = w * h * getBytesPerPixel(bitmap.getConfig());
// 图片内存 系统分配内存
return byteCount <= bitmap.getAllocationByteCount();
}
阻塞队列:主动回收
private ReferenceQueue<Bitmap> getReferenceQueue() {
if (referenceQueue == null) {
referenceQueue = new ReferenceQueue<>();
new Thread(new Runnable() {
@Override
public void run() {
while (!shutDown) {
try {
//remove带阻塞功能,
Reference<? extends Bitmap> remove = referenceQueue.remove();
Bitmap bitmap = remove.get();
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
return referenceQueue;
}