解码会产生很多图片,滑过后不要显示,如果直接回收,会浪费不少资源.
在没有缓存的情况下,会看到gc还是比较频繁的.
有了缓存后,明显gc少了.
目录
常用的缓存
自定义缓存
显示相关的内存缓存
解码缓存池
内存缓存实现:
解码缓存池实现:
lrucache,这是最常用的,也是android sdk里面有的.就是按访问时间顺序,内部使用linkedhashmap,会记录访问时间.
androidx.core.util.Pools
这是一个接口,实现后可以自己管理池.严格来说,它并不是一个缓存类,是用来构建缓存的基础类.
用lrucache作缓存,有一些不好的点,设置了容量并不是图片的字节总数,是图片的张数.这对于图片大小不一的情况,可能并不是很好.
需要string作缓存key,能读取,且能根据容量把最早未访问的清除出缓存队列.
当需要解码时,从池里面找到bitmap,对于高版本的系统,解码可以使用比当前高宽大的图片来处理.那么当图片从上面的内存缓存中清除,不是调用recycle(),而是将它放入解码缓存池.这对于解码来说,会明显减少gc.
既然lru这么好用,就参与它,把源码拿来修改下.有了自动清除最早访问的位图功能,又有根据总字节数去管理缓存.
private static final int mMaxPoolSizeInBytes = 100 * 1024 * 1024;
private int mPoolSizeInBytes = 0;
定义了最大缓存100m, 这个值可以根据系统内存大小去调整,这里先固定.
private final LinkedHashMap map = new LinkedHashMap<>(16, 0.75f, true);
默认先设16
获取图片,不需要去创建,如果为空就返回空.这与lru不一样的.
public final Bitmap getBitmap(@NonNull String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Bitmap mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
return null;
}
//添加图片,
public final Bitmap addBitmap(@NonNull String key, @NonNull Bitmap value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
//添加前先判断是否总容量已经达到限定的值,如果是就清除,直到总容量符合要求,然后再添加.如果总容量设置很小,那么这里的效率会比较低.
while (mPoolSizeInBytes > mMaxPoolSizeInBytes) {
removeLast();
}
mPoolSizeInBytes += value.getByteCount();
Bitmap previous;
synchronized (this) {
putCount++;
previous = map.put(key, value);
if (previous != null) { //对于移除的要把容量减去它的大小.
mPoolSizeInBytes -= previous.getByteCount();
}
}
//System.out.println(String.format("put.size:%s, key:%s, val:%s, size:%s", map.size(), key, value, mPoolSizeInBytes));
if (previous != null) {
entryRemoved(false, key, previous, value);
}
return previous;
}
//与lrucache差不多,记得容量的计算
private void removeLast() {
String key;
Bitmap value;
synchronized (this) {
if (map.isEmpty()) {
return;
}
Map.Entry toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
mPoolSizeInBytes -= value.getByteCount();
evictionCount++;
}
//System.out.println(String.format("removeLast.size:%s, key:%s,val:%s, size:%s", map.size(), key, value, mPoolSizeInBytes));
entryRemoved(true, key, value, null);
}
public final Bitmap remove(@NonNull String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Bitmap previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
mPoolSizeInBytes -= previous.getByteCount();
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
当关闭页面时,建议清除所有的数据
public final void clear() {
int size = map.size();
for (int i = 0; i < size; i++) {
removeLast();
}
}
这是内存缓存类,可以设置为单例.
BitmapPool,也设置单例.
public static class FixedSimplePool implements Pools.Pool {
private final Object[] mPool;
private int mPoolSize;
/**
* Creates a new instance.
*
* @param maxPoolSize The max pool size.
* @throws IllegalArgumentException If the max pool size is less than zero.
*/
public FixedSimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException("The max pool size must be > 0");
}
mPool = new Object[maxPoolSize];
}
@Override
@SuppressWarnings("unchecked")
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex] = null;
mPoolSize--;
return instance;
}
return null;
}
@Override
public boolean release(@NonNull T instance) {
if (isInPool(instance)) {
return false;
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize] = instance;
mPoolSize++;
return true;
}
return false;
}
private boolean isInPool(@NonNull T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) {
return true;
}
}
return false;
}
}
这段代码好像是sdk里面有的.忘了是不是了.就是实现了 pools接口,然后用数组去存储数据
整个pool向外提供两个方法,aquried,release.
public Bitmap acquire(int width, int height) {
Bitmap b = simplePool.acquire();
if (null == b) {
b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
} else {
if (b.getHeight() == height && b.getWidth() == width) {
//Log.d("TAG", String.format("use cache:%s-%s-%s%n", width, height, simplePool.mPoolSize));
b.eraseColor(0);
} else {
b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
}
return b;
}
public void release(Bitmap bitmap) {
boolean isRelease = simplePool.release(bitmap);
if (!isRelease) {
System.out.println("recycle bitmap:" + bitmap);
bitmap.recycle();
}
}
解码时的bitmap从这个单例里面取.而不是新建
val bm = BitmapPool.getInstance().acquire(width, height)
// val page = mupdfDocument?.loadPage(pageSize.index) ?: return null
至于什么时候release对象,这需要在确定不用bitmap时,把它加入到BitmapPool中.