在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;
}
}
}