最近写了一个图片的三级缓存,当然现在有很多的开源库都有这个功能,比如xUtils3等,那么我们为什么要自己去写呢,第一个是进一步熟悉它的原理,第二个是使用LruCache最近最少算法,LruCache的好处是可以指定你在手机缓存空间使用多大,在缓存的文件大小超出了你指定的大小,系统会自动回收最少使用的对象。LruCache声明的是一个强引用,它是不会被系统回收的。
图片的三级缓存,顾名思义有三级
按照我们使用的优先级分为:
第一级 手机缓存,优先使用,手机缓存加载速度最快
第二级 手机本地存储,当没有缓存的时候,使用本地存储,本地存储的调用也快于联网,如果二级有,说明联网加载过了,而一级还没有缓存,在调用二级时,需要缓存到一级, 在下次再次调用时,就直接调用一级了
第三级 联网,最后的选择是联网加载,当然有一种特殊情况,如果检测手机没有SD卡,就得跳过二级,直接使用三级了,如果调用到三级了,说明一级和二级都没有,那么在三级联网加载过后,需要存储到二级并缓存到一级。在下次调用时就直接一级了。如果在没有网络时,当用户打开之前打开过的界面时,就可以从二级中加载,不必使用网络也可浏览之前加载过的
代码如下,我写的是分为了四个类,一个主类,三个缓存工具类
一、调用方法的主类
/**
* Created by Star-梦回 on 2016/3/30.
* 获取一个bitmap对象。设置显示图片
*/
public class BitmapCacheUtils {
public final Context context;
private final Handler handler;
/**
* 手机缓存工具类
*/
public MemoryCacheUtils memoryCacheUtils;
/**
* 网络获取资源工具类
*/
public NetCacheUtils netCacheUtils;
public LocalCacheUtils localCacheUtils;
public BitmapCacheUtils(Context context, Handler handler) {
this.context = context;
this.handler = handler;
memoryCacheUtils = new MemoryCacheUtils(context);
localCacheUtils = new LocalCacheUtils(memoryCacheUtils);
netCacheUtils = new NetCacheUtils(handler, memoryCacheUtils,localCacheUtils);
}
/**
* 根据图片的网络地址获取为内存的bitmap对象
*
* @param imageUrl
* @return
*/
public Bitmap getBitmap(String imageUrl, int position) {
//1、内存中获取,最快
Bitmap bitmap = memoryCacheUtils.getBitmap(imageUrl);
if (bitmap != null) {
LogUtil.e("手机缓存文件获取成功");
return bitmap;
}
//2、本地存储中,次之
bitmap = localCacheUtils.getBitmap(imageUrl);
if (bitmap != null) {
LogUtil.e("手机本地存储文件获取成功");
return bitmap;
}
//3、联网获取,最后
netCacheUtils.getBitmapFromNet(imageUrl, position);
return null;
}
}
二、一级缓存工具类:这个类需要特殊说明
我们在缓存中使用的是LruCache最近最少算法缓存的,具体的可以看方法注释,获取内存有三种,我选择的是当前可用内存,取的是1/8,当前你如果感觉不够用你可以自己更改,除的数变小一点,表示缓存可使用的空间更大一点。
/**
* Created by Star-梦回 on 2016/3/30.
* 手机缓存工具类
*/
public class MemoryCacheUtils {
/**
* 手机缓存的容器,最近最少算法集合,底部是LinkedHashMap形式存储
*/
private LruCache<String, Bitmap> lruCache;
private Context context;
public MemoryCacheUtils(Context context) {
this.context = context;
//获取ActivityManager对象
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//获取手机当前可用内存的大小,单位是Mb
int memoryClass = manager.getMemoryClass();
//换算成byte,获取可用空间的1/8作为图片的缓存,在空间不足时,系统会自动回收
int size = memoryClass * 1024 * 1024 / 8;
lruCache = new LruCache<String, Bitmap>(size) {
//之所以说需要换算成byte,是因为,该方法的返回值是byte,两者需要统一
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
/**
* 保存bitmap对象到手机缓存中
*
* @param imageUrl
* @param bitmap
*/
public void putBitmap(String imageUrl, Bitmap bitmap) {
LogUtil.e("保存到手机缓存成功");
lruCache.put(imageUrl, bitmap);
}
/**
* 获取bitmap对象
*
* @param imageUrl
* @return
*/
public Bitmap getBitmap(String imageUrl) {
return lruCache.get(imageUrl);
}
}
三、二级缓存工具类
/**
* Created by Star-梦回 on 2016/3/30.
* 本地存储工具类
*/
public class LocalCacheUtils {
/**
* 手机缓存工具类
*/
private MemoryCacheUtils memoryCacheUtils;
public LocalCacheUtils(MemoryCacheUtils memoryCacheUtils) {
this.memoryCacheUtils = memoryCacheUtils;
}
/**
* 获取bitmap文件
*
* @param imageUrl
* @return
*/
public Bitmap getBitmap(String imageUrl) {
String fileName = MD5.md5(imageUrl);
File file = new File(Environment.getExternalStorageDirectory() + "/xinxinnews", fileName);
try {
if (file.exists()) {
FileInputStream fis = new FileInputStream(file);
Bitmap bitmap = BitmapFactory.decodeStream(fis);
fis.close();
//添加到手机缓存中
memoryCacheUtils.putBitmap(imageUrl, bitmap);
return bitmap;
}
} catch (Exception e) {
LogUtil.e("本地存储文件获取失败");
e.printStackTrace();
}
return null;
}
/**
* 保存一个bitmap对象到手机本地存储中
*
* @param imageUrl
* @param bitmap
*/
public void putBitmap(String imageUrl, Bitmap bitmap) {
//对图片路径进行md5加密,作为文件名,因为这样,长度一样切唯一
String fileName = MD5.md5(imageUrl);
File file = new File(Environment.getExternalStorageDirectory() + "/xinxinnews", fileName);
try {
File parentFile = file.getParentFile();//获取图片路径
if (!parentFile.exists()) {//如果该文件路径不存在
parentFile.mkdirs();//创建
}
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
LogUtil.e("本地存储文件成功");
fos.flush();//刷新
fos.close();//关闭
} catch (Exception e) {
e.printStackTrace();
}
}
}
四、三级缓存工具类
/**
* Created by Star-梦回 on 2016/3/30.
* 联网下载图片工具类 分线程使用线程池
*/
public class NetCacheUtils {
/**
* 成功
*/
public static final int SUCCESS = 1;
/**
* 失败
*/
public static final int FAIL = 2;
private Handler handler;
/**
* 手机缓存工具类
*/
private MemoryCacheUtils memoryCacheUtils;
/**
* 手机本地存储
*/
private LocalCacheUtils localCacheUtils;
/**
* 线程池接口
*/
private ExecutorService service;
public NetCacheUtils(Handler handler, MemoryCacheUtils memoryCacheUtils, LocalCacheUtils localCacheUtils) {
this.handler = handler;
this.memoryCacheUtils = memoryCacheUtils;
this.localCacheUtils = localCacheUtils;
service = Executors.newFixedThreadPool(10);
}
/**
* 线程池的使用
*/
class MyRunnable implements Runnable {
private String imageUrl;
private int position;
public MyRunnable(String imageUrl, int position) {
this.position = position;
this.imageUrl = imageUrl;
}
@Override
public void run() {
Bitmap bitmap = null;
InputStream inputStream = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");//默认就是get,参数必须大写
conn.setReadTimeout(10000);
conn.setConnectTimeout(10000);
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
inputStream = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
Message msg = new Message();
msg.what = SUCCESS;
msg.obj = bitmap;
msg.arg1 = position;//显示图片的位置
handler.sendMessage(msg);
LogUtil.e("网络获取图片资源成功" + position);
//资源获取成功后,加入到一级和二级缓存中
//1、缓存到手机缓存中
memoryCacheUtils.putBitmap(imageUrl, bitmap);
//2、缓存到手机本地存储中
localCacheUtils.putBitmap(imageUrl, bitmap);
}
//关闭
conn.disconnect();
} catch (Exception e) {
//如果失败了
handler.sendEmptyMessage(FAIL);
LogUtil.e("网络获取图片资源失败");
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 根据 图片的网络地址,获取bitmap对象
*
* @param imageUrl
* @param position
* @return
*/
public void getBitmapFromNet(final String imageUrl, final int position) {
service.execute(new MyRunnable(imageUrl, position));
}
}
那么在第三级联网时,使用到了一个Hanlder, 是因为这是在分线程联网加载的,一级和二级没有执行分线程,所以可以直接返回Bitmap对象,但是三级是分线程不能直接返回对象,这时候我们就需要用到Hanlder,那么又有一个问题,那联网后怎么知道这个Bitmap对象是要装配到ListView中的哪个Item上呢,这是时候我们就需要用到Item的position了,在联网成功后,我们都做什么了,看代码
Message msg = Message.obtain();
msg.what = SUCCESS;
msg.obj = bitmap;
msg.arg1 = position;//显示图片的位置
handler.sendMessage(msg);
把这个position再传回去,在主线程处理消息时,我们就可以根据这个位置去设置图片的显示位置了
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case NetCacheUtils.SUCCESS://成功
Bitmap bitmap = (Bitmap) msg.obj;
int position = msg.arg1;
ImageView image = (ImageView) listView.findViewWithTag(position);//注意这行代码
image.setImageBitmap(bitmap);
break;
case NetCacheUtils.FAIL://失败
break;
}
}
};
如果要执行这一行代码
listView.findViewWithTag(position)
还必须要在adapter中的getView()中给每一个item的图片设置Tag
myViewHolder.iv_item.setTag(position);
这样就搞定了,最后就是在设置图片的时候调用主类的getBitmap()方法了,该方法有两个参数,一个是图片的url,一个是position
另外再说明下,一级和二级是在调用方法时,直接返回的Bitmap对象,三级并没有返回Bitmap,而是通过Hanlder发送回来的,三级的图片设置是在hanlder处理消失中进行装配的。
完了!谢谢大家!