使用三级缓存解决内存溢出
在Android开发的过程中,涉及到图片通常容易产生内存溢出的问题,
使用三级缓存的思路可以比较好的解决这个问题。
如下图所示为三级缓存的示意图,第一级为内存缓存,第二级为软引用缓存,第三级为文件缓存
它们的特点如下图所示。
类图如下图所示,Cache为缓存的接口,定义了缓存必须实现的方法,其他的类均实现了Cache接口。ThreeLevelCache包含有
MemoryCache、SoftReferenceCache和FileCache。
Cache接口
public interface Cache { public void put(String key,Bitmap bitmap);//存入 public Bitmap get(String key);//取出 public boolean isExist(String key);//是否存在 public void clear();//清空 }
内存缓存MemoryCache
/** * 内存缓存,设置大小限制,当超过限制,将最近最久未使用的一项移出 * @author huangbei * */ public class MemoryCache implements Cache { private Map<String, Bitmap> cache ; private Cache softCache; //内存缓存所占的字节,初始为0 private long size ; // 内存缓存当中所占字节的最大限制 private long limit ; public MemoryCache(Cache softCache){ //设置默认限制为最大运行内存的八分之一 setLimit(Runtime.getRuntime().maxMemory() / 8); size=0; this.softCache= softCache; // 放入缓存时是个同步操作 // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率 cache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true)); } public void setLimit(long limit){ this.limit=limit; } @Override public void put(String key, Bitmap bitmap) { this.cache.put(key, bitmap); if(!checkSize(bitmap)){ remove(); } } @Override public Bitmap get(String key) { return cache.get(key); } @Override public boolean isExist(String key) { return cache.containsKey(key); } @Override public void clear() { cache.clear(); } //检查添加的图片是否会超过限制 private boolean checkSize(Bitmap bitmap){ long bitsize=BitmapUtil.getSizeInBytes(bitmap); if((size+bitsize)<=limit){ return true; }else{ return false; } } //移除掉超过内存限制的图片 private void remove(){ Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator(); while (iter.hasNext()) { Entry<String, Bitmap> entry = iter.next(); size -= BitmapUtil.getSizeInBytes(entry.getValue()); softCache.put(entry.getKey(), entry.getValue()); iter.remove(); if (size <= limit) break; } } }
软引用缓存SoftReferenceCache
/** * 软引用缓存,不设置大小限制,内存不够,系统自动释放 * @author huangbei * */ public class SoftReferenceCache implements Cache { //软引用缓存 private Map<String, SoftReference<Bitmap>> cache; public SoftReferenceCache(){ cache = new HashMap<String, SoftReference<Bitmap>>(); } @Override public void put(String key, Bitmap bitmap) { cache.put(key, new SoftReference<Bitmap>(bitmap)); } @Override public Bitmap get(String key) { return cache.get(key).get(); } @Override public boolean isExist(String key) { if(cache.containsKey(key)&&cache.get(key).get()!=null) return true; else return false; } @Override public void clear() { cache.clear(); } }
文件缓存FileCache
/** * 文件缓存,图片存于文件当中,不设置大小限制,避免重复下载,节省流量 * @author huangbei * */ public class FileCache implements Cache{ private String cashe_dir=""; private File cacheDir; //图片存储的路径 public FileCache(Context context) { if (android.os.Environment.getExternalStorageState() .equals(android.os.Environment.MEDIA_MOUNTED)){ cacheDir = new File(android.os.Environment.getExternalStorageDirectory(), cashe_dir); }else{ cacheDir = context.getCacheDir(); } if (!cacheDir.exists()) cacheDir.mkdirs(); } @Override public void put(String key, Bitmap bitmap) { File f = new File(cacheDir, key); if(!f.exists()){ try { f.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } BitmapUtil.saveBitmap(f, bitmap); } @Override public Bitmap get(String key) { File f = new File(cacheDir, key); if (f.exists()){ return BitmapUtil.decodeFile(f); }else{ return null; } } @Override public boolean isExist(String key) { File f = new File(cacheDir, key); if (f.exists()){ return true; }else{ return false; } } @Override public void clear() { File[] files = cacheDir.listFiles(); for (File f : files) f.delete(); } }
三级缓存 ThreeLevelCache
public class ThreeLevelCache implements Cache{ private Cache memoryCache; private Cache softCache; private Cache fileCache; private ThreeLevelCache cache; private ThreeLevelCache(Context context){ this.softCache=new SoftReferenceCache(); this.fileCache=new FileCache(context); this.memoryCache=new MemoryCache(softCache); } public ThreeLevelCache newInstance(Context context){ if(cache==null){ cache=new ThreeLevelCache(context); } return cache; } @Override public void put(String key, Bitmap bitmap) { this.fileCache.put(key, bitmap); this.memoryCache.put(key, bitmap); } @Override public Bitmap get(String key) { if(memoryCache.isExist(key)){ return memoryCache.get(key); }else if(softCache.isExist(key)){ return softCache.get(key); }else if(fileCache.isExist(key)){ return fileCache.get(key); }else{ return null; } } @Override public boolean isExist(String key) { return memoryCache.isExist(key)||softCache.isExist(key)||fileCache.isExist(key); } @Override public void clear() { memoryCache.clear(); softCache.clear(); fileCache.clear(); } }
其中所用到的一些关于图片的方法存于类BitmapUtil中
public class BitmapUtil { //图片所占的内存大小 public static long getSizeInBytes(Bitmap bitmap) { if (bitmap == null){ return 0; } return bitmap.getRowBytes() * bitmap.getHeight(); } //将图片存入文件当中 public static boolean saveBitmap(File file, Bitmap bitmap){ if(file == null || bitmap == null) return false; try { BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); return bitmap.compress(CompressFormat.JPEG, 100, out); } catch (FileNotFoundException e) { e.printStackTrace(); return false; } } // decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的 public static Bitmap decodeFile(File f) { try { // decode image size BitmapFactory.Options option = new BitmapFactory.Options(); option.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(f), null, option); // Find the correct scale value. It should be the power of 2. final int REQUIRED_SIZE = 100; int width_tmp = option.outWidth, height_tmp = option.outHeight; int scale = 1; while (true) { if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE){ break; } width_tmp /= 2; height_tmp /= 2; scale *= 2; } // decode with inSampleSize BitmapFactory.Options option2 = new BitmapFactory.Options(); option2.inSampleSize = scale; return BitmapFactory.decodeStream(new FileInputStream(f), null, option2); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } }