android Imageloader实现图像的三级缓存和代码结构优化

三级缓存的概念:即,网络,本地,内存,在安卓中,加载网络资源(特别是图片)是一件很消耗资源的资源的,因此我们使用三级缓存的形式,可以大大减少APP资源的消耗,增加开发效率,下面是三级缓存的流程图

Created with Raphaël 2.1.0Activity开始APP内存是否有图片缓存?ImagView显示本地存储yesno
Created with Raphaël 2.1.0本地存储SD卡存储是否有图片缓存?ImagView显示网络获取yesno
Created with Raphaël 2.1.0网络获取HTTP请求是否有网络图片返回?ImagView显示没有网络图片,抛出异常yesno

由流程图可以知道,三级缓存最先访问的是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");

运行结果:
运行结果

本文源码地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章