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");

運行結果:
運行結果

本文源碼地址

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