LruCache是内存缓存,DiskLruCache对应的磁盘缓存。在学习Bitmap缓存优化的时候学习了这两种缓存方式,我觉得这种策略可以应用于Android的开发中(现在应该都是这样应用的吧),它不仅仅是Bitmap,它可以是商品,也可以是一组数据。现在就以Bitmap为案例,把我知道的关于Bitmap缓存的知识都记录下来。
LruCache的使用
LruCache在androidx.collection包下有这样一个类。先说说它的基本使用,首先创建LruCache的实例并分配给它内存的大小,然后重写sizeof方法返回每个Bitmap所占的内存。
// 应用可分配的最大内存数(这里转换为k字节)
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 分配内存1/8给Bitmap缓存
int cacheSize = maxMemory / 8;
mBitmapLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(@NonNull String key, @NonNull Bitmap bitmap) {
// 每个Bitmap所占的内存数
return bitmap.getByteCount() / 1024;
}
};
然后当我们去加载Bitmap的时候,首先去LruCache中查找,如果有就直接从内存中取出,如果没有就从网络下载下来进行加载,并把Bitmap存进LruCache中。LruCache大概的策略就是这样。LruCache里面维护了一个LinkedHashMap,它是一个链表,我们可以把它理解成队列。这个LinkedHashMap初始化的时候使用访问顺序作为队列排列的规则,也就是说这个队列是根据访问的顺序从队头到队尾一次排列的。当我们的图片足够多,队列不足以存储我们的图片的时候,队列就会淘汰掉最近最少访问的Bitmap,也就是队尾的Bitmap。
LruCache的源码解析
ok,上面简单的说明LruCache策略和简单使用,下面我们看看它代码是怎么实现的。
在看源码之前,这里有必要补充下,首先我们看到LruCache类注释,它说android.util.LruCache包下有个LruCache类,它曾经运行在api12之前的。这个包下LruCache我看了下与androidx.collection包下LruCache代码几乎都相同,不同的是,当队列缓存不足的时候它淘汰的是队列头部的元素,经我查阅其他博客,发现在Java某个版本之前LinkedHashMap插入元素采用的尾插法,而现在的是头插法,就是说它的排列顺序和现在的是相反的,自然淘汰的位置也是相反的。
好的,我们继续回到LruCache源码解析。
我们现在LruCache的构造方法,它使用maxSize保存内存缓存的大小, 然后初始化LinkedHashMap,注意到LinkedHashMap的第三个参数,如果设置为false,则队列的排列顺序是按照插入顺序排序的,true则访问顺序排序。
接着,看看它的get方法
@Nullable
public final V get(@NonNull K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
我们传入一个key去取对应的Bitmap,如果存在,则将对应的Bitmap返回并将它移动到队首;如果不存在,则去create一个Bitmap,这个方法默认返回null,如果我们需要去创建一个Bitmap,可以去重写这个方法。在同步块中,将这个新建的Bitmap放入缓存队列中,这里有两种情况的处理:第一中情况,这个key对应的值之前的存在的,它最重新设置进队列中,这个应该是个处理冲突的一个措施。然后调用entryRemoved方法,这个方法也是默认是个空方法。第二种情况,这个key对应的值之前是null,那么它就会调用safeSizeOf方法返回的值加到队列总的缓存中。safeSizeOf方法又调用了sizeOf方法,sizeOf方法默认返回1。
我们可以这样理解这个sizeOf方法,当我们没有重写sizeOf方法去返回Bitmap占用内存大小,那么我们设置的内存大小maxSize默认是队列可以缓存最大Bitmap的个数;如果像上面那样设置,那么maxSize是队列的可以缓存的最大内存。
接着看trimToSize方法
这方法主要是去判断队列中缓存的Bitmap是超过了我们设定的缓存的大小,如果超过了我们设定的缓存的大小,则需要移除队列中最近最少访问的条目。size保存的是队列中所有元素的总的内存,maxSize是我们分配的内存。
我们再看看put方法
这个方法里面的调用的方法上面都介绍过了,主要的逻辑在同步块里,入队设置的元素,并添加元素所占的内存,如果上一个key对应的值不为空,size减去这个值的size。
DiskLruCache的使用
DiskLruCache磁盘缓存,相对于内存缓存来说它是更慢的,在实际应用中,DiskLruCache通常作为二级缓存。具体的流程应该是这个样子。我们在加载图片的时候,首先去内存缓存(LruCache)中找,没有找到就去磁盘缓存(DiskLruCache)中找,没有找到最后去网络去下载然后解码显示图片,并且将图片保存到磁盘缓存和内存缓存中。
private void loadImage(ImageView imageView, Beauty item) {
// 从LruCache中寻找
Bitmap bitmapFromCache = ImageLoaderUtils.share().getBitmapFromCache(item.getBeautyImg());
if (bitmapFromCache != null) {
// 显示图片
imageView.setImageBitmap(bitmapFromCache);
} else {
imageView.setImageResource(R.mipmap.ic_launcher);
// 内存缓存中没有,开启一个子线程寻找
TaskScheduler.execute(new Task<Bitmap>() {
@Override
public Bitmap doInBackground() {
synchronized (mDiskLock) {
// 从DiskLruCache寻找
Bitmap bitmapFromDiskCache = DiskLruCacheUtils.share().getBitmapFromDiskCache(item.getBeautyImg());
if (bitmapFromDiskCache == null) {
Log.d(TAG, "bitmapFromDiskCache == null ");
// 从网络中下载
bitmapFromDiskCache = decodeURL(item.getBeautyImg());
}
return bitmapFromDiskCache;
}
}
@Override
public void onSuccess(Bitmap bitmap) {
if (bitmap != null) {
// 获取图片成功并显示图片
ImageLoaderUtils.share().addBitmapToCache(bitmap, item.getBeautyImg());
imageView.setImageBitmap(bitmap);
}
}
});
}
}