安卓圖片三級快取策略與實現
阿新 • • 發佈:2019-02-01
前言:
這裡說的三級快取,分別指的是:記憶體快取、檔案快取和網路這三個層面。
一般來說,我們首次載入圖片,記憶體和檔案是沒有快取的,這樣我們需要從網路載入,載入完成後,我們會存到記憶體和檔案中去;當再次載入圖片的時候,我們會先查詢記憶體有沒有,如果有就直接顯示記憶體中的圖片,如果沒有,我們會接著查詢檔案中是否有,如果檔案中有,我們會顯示檔案中的圖片,並且把它存到記憶體中去,這樣下次我們在記憶體中就能找到它了。
我們之所以要做快取,主要是為了提高效率,節省流量。但是為什麼要做三級呢?為什麼不只存在記憶體或者只存在檔案中呢?這是因為記憶體的讀取速度快,但是容易被回收,容量小,檔案的讀取速度次之,不過容量大,不到不得已不會被回收。
有了以上的介紹,我們已經知道了三級快取的必要性和實施步驟,接下來,我們就要選擇在每級快取的快取策略了。
記憶體快取,最開始大家推崇的是用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的例項2.在getBitmap函式中通過mLruCache.get(url)得到記憶體的圖片,沒有時返回空。 3.在putBitmap函式中通過mLruCache.put(url,bitmap)把圖片存入記憶體。 具體用法可以看下面我貼出的程式碼。// 獲取應用可佔記憶體的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
使用DiskLruCache需要以下步驟: 1.通過DiskLruCache.open(...)得到DiskLruCache的例項2.在getBitmap函式中如果mLruCache.get(url)返回空,通過mDiskLruCache.get(key)得到DiskLruCache.Snapshot,通過BitmapFratory(snapshot.getInputStream(0))得到圖片,沒有時返回空//DiskLruCache例項,它的構造方法是私有的,所以我們需要通過它提供的open方法來生成。 try { mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.getContext(),CACHE_FOLDER_NAME), getAppVersion(MyApplication.getContext()) , 1, DISKMAXSIZE); } catch (IOException e) { e.printStackTrace(); }
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);
}
});
好了,以上就是安卓三級快取的實現。