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進行圖片載入的三級快取策略介紹完畢。以上程式碼可以直接使用。