在開發安卓應用中避免不了要使用到網絡圖片,獲取網絡圖片很簡單,可是須要付出必定的代價——流量。對於少數的圖片而言問題不大,但若是手機應用中包含大量的圖片,這勢必會耗費用戶的必定流量,若是咱們不加以處理,每次打開應用都去網絡獲取圖片,那麼用戶可就不樂意了,這裏的處理就是指今天要講的緩存策略(緩存層分爲三層:內存層,磁盤層,網絡層)。java
關於緩存層的工做,當咱們第一次打開應用獲取圖片時,先到網絡去下載圖片,而後依次存入內存緩存,磁盤緩存,當咱們再一次須要用到剛纔下載的這張圖片時,就不須要再重複的到網絡上去下載,直接能夠從內存緩存和磁盤緩存中找,因爲內存緩存速度較快,咱們優先到內存緩存中尋找該圖片,若是找到則運用,若是沒有找到(內存緩存大小有限),那麼咱們再到磁盤緩存中去找。只要咱們合理的去協調這三層緩存運用,即可以提高應用性能和用戶體驗。android
此博文源碼下載地址 https://github.com/Javen205/VolleyDemo.gitgit
一、內存層:(手機內存)github
內存緩存相對於磁盤緩存而言,速度要來的快不少,但缺點容量較小且會被系統回收,這裏的實現我用到了LruCache。算法
LruCache這個類是Android3.1版本中提供的,若是你是在更早的Android版本中開發,則須要導入android-support-v4的jar包。緩存
磁盤層:(SD卡)網絡
相比內存緩存而言速度要來得慢不少,但容量很大,這裏的實現我用到了DiskLruCache類。框架
DiskLruCache是非Google官方編寫,但得到官方認證的硬盤緩存類,該類沒有限定在Android內,因此理論上java應用也可使用DiskLreCache來緩存。eclipse
這是DiskLruCache類的下載地址:http://pan.baidu.com/s/1o6tPjz8ide
網絡層:(移動網絡,無線網絡)
這個就沒什麼解釋的了,就是咱們上網用的流量。這裏的網絡訪問實現我用到了開源框架Volley。
開源框架Volley是2013年Google I/O大會發布的,Volley是Android平臺上的網絡通訊庫,能使網絡通訊更快,更簡單,更健壯。它的設計目標就是很是適合去進行數據量不大,但通訊頻繁的網絡操做,而對於大數據量的網絡操做,好比說下載文件等,Volley的表現就會很是糟糕。
這是Volley的下載地址:http://pan.baidu.com/s/1kThedX9
如下是代碼實現:
先附上兩個工具類
生成MD5序列幫助類,DiskLruCache磁盤緩存類(下載地址見上文)
import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5Utils { /** * 使用md5的算法進行加密 */ public static String md5(String plainText) { byte[] secretBytes = null; try { secretBytes = MessageDigest.getInstance("md5").digest( plainText.getBytes()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("沒有md5這個算法!"); } String md5code = new BigInteger(1, secretBytes).toString(16);// 16進制數字 // 若是生成數字未滿32位,須要前面補0 for (int i = 0; i < 32 - md5code.length(); i++) { md5code = "0" + md5code; } return md5code; } }
package com.javen.volley.cache; import java.io.File; import java.io.IOException; import java.io.OutputStream; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.os.Environment; import android.support.v4.util.LruCache; import android.util.Log; import com.android.volley.toolbox.ImageLoader.ImageCache; import com.javen.volley.cache.DiskLruCache.Snapshot; /** * 圖片緩存幫助類 * 包含內存緩存LruCache和磁盤緩存DiskLruCache * @author Javen */ public class ImageCacheUtil implements ImageCache { private String TAG=ImageCacheUtil.this.getClass().getSimpleName(); //緩存類 private static LruCache<String, Bitmap> mLruCache; private static DiskLruCache mDiskLruCache; //磁盤緩存大小 private static final int DISKMAXSIZE = 10 * 1024 * 1024; public ImageCacheUtil(Context context) { // 獲取應用可佔內存的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(); } }; try { // 獲取DiskLruCahce對象 // mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.newInstance(), "xxxxx"), getAppVersion(MyApplication.newInstance()), 1, DISKMAXSIZE); mDiskLruCache = DiskLruCache.open(getDiskCacheDir(context.getApplicationContext(), "xxxxx"), getAppVersion(context), 1, DISKMAXSIZE); } catch (IOException e) { e.printStackTrace(); } } /** * 從緩存(內存緩存,磁盤緩存)中獲取Bitmap */ @Override public Bitmap getBitmap(String url) { if (mLruCache.get(url) != null) { // 從LruCache緩存中取 Log.i(TAG,"從LruCahce獲取"); return mLruCache.get(url); } else { String key = MD5Utils.md5(url); try { if (mDiskLruCache.get(key) != null) { // 從DiskLruCahce取 Snapshot snapshot = mDiskLruCache.get(key); Bitmap bitmap = null; if (snapshot != null) { bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0)); // 存入LruCache緩存 mLruCache.put(url, bitmap); Log.i(TAG,"從DiskLruCahce獲取"); } return bitmap; } } catch (IOException e) { e.printStackTrace(); } } return null; } /** * 存入緩存(內存緩存,磁盤緩存) */ @Override public void putBitmap(String url, Bitmap bitmap) { // 存入LruCache緩存 mLruCache.put(url, bitmap); // 判斷是否存在DiskLruCache緩存,若沒有存入 String key = MD5Utils.md5(url); 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(); } } /** * 該方法會判斷當前sd卡是否存在,而後選擇緩存地址 * * @param context * @param uniqueName * @return */ 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); } /** * 獲取應用版本號 * * @param context * @return */ 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請求隊列處理類,用來管理Rquest請求對象操做
package com.javen.volley; import android.content.Context; import android.text.TextUtils; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.ImageLoader; import com.android.volley.toolbox.Volley; import com.javen.volley.cache.ImageCacheUtil; public class VolleyController { // 建立一個TAG,方便調試或Log private static final String TAG = "VolleyController"; // 建立一個全局的請求隊列 private RequestQueue reqQueue; private ImageLoader imageLoader; // 建立一個static VolleyController對象,便於全局訪問 private static VolleyController mInstance; private Context mContext; private VolleyController(Context context) { mContext=context; } /** * 如下爲須要咱們本身封裝的添加請求取消請求等方法 */ // 用於返回一個VolleyController單例 public static VolleyController getInstance(Context context) { if (mInstance == null) { synchronized(VolleyController.class) { if (mInstance == null) { mInstance = new VolleyController(context); } } } return mInstance; } // 用於返回全局RequestQueue對象,若是爲空則建立它 public RequestQueue getRequestQueue() { if (reqQueue == null){ synchronized(VolleyController.class) { if (reqQueue == null){ reqQueue = Volley.newRequestQueue(mContext); } } } return reqQueue; } public ImageLoader getImageLoader(){ getRequestQueue(); //若是imageLoader爲空則建立它,第二個參數表明處理圖像緩存的類 if(imageLoader==null){ //LruCache //imageLoader=new ImageLoader(reqQueue, new LruBitmapCache()); //LruCache DiskLruCache imageLoader=new ImageLoader(reqQueue, new ImageCacheUtil(mContext)); } return imageLoader; } /** * 將Request對象添加進RequestQueue,因爲Request有*StringRequest,JsonObjectResquest... * 等多種類型,因此須要用到*泛型。同時可將*tag做爲可選參數以便標示出每個不一樣請求 */ public <T> void addToRequestQueue(Request<T> req, String tag) { // 若是tag爲空的話,就是用默認TAG req.setTag(TextUtils.isEmpty(tag) ? TAG : tag); getRequestQueue().add(req); } public <T> void addToRequestQueue(Request<T> req) { req.setTag(TAG); getRequestQueue().add(req); } // 經過各Request對象的Tag屬性取消請求 public void cancelPendingRequests(Object tag) { if (reqQueue != null) { reqQueue.cancelAll(tag); } } }
圖片緩存管理類(方便外部調用)
這裏向外部提供了一個loadImage的重載方法,一個傳入加載圖片的寬高,一個默認加載原圖,使得外部再也不須要關注任何關於緩存的操做。
package com.javen.volley.utils; import android.content.Context; import android.graphics.Bitmap; import android.widget.ImageView; import com.android.volley.VolleyError; import com.android.volley.toolbox.ImageLoader.ImageContainer; import com.android.volley.toolbox.ImageLoader.ImageListener; import com.javen.volley.VolleyController; /** * 圖片緩存管理類 獲取ImageLoader對象 * @author Javen * */ public class ImageCacheManager { private static String TAG = ImageCacheManager.class.getSimpleName(); /** * 獲取ImageListener * * @param view * @param defaultImage * @param errorImage * @return */ public static ImageListener getImageListener(final ImageView view, final Bitmap defaultImage, final Bitmap errorImage) { return new ImageListener() { @Override public void onErrorResponse(VolleyError error) { // 回調失敗 if (errorImage != null) { view.setImageBitmap(errorImage); } } @Override public void onResponse(ImageContainer response, boolean isImmediate) { // 回調成功 if (response.getBitmap() != null) { view.setImageBitmap(response.getBitmap()); } else if (defaultImage != null) { view.setImageBitmap(defaultImage); } } }; } /** * 提供給外部調用方法 * * @param url * @param view * @param defaultImage * @param errorImage */ public static void loadImage(Context context,String url, ImageView view, Bitmap defaultImage, Bitmap errorImage) { VolleyController.getInstance(context).getImageLoader().get(url, ImageCacheManager.getImageListener(view, defaultImage, errorImage), 0, 0); } /** * 提供給外部調用方法 * * @param url * @param view * @param defaultImage * @param errorImage */ public static void loadImage(Context context,String url, ImageView view, Bitmap defaultImage, Bitmap errorImage, int maxWidth, int maxHeight) { VolleyController.getInstance(context).getImageLoader().get(url, ImageCacheManager.getImageListener(view, defaultImage, errorImage), maxWidth, maxHeight); } }
使用方法
public void CacheImage(View view){ Bitmap defaultImage=BitmapFactory.decodeResource(getResources(), R.drawable.loading); Bitmap errorImage=BitmapFactory.decodeResource(getResources(), R.drawable.load_error); ImageCacheManager.loadImage(this, url, imageView, defaultImage, errorImage); }