1. 程式人生 > >安卓圖片三級快取策略與實現

安卓圖片三級快取策略與實現

前言:

這裡說的三級快取,分別指的是:記憶體快取、檔案快取和網路這三個層面。

一般來說,我們首次載入圖片,記憶體和檔案是沒有快取的,這樣我們需要從網路載入,載入完成後,我們會存到記憶體和檔案中去;當再次載入圖片的時候,我們會先查詢記憶體有沒有,如果有就直接顯示記憶體中的圖片,如果沒有,我們會接著查詢檔案中是否有,如果檔案中有,我們會顯示檔案中的圖片,並且把它存到記憶體中去,這樣下次我們在記憶體中就能找到它了。

我們之所以要做快取,主要是為了提高效率,節省流量。但是為什麼要做三級呢?為什麼不只存在記憶體或者只存在檔案中呢?這是因為記憶體的讀取速度快,但是容易被回收,容量小,檔案的讀取速度次之,不過容量大,不到不得已不會被回收。

有了以上的介紹,我們已經知道了三級快取的必要性和實施步驟,接下來,我們就要選擇在每級快取的快取策略了。

記憶體快取,最開始大家推崇的是用SoftRefrence(軟引用),它只有在記憶體不夠的情況下才會被GC回收。但是高版本的安卓系統更傾向於回收SoftRefrence,這使得SoftRefrence不那麼好用了。不過,安卓在3.0之後提供了LRUCache,它採用了最近最少使用的淘汰策略。本篇文章我們的記憶體快取使用的就是LruCache. 

檔案快取,我們使用的是DiskLruCache

記憶體快取LruCache

我們定義ImageCacheUtil類來進行圖片的快取,它實現了Volley的ImageLoader.ImageCache介面,改介面需要實現兩個方法: 1.getBitmap : Volley請求的時候會先回調getBitmap看快取是否有圖片,沒有的話才會去網路請求 2.putBitmap : Volley下載完圖片的回撥,實現該方法可以進行圖片快取 使用LruCache需要以下步驟 1.通過new LruCache得到LruCache的例項
        // 獲取應用可佔記憶體的1/8作為快取
        int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);
        // 例項化LruCaceh物件
        mLruCache = new LruCache<String, Bitmap>(maxSize) {
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight();
            }
        };
2.在getBitmap函式中通過mLruCache.get(url)得到記憶體的圖片,沒有時返回空。 3.在putBitmap函式中通過mLruCache.put(url,bitmap)把圖片存入記憶體。 具體用法可以看下面我貼出的程式碼。

檔案快取DiskLruCache

使用DiskLruCache需要以下步驟: 1.通過DiskLruCache.open(...)得到DiskLruCache的例項
        //DiskLruCache例項,它的構造方法是私有的,所以我們需要通過它提供的open方法來生成。

        try {
            mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.getContext(),CACHE_FOLDER_NAME),
                    getAppVersion(MyApplication.getContext()) , 1, DISKMAXSIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
2.在getBitmap函式中如果mLruCache.get(url)返回空,通過mDiskLruCache.get(key)得到DiskLruCache.Snapshot,通過BitmapFratory(snapshot.getInputStream(0))得到圖片,沒有時返回空
            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();
            }
3.在putBitmap函式中如果mDiskLruCache.get(key)==null則把圖片存入檔案。
        //存入檔案
        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();
        }

下面是這個ImageCacheUtil類的全部

public class ImageCacheUtil implements ImageLoader.ImageCache {
    //快取類
    private static LruCache<String, Bitmap> 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<String, Bitmap>(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;
    }
}

Volley下載圖片

Volley需要我們宣告一個RequestQueue來維持請求佇列,我們定義RequestQueueManager來進行管理。
public class RequestQueueManager {
    public static RequestQueue mRequestQueue = Volley.newRequestQueue(MyApplication.getContext());
    public static void addRequest(Request<?> request, Object object){
        if (object != null){
            request.setTag(object);
        }
        mRequestQueue.add(request);
    }
    public static void cancelAll(Object tag) {
        mRequestQueue.cancelAll(tag);
    }
}
Volley給我們提供了ImageLoader類和ImageCache類用於圖片下載。我們可以用ImageLoader的get(url,ImageLoader.ImageListeer,width,height)方法來下載圖片 ImageLoader的使用步驟如下: 1.用new ImageLoader方法得到ImageLoader的一個例項。其中構造方法需要傳入requestQueue和ImageCache(我們在介紹記憶體的時候的ImageCaheUtil類實現了ImageCache)
  private static ImageCacheUtil mImagetCache = new ImageCacheUtil();

  public static ImageLoader mImageLoader = new ImageLoader(RequestQueueManager.mRequestQueue,mImagetCache);
2.用get方法進行圖片下載
    public static void loadImage(String url,ImageLoader.ImageListener imageListener){
        mImageLoader.get(url,imageListener,0,0);
    }

    public static void loadImage(String url,ImageLoader.ImageListener imageListener,int maxWidth,int maxHeight){
        mImageLoader.get(url,imageListener,maxWidth,maxHeight);
    }
3.在Activity中呼叫
        ImageCacheManager.loadImage("http://img0.bdstatic.com/img/image/shouye/xiaoxiao/%E5%AE%A0%E7%89%A983.jpg",
                new ImageLoader.ImageListener() {
                    @Override
                    public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) {
                        progressLy.setVisibility(View.GONE);
                        if(imageContainer.getBitmap() != null){
                            imageView.setImageBitmap(imageContainer.getBitmap());
                        }
                    }

                    @Override
                    public void onErrorResponse(VolleyError volleyError) {
                        progressLy.setVisibility(View.GONE);
                    }
                });
好了,以上就是安卓三級快取的實現。