1. 程式人生 > >Volley(三) 三級快取策略

Volley(三) 三級快取策略

  在之前的文章中已經介紹過使用Volley的基本方法。總結起來就是需要一個RequestQueue,一個相應的Request,然後在這個RequestQueue中將這個Request新增進去。同時也介紹了圖片載入的方法。一共有三個,分別是建立ImageReqest,其次是使用ImageLoader,最後是NetworkImageView方法。我們再來回顧一下ImageRequest方法。在
ImageReqest需要兩個引數,RequestQueue以及ImageCache。而在ImageCache中主要是put 和get 方法。

 protected void MyImageLoaderResult
() { ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache()); ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.defaultiamge, R.drawable.failed); imageLoader.get(url3, listener); }
 public class BitmapCache implements
ImageCache {
private LruCache<String, Bitmap> mCache; public BitmapCache() { int maxSize = 5 * 1024 * 1024; mCache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return
bitmap.getRowBytes() * bitmap.getHeight(); } }; } @Override public Bitmap getBitmap(String url) { // TODO Auto-generated method stub return mCache.get(url); } @Override public void putBitmap(String url, Bitmap arg1) { // TODO Auto-generated method stub mCache.put(url, arg1); } }

為了明白為什麼這樣寫,我們先來看看在ImageLoader 的建構函式中發生了什麼。

    public ImageLoader(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
    }

建構函式中,傳入了一個queue 以及一個imageCache。imageCache即是對圖片的一個快取。
之後又new 了一個listener

        ImageListener listener = ImageLoader.getImageListener(imageView,  
                R.drawable.defaultiamge, R.drawable.failed);

通過原始碼來看看ImageLoader.getImageListener()發生了什麼。

public static ImageListener getImageListener(final ImageView view,
            final int defaultImageResId, final int errorImageResId) {
        return new ImageListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                if (errorImageResId != 0) {
                    view.setImageResource(errorImageResId);
                }
            }

            @Override
            public void onResponse(ImageContainer response, boolean isImmediate) {
                if (response.getBitmap() != null) {
                    view.setImageBitmap(response.getBitmap());
                } else if (defaultImageResId != 0) {
                    view.setImageResource(defaultImageResId);
                }
            }
        };
    }

這裡是不是和之前用StringRequest之類的很像?一個對錯誤的處理以及一個onRespones。
接下來是最後一步。
imageLoader.get(url3, listener); 這個get方法的內容如下:

 public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight, ScaleType scaleType) {

        // only fulfill requests that were initiated from the main thread.
        throwIfNotOnMainThread();

        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

        // Try to look up the request in the cache of remote images.
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // Return the cached bitmap.
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }

        // The bitmap did not exist in the cache, fetch it!
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // Update the caller to let them know that they should use the default bitmap.
        imageListener.onResponse(imageContainer, true);

        // Check to see if a request is already in-flight.
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // If it is, add this request to the list of listeners.
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
                cacheKey);

        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }

因為在ImageLoader 中由多個對get的過載,而最後都是呼叫了同一個,因此給出了最終呼叫的get原始碼。
首先是從mCache中去讀取快取。如果快取則直接返回結果。

// Try to look up the request in the cache of remote images.
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // Return the cached bitmap.
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }

如果沒有就需要先建立一個空的ImageContainer,判斷該url的請求是否已經在網路請求佇列中,如果在則等待它的執行,沒有的話就需要自己進行一次網路請求。

 // The bitmap did not exist in the cache, fetch it!
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);//新建imageContainer

        // Update the caller to let them know that they should use the default bitmap.
        imageListener.onResponse(imageContainer, true);

        // Check to see if a request is already in-flight.
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        //判斷是否在請求佇列中
        if (request != null) {
            // If it is, add this request to the list of listeners.
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // The request is not already in flight. Send the new request to the network and
        // track it. //不在則發起一次新的網路請求,可以看出請求的方式還是ImageRequest的方式進行。
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
                cacheKey);

        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;

在上面的流程中,出現了一個重要的介面,ImageCache 。

/** The cache implementation to be used as an L1 cache before calling into volley. */
    private final ImageCache mCache;
    ......
    public interface ImageCache {
        public Bitmap getBitmap(String url);
        public void putBitmap(String url, Bitmap bitmap);
    }

此處的ImageCache充當了一級快取的作用。為了實現二級和三級的快取,我們可以通過一個繼承於ImageCache的類來實現。
  一般來說二級快取採用記憶體進行快取,三級快取採用磁碟進行快取。下面,先通過這個介面實現一個二級快取類。
此處使用了一個記憶體快取類LruCache。

    public class BitmapCache implements ImageCache {
        private LruCache<String, Bitmap> mCache; 

        public BitmapCache() {
            int maxSize = 5 * 1024 * 1024;
            mCache = new LruCache<String, Bitmap>(maxSize) {  
                @Override  
                protected int sizeOf(String key, Bitmap bitmap) {  
                    return bitmap.getRowBytes() * bitmap.getHeight();  
                }
            };
        }
        @Override
        public Bitmap getBitmap(String url) {
            // TODO Auto-generated method stub
            return mCache.get(url);//從記憶體快取中取出圖片
        }

        @Override
        public void putBitmap(String url, Bitmap arg1) {
            // TODO Auto-generated method stub
            mCache.put(url, arg1);//像記憶體快取中放入圖片
        }

    }

要實現三級快取只需要在二級快取類上做些修改即可。因為要使用到磁碟進行快取,因此需要對磁碟進行讀寫。對磁碟進行快取可以通過DiskLruCache進行實現,這是一個開源的磁碟快取方案。
先來看看改造後再快取類。

    public class BitmapCache implements ImageCache {
        private LruCache<String, Bitmap> mCache; 
        private static DiskLruCache mDiskLruCache;
         private static final int DISKMAXSIZE = 30 * 1024 * 1024;//磁碟快取大小

        public BitmapCache() {
            int maxSize = 5 * 1024 * 1024;
            mCache = new LruCache<String, Bitmap>(maxSize) {  
                @Override  
                protected int sizeOf(String key, Bitmap bitmap) {  
                    return bitmap.getRowBytes() * bitmap.getHeight();  
                }
            try {
            mDiskLruCache = DiskLruCache.open(getDiskCacheDir(context.getApplicationContext(), "xxxxx"), getAppVersion(context), 1, DISKMAXSIZE);//初始化mDiskLruCache ,包括了快取路徑,系統版本,同一個key可以對應多少個快取檔案,基本都是傳1以及最大快取大小。
            } catch (IOException e){
                e.printStackTrace();
            }
        }
        @Override
        public Bitmap getBitmap(String url) {
            // TODO Auto-generated method stub
            if(mLruCache.get(url) != null) {
                return mCache.get(url);//從記憶體快取中取出圖片
            } else {
                String key = MD5Utils.md5(url);
                try {
                    if (mDiskLruCache.get(key) != null) {
                             Snapshot snapshot = mDiskLruCache.get(key);
                             Bitmap bitmap = null;
                               if (snapshot != null) {
                                   bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
                                    mLruCache.put(url, bitmap);//將圖片存入二級快取
                                   }
                             return bitmap;
                        }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
             return null;
        }

        @Override
        public void putBitmap(String url, Bitmap arg1) {
            // TODO Auto-generated method stub
            mCache.put(url, arg1);//像記憶體快取中放入圖片
            String key = MD5Utils.md5(url);// 圖片對應的URL需要進行md5轉化。
            try{
                if (mDiskLruCache.get(key) == null) {
                    DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                    if (editor != null) {
                         OutputStream outputStream = editor.newOutputStream(0);
                         if (bitmap.compress(CompressFormat.JPEG, 100, outputStream)) {
                             editor.commit();
                         } else {
                             editor.abort();
                         }
                    }
                    mDiskLruCache.flush();
                }
            } catch(IOException e) {
                 e.printStackTrace();
            }
        }
    }

在上面的程式碼中,通過兩個函式來判斷了快取路徑以及系統版本。函式如下:

 public static 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();
        }
        return new File(cachePath + File.separator + uniqueName);
    }
 public int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

到此,Volley進行圖片載入的三級快取策略介紹完畢。以上程式碼可以直接使用。