Android Volley框架的使用(四)图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)
在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量。对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理,每次打开应用都去网络获取图片,那么用户可就不乐意了,这里的处理就是指今天要讲的缓存策略(缓存层分为三层:内存层,磁盘层,网络层)。
关于缓存层的工作,当我们第一次打开应用获取图片时,先到网络去下载图片,然后依次存入内存缓存,磁盘缓存,当我们再一次需要用到刚才下载的这张图片时,就不需要再重复的到网络上去下载,直接可以从内存缓存和磁盘缓存中找,由于内存缓存速度较快,我们优先到内存缓存中寻找该图片,如果找到则运用,如果没有找到(内存缓存大小有限),那么我们再到磁盘缓存中去找。只要我们合理的去协调这三层缓存运用,便可以提升应用性能和用户体验。
此博文源码下载地址 https://github.com/Javen205/VolleyDemo.git
1、内存层:(手机内存)
内存缓存相对于磁盘缓存而言,速度要来的快很多,但缺点容量较小且会被系统回收,这里的实现我用到了LruCache。
LruCache这个类是Android3.1版本中提供的,如果你是在更早的Android版本中开发,则需要导入android-support-v4的jar包。
磁盘层:(SD卡)
相比内存缓存而言速度要来得慢很多,但容量很大,这里的实现我用到了DiskLruCache类。
DiskLruCache是非Google官方编写,但获得官方认证的硬盘缓存类,该类没有限定在Android内,所以理论上java应用也可以使用DiskLreCache来缓存。
这是DiskLruCache类的下载地址:http://pan.baidu.com/s/1o6tPjz8
网络层:(移动网络,无线网络)
这个就没什么解释的了,就是我们上网用的流量。这里的网络访问实现我用到了开源框架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); }