在前面的两篇文章《LruCache内存缓存图片技术精炼详解》、《DiskLruCache本地缓存图片技术精炼详解》笔者分别讲解了利用LruCache实现内存缓存和利用DiskLruCache本地缓存两种缓存技术,那么今天本节文章,就和大家一起实现一次三级缓存加载图片。
(1)三级缓存的定义
现在大部分的缓存框架,比如图片加载框架,网络请求框架等都使用三级缓存来提高效率,即内存-文件(SD卡或手机)-网络。对于图片加载来说,就是加载图片的时候首先从内存缓存中取,如果没有再从文件缓存中取,如果文件缓存没有取到,就从网络下载图片并且加入内存和文件缓存。
(2)三级缓存的优势
①网络缓存, 不优先加载, 速度慢,浪费流量
②本地缓存, 次优先加载, 速度快
③内存缓存, 优先加载, 速度最快
我们可以看到,对一张图片使用三种了三种加载方式,每种加载方式优势互补,既弥补了单一缓存的缺点,又融合了三种缓存方式的优势,最终可以很快的实现一次网络图片的加载:第一次从网络加载,保存到内存和本地;下次加载图片,先从内存中找,找到了直接加载,没有找到就再去本地中找,找到了直接加载,两个地方都没有找到,再从网络中下载,层层递进。
(3)三级缓存使用对象
①内存缓存使用LruCache;
②本地缓存使用DiskLruCache;
③网络加载图片使用Volley框架。
(1)Volley-使用ImageLoader加载网络图片
①创建一个RequestQueue对象;
② 创建一个ImageLoader对象;
③ 获取一个ImageListener对象;
④ 调用ImageLoader的get()方法加载网络上的图片
我们在第二步中,复写一个新的ImageCache接口对象,在其中实现三级缓存的逻辑。
实现接口对象时,需要实现该接口中的两个方法,方法中定义好存取逻辑顺序:
有需要的读者,可参考笔者的《网络通讯库Volley精炼详解第(二)课:使用Volley加载网络图片》,里面有使用ImageLoader加载网络图片的详细介绍。
(2)LruCache-内存缓存
// 创建LruCache实例,获取应用可占内存的1/8作为缓存
int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);
// 实例化LruCaceh对象
mLruCache = new LruCache(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
(3)DiskLruCache—本地缓存
1)创建DiskLruCache实例
//创建DiskLruCache实例
try {
mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.getContext(),CACHE_FOLDER_NAME),
getAppVersion(MyApplication.getContext()) , 1, DISKMAXSIZE);
} catch (IOException e) {
e.printStackTrace();
}
2)三级缓存读取图片逻辑代码
try {
if(mDiskLruCache.get(diskKey) != null){ //文件中有
//从文件中取
Log.d(TAG,"从文件中取");
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(diskKey);
Bitmap bitmap = null;
if(snapshot != null){
bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
//存入内存
mLruCache.put(s,bitmap);
}
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
3)三级缓存存储图片逻辑代码
try {
if(mDiskLruCache.get(diskKey) == null){
Log.d(TAG,"存入文件");
DiskLruCache.Editor editor = mDiskLruCache.edit(diskKey);
if(editor != null){
OutputStream outputStream = editor.newOutputStream(0);
if(bitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream)){
editor.commit();
}else{
editor.abort();
}
}
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
下面,是完整的三级缓存代码段—创建了一个
ImageCacheUtil类,用以实现ImageCache接口,获得对象之后,传入Volley的ImageLoade中,作为其第二个参数,从而完美实现三级缓存。
public class ImageCacheUtil implements ImageLoader.ImageCache {
//缓存类
private static LruCache mLruCache;
private static DiskLruCache mDiskLruCache;
//磁盘缓存大小
private static final int DISKMAXSIZE = 10 * 1024 * 1024;
//路径
private static String CACHE_FOLDER_NAME = "YR_ImageCache";
private String TAG = ImageCacheUtil.class.getSimpleName();
public ImageCacheUtil() {
// 获取应用可占内存的1/8作为缓存
int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);
// 实例化LruCaceh对象
mLruCache = new LruCache(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
//DiskLruCache实例,它的构造方法是私有的,所以我们需要通过它提供的open方法来生成。
try {
mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.getContext(),CACHE_FOLDER_NAME),
getAppVersion(MyApplication.getContext()) , 1, DISKMAXSIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* volley请求的时候会先回调getBitmap查看缓存中是否有图片,没有再去请求
* @param s
* @return
*/
@Override
public Bitmap getBitmap(String s) {
if(mLruCache.get(s) != null){ //内存中有
//从内存获取
Log.d(TAG,"从内存获取");
return mLruCache.get(s);
}else {
String diskKey = MD5Utils.md5(s);
try {
if(mDiskLruCache.get(diskKey) != null){ //文件中有
//从文件中取
Log.d(TAG,"从文件中取");
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(diskKey);
Bitmap bitmap = null;
if(snapshot != null){
bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
//存入内存
mLruCache.put(s,bitmap);
}
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
}
Log.d(TAG,"从网络中取");
return null;
}
/**
* 当Volley下载完图片后会来回调putBitmap方法来将图片进行缓存
* @param s
* @param bitmap
*/
@Override
public void putBitmap(String s, Bitmap bitmap) {
//存入内存
Log.d(TAG,"存入内存");
mLruCache.put(s,bitmap);
//存入文件
String diskKey = MD5Utils.md5(s);
try {
if(mDiskLruCache.get(diskKey) == null){
Log.d(TAG,"存入文件");
DiskLruCache.Editor editor = mDiskLruCache.edit(diskKey);
if(editor != null){
OutputStream outputStream = editor.newOutputStream(0);
if(bitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream)){
editor.commit();
}else{
editor.abort();
}
}
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//该方法会判断当前sd卡是否存在,然后选择缓存地址
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
Log.d(TAG,cachePath + File.separator + uniqueName);
return new File(cachePath + File.separator + uniqueName);
}
//获得应用version号码
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
}
总结:本节文章,主要在笔者前面DisLruCache、LruCache、Volley的基础上综合,实现了一个现在比较流行的运用于常见应用程序的三级缓存的技术,这也是笔者第一次将多个知识点进行综合。整段代码,初次看起来或许难以入手,但是实际上,每段代码的逻辑非常清晰,都起到了对应的效果,然后通过一个逻辑控制,将所有代码整合在一起。不了解相关内容的读者或许很难相信,这么一长段的代码,只是实现了一个ImageCache接口,然后获取其对象之后,只是传入ImageLoader中作为第二个参数。但是,我们在这个逻辑中,实现了本地缓存和内存缓存的读写操作逻辑,这是实实在在的知识点。建议初次接触三级缓存的读者,可以参考笔者的前面两篇关于DisLruCache本地缓存和LruCache内存缓存的文章,相信会帮助到你的。