三级缓存的概念:即,网络,本地,内存,在安卓中,加载网络资源(特别是图片)是一件很消耗资源的资源的,因此我们使用三级缓存的形式,可以大大减少APP资源的消耗,增加开发效率,下面是三级缓存的流程图
由流程图可以知道,三级缓存最先访问的是APP的内存,其次是本地缓存,最后才是直接从网络获取图片。下面我们开始将这三个部分一起来实现下
在开始之前呢我们先来设计下项目的架构和思路,项目的架构对以后的维护是相当重要的,我们定义几个类,每个类分别实现以下功能
ImagLoader类:该类负责管理ImageView 显示图像的逻辑处理(只要负责图像的显示)
ImageCache 接口:该接口用于定义图像缓存的方法
MemoryCache 类:该类实现了ImageCache 接口接口,用来将图像缓存到APP内存和将图像从APP内存中取出来
DiskCache 类:该类实现了ImageCache 接口接口,用来将图像缓存到本地缓存和将图像从本地缓存中取出来
DoubleCache 类:该类用来处理MemoryCache和DiskCache之间的逻辑关系,也就是先从APP内存获取,再从本地缓存获取的逻辑关系
1 首先我们先来定义ImageCache接口
public interface ImageCache {
public Bitmap get(String url);
public void set(String url,Bitmap bitmap);
}
该类中定义了两个方法,get方法是从缓存里面(包括本地缓存和内存缓存)获取bitmap,set方法是把bitmap设置到缓存里面
2 其次,我们来实现MemoryCache类
public class MemoryCache implements ImageCache {
private LruCache<String ,Bitmap>mMemoryCache;
public MemoryCache(){
final int max=(int) (Runtime.getRuntime().maxMemory()/1024);
mMemoryCache=new LruCache<String,Bitmap>(max/4){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
}
@Override
public Bitmap get(String url) {
Bitmap bitmap=mMemoryCache.get(url);
if (bitmap==null){
Log.e("TAG","读取内存缓存失败");
}else {
Log.e("TAG","读取内存缓存成功");
}
return bitmap;
}
@Override
public void set(String url, Bitmap bitmap) {
mMemoryCache.put(url,bitmap);
}
}
在该类中,我们使用了安卓提供的LruCache来实现内存缓存,通过Runtime.getRuntime().maxMemory()获取APP当前可用内存的大小来定义使用内存缓存的阀值,避免出现OOM这种导致APP崩溃的尴尬事件,值得注意的是,使用LruCache的时候一定要注意key的值是否正确,否则会出现返回值为空的现象。
关于LruCache的更多资料可用查看官网API介绍,本文不再做过多介绍
LruCache官方API地址
3 接下来我们来实现DiskCache类
public class DiskCache implements ImageCache {
// private String filepath="sdcard/cache/";
private String filepath= Environment.getExternalStorageDirectory().toString()+"/Imageloder/";
@Override
public Bitmap get(String url) {
Bitmap bitmap=BitmapFactory.decodeFile(filepath+url);
if (bitmap==null){
Log.e("TAG","读取本地缓存失败");
return null;
}else{
Log.e("TAG","读取本地缓存成功");
return bitmap;
}
}
@Override
public void set(String url, Bitmap bitmap) {
File appDir = new File(Environment.getExternalStorageDirectory(), "Imageloder");
if (!appDir.exists()) {
appDir.mkdir();
Log.e("TAG","创建本地文件夹");
}
String fileName = url;
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
该类需要使用到读取文件的权限,请在XML中注册,在Android6.0以上的设备需要使用动态权限设置,本文直接指定使用5.1的SDK
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
在
4 在实现完本地缓存之后,我们开始来实现处理本地和内存的逻辑关系类
DoubleCache ,该类也是实现ImageCache接口,但是在get方法和put的方法中做了不同的处理:
public class DoubleCache implements ImageCache {
private ImageCache mMemoryCache;
private ImageCache mDiskCache;
public DoubleCache (){
mMemoryCache= new MemoryCache();
mDiskCache=new DiskCache();
}
@Override
public Bitmap get(String url) {
Bitmap bitmap=mMemoryCache.get(url);
if (bitmap==null){
bitmap=mDiskCache.get(url);
}
return bitmap;
}
@Override
public void set(String url, Bitmap bitmap) {
mMemoryCache.set(url,bitmap);
mDiskCache.set(url,bitmap);
}
}
在该类的get方法中,首先我们先通过mMemoryCache.get(url)来获取内存中的bitmap对象,当内存中没有bitmap返回时,我们将从本地缓存中
(mDiskCache.get(url))获取bitmap对象,最后再返回bitmap对象
5 最后我们的在来实现ImagLoader
public class ImageLoader {
ImageCache imageCache=new MemoryCache();
ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void display(String url, ImageView imageView,String key){
Bitmap bitmap=imageCache.get(key);
if (bitmap!=null){
imageView.setImageBitmap(bitmap);
return;
}
submitLoadRequest(url,imageView,key);
}
public void setImageCache(ImageCache imageCache) {
this.imageCache = imageCache;
}
private void submitLoadRequest(final String url, final ImageView imageView,final String key){
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap=downloadImag(url);
if (bitmap==null){
Log.e("TAG","图片下载失败");
return;
}else {
Log.e("TAG","图片下载成功");
imageCache.set(key,bitmap);
if (imageView.getTag().equals(url)){
imageView.setImageBitmap(bitmap);
}
}
}
});
}
private Bitmap downloadImag(String url){
Bitmap bitmap=null;
try {
URL url1=new URL(url);
final HttpURLConnection connection=(HttpURLConnection) url1.openConnection();
bitmap= BitmapFactory.decodeStream(connection.getInputStream());
connection.disconnect();
}catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
}
在该类中,我们定义的一个公开的方法display给外部调用,display中有三个参数,url是指当前要显示的图片的网络url地址,Imagview是当前要显示图片的控件,key是指图片缓存到APP内存时的索引或者是图片缓存到本地的文件名称,可能有的人会问,名称和索引直接用url不行?为什么要多定义一个看起来没有用的key?在很多网络图片中,url是相当长的,而且可能会有一些字符是我们Android的文件系统无法正常识别的,这时候我们就不能直接用url做文件名,值得注意的是,在该类中,我们灵活定义了setImageCache方法,该方法的存在使得用户可以很方便的定义自己使用哪种缓存方式(注:在三级缓存里面不一定是三种缓存都要使用的,可以只使用本地,也可以只使用内存,也可以本地和内存混合使用)
6最后,我们在Activity中使用ImageLoader就可以了,本文的例子:
String url="http://img1.gamedog.cn/2014/04/24/119- 1404240UG30.jpg";
ImageLoader imageLoader;
ImageCache imageCache;
imageLoader=new ImageLoader();
imageCache=new DoubleCache();
imageLoader.setImageCache(imageCache);
imageLoader.display(url,imageView,"01.jpg");
运行结果: