在一个app中,图片资源是处处存在的,加载图片的流程一般是:
1 先从缓存中读取
2 若缓存中不存在,从SD卡中读取
3 若SD卡中也不存在,则从服务器拉取
后两个步骤纯碎属于业务逻辑,暂且不表,这里来看一下手Q使用的图片缓存管理策略。
说到缓存管理,首先谈一下java中的强引用和弱引用
- 强引用:最普遍的引用,若一个对象具有强引用,那么GC绝不会回收它。如A a = new A()
- 弱引用: 弱引用又分为以下三类:
- 软引用(SoftReference): 这类引用只有当内存空间不足GC才会回收它
- 弱引用(WeakReference): 这类引用拥有更短的生命周期,GC扫描过程中一旦发现了此类引用,不管当前内存是否足够,立即回收
- 虚引用(PhantomRefence): 这类引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,则任何时刻都可能被回收
下面来看看这样一个图片缓存类,为了更大限度使用缓存,它使用了强引用缓存(强引用)和弱引用缓存(弱引用)双重缓存,强引用缓存不会轻易被回收,用来保存常用数据,不常用的转入弱引用缓存。**
ImageCache.java
public class ImageCache {
private static final String TAG = "ImageCache";
//CustomLruCache是一个继承了LruCache的继承类,它代表强引用缓存,它的缓存大小一般由业务方提供
private CustomLruCache<String, Drawable> mMemoryCache;// Default memory cache size
//这里设置的是弱引用缓存以及它所占据的空间大小
private static final int DEFAULT_MEM_CACHE_SIZE = 5; // 5MB
private final HashMap<String, WeakReference<Drawable>> mRefCache = new HashMap<String, WeakReference<Drawable>>();
public ImageCache(int memSize) {
memSize = Math.max(memSize, DEFAULT_MEM_CACHE_SIZE);
QLog.d(TAG, "Memory cache size = " + memSize + "MB");
mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {
//这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数
@Override
protected int sizeOf(String key, Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
//若是bitmap位图则直接计算它的大小
return bitmap.getRowBytes() * bitmap.getHeight();
}
return 0;
} else if (drawable instanceof AnimationDrawable) {
//若是逐帧动画,则首先获取它所有的帧数,再计算总共的大小
AnimationDrawable anim = (AnimationDrawable) drawable;
int count = anim.getNumberOfFrames();
int memSize = 0;
for (int i = 0; i < count; i++) {
Drawable dr = anim.getFrame(i);
if (dr instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) dr).getBitmap();
if (bitmap != null) {
memSize += bitmap.getRowBytes() * bitmap.getHeight();
}
}
}
return memSize;
}
return 0;
}
};
}
//从缓存中获取图片
public Drawable getImageFromMemCache(String key) {
Drawable memDrawable = null;
if (mMemoryCache != null) {
//首先从强引用缓存中获取图片,若找到的话,把元素移动到CustomLruCache的最后面,从而保证它在LRU算法中最后被删除?
//疑问,其实LinkedHashMap本身就存在LRU的算法机制,因此,get的时候,会自动移入到队列尾部
memDrawable = mMemoryCache.remove(key);
if (memDrawable != null) {
memDrawable = memDrawable.getConstantState().newDrawable();
mMemoryCache.put(key, memDrawable);
return memDrawable;
}
}
//强引用缓存中没有找到,开始在弱引用缓存中查找
WeakReference<Drawable> ref = mRefCache.get(key);
if (ref != null) {
//若找到的话,这里是否添加一步,将其从弱引用缓存移入强引用缓存中比较好
memDrawable = ref.get();
if (memDrawable == null) {
mRefCache.remove(key);
}
}
return memDrawable;
}
//添加图片到缓存,这里不理解为什么要向强引用缓存和弱引用缓存都要添加一份
public void addImageToCache(String data, Drawable drawable) {
// Add to memory cache
if (mMemoryCache != null && mMemoryCache.get(data) == null) {
mMemoryCache.put(data, drawable);
mRefCache.put(data, new WeakReference<Drawable>(drawable));
}
}
//从缓存中删除资源
public void removeImageFromCache(String data) {
if (mRefCache != null) {
mRefCache.remove(data);
}
if (mMemoryCache != null) {
mMemoryCache.remove(data);
}
}
public Drawable getImageFromDiskCache(String pathName) {
// TODO 暂不支持disk cache
return null;
}
public void clearCaches() {
// mDiskCache.clearCache();
mMemoryCache.evictAll();
mRefCache.clear();
}
}
整个缓存策略是使用弱引用缓存和强引用缓存配合使用,并结合LRUCache,在尽可能地利用缓存的基础上,也大大提高了缓存命中率。我个人觉得这个类有改进的地方,比如,当LRUCache在移除元素的时候,默认是直接删除掉。这里更好的方式是重写LRUCache的entryRemoved方法,使得强引用缓存满的时候,会根据LRU算法将最近最久没有被使用的图片自动移入弱引用缓存,如下:
mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {
//这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数
@Override
protected int sizeOf(String key, Drawable drawable) {
.........
}
当强引用缓存满时,会自动调用这个方法,这时候,将移除的元素添加到弱引用缓存中
@Override
protected void entryRemoved(boolean evicted, String key, Drawable oldDrawable, Drawable newDrawable) {
if (oldDawable != null) {
mRefCache.put(data, new WeakReference<Drawable>(oldDawable));
}
}
};