在Android_开源框架_Volley(Google IO 2013)源代码及内部实现过程分析一文中,简单概述了Volley框架内部实现过程。如想理解彻底应该熟悉android多线程通信机制(Android_Thread多线程_Handler,Message,Looper,MessageQueue多线程和特殊UI更新一文) ,JDK1.5提供的java.util.concurrent相关并发库和http访问网络(Android_HttpURLConnection_Get和Post请求[该框架使用]/Android_HttpClient_get请求post表单提交上传一文)及图片缓存(Android_图片的三级缓存一文)相关知识
https://android.googlesource.com/platform/frameworks/volley
(1).封装MyVolley类
在使用Volley之前,应该先进行初始化(可以自定义Application或者在SplashActivity中完成初始化),仅对外提供RequestQueue(添加request)和ImageLoader(下载图片)get方法!
/** * MyVolley.java * @see http://blog.csdn.net/zimo2013 * @author zimo2013 * */ public class MyVolley { private static final String TAG = "MyVolley"; private static MyVolley instance; private static RequestQueue mRequestQueue; private static ImageLoader mImageLoader; private final static int RATE = 8; // 默认分配最大空间的几分之一 private MyVolley(Context context) { mRequestQueue = Volley.newRequestQueue(context); // 确定在LruCache中,分配缓存空间大小,默认程序分配最大空间的 1/8 ActivityManager manager = (ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE); int maxSize = manager.getMemoryClass() / RATE; // 比如 64M/8,单位为M // BitmapLruCache自定义缓存class,android本身支持二级缓存,在BitmapLruCache封装一个软引用缓存 mImageLoader = new ImageLoader(mRequestQueue, new BitmapLruCache( 1024 * 1024 * maxSize)); Log.i(TAG, "MyVolley初始化完成"); } /** * 初始化Volley相关对象,在使用Volley前应该完成初始化 * * @param context */ public static void init(Context context) { if (instance == null) { instance = new MyVolley(context); } } /** * 得到请求队列对象 * * @return */ private static RequestQueue getRequestQueue() { throwIfNotInit(); return mRequestQueue; } /** * 得到ImageLoader对象 * * @return */ public static ImageLoader getImageLoader() { throwIfNotInit(); return mImageLoader; } public static void addRequest(Request> request) { getRequestQueue().add(request); } public static void getImage(String requestUrl, ImageView imageView) { getImage(requestUrl, imageView, 0, 0); } public static void getImage(String requestUrl, ImageView imageView, int defaultImageResId, int errorImageResId) { getImage(requestUrl, imageView, defaultImageResId, errorImageResId, 0, 0); } public static void getImage(String requestUrl, ImageView imageView, int defaultImageResId, int errorImageResId, int maxWidth, int maxHeight) { imageView.setTag(requestUrl); getImageLoader().get(requestUrl, ImageListenerFactory.getImageListener( imageView, defaultImageResId, errorImageResId), maxWidth, maxHeight); } /** * 检查是否完成初始化 */ private static void throwIfNotInit() { if (instance == null) {// 尚未初始化 throw new IllegalStateException("MyVolley尚未初始化,在使用前应该执行init()"); } } }
(2).图片的三级缓存相关类
/** * LruCache缓存管理类,该类实现了ImageCache接口,并实现了LruCache * 一旦bitmap对象从LruCache中被挤出,将会被放置在BitmapSoftRefCache中,再配合该框架本身支持的硬盘缓存,可以完成图片三级缓存 * * BitmapLruCache.java * @author zimo2013 * @see http://blog.csdn.net/zimo2013 * */ public class BitmapLruCache extends LruCache
implements ImageCache { private static final String TAG = "BitmapLruCache"; private BitmapSoftRefCache softRefCache; public BitmapLruCache(int maxSize) { super(maxSize); softRefCache = new BitmapSoftRefCache(); } @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { if (evicted) { LogUtil.i(TAG, "空间已满,缓存图片被挤出:" + key); // 将被挤出的bitmap对象,添加至软引用BitmapSoftRefCache softRefCache.putBitmap(key, oldValue); } } /** * 得到缓存对象 */ @Override public Bitmap getBitmap(String url) { Bitmap bitmap = get(url); // 如果bitmap为null,尝试从软引用缓存中查找 if (bitmap == null) { bitmap = softRefCache.getBitmap(url); } else { LogUtil.i(TAG, "LruCache命中:" + url); } return bitmap; } /** * 添加缓存对象 */ @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); } } /** * 软引用缓存管理类 * * BitmapSoftRefCache.java * @author zimo2013 * @see http://blog.csdn.net/zimo2013 * */ public class BitmapSoftRefCache implements ImageCache{ private static final String TAG = "BitmapSoftRefCache"; private LinkedHashMap
> map; public BitmapSoftRefCache() { map = new LinkedHashMap >(); } /** * 从软引用集合中得到Bitmap对象 */ @Override public Bitmap getBitmap(String url) { Bitmap bitmap = null; SoftReference softRef = map.get(url); if(softRef != null){ bitmap = softRef.get(); if(bitmap == null){ map.remove(url); //从map中移除 LogUtil.w(TAG, url+"对象已经被GC回收"); }else{ LogUtil.i(TAG, "命中"+url); } } return bitmap; } /** * 从软引用集合中添加bitmap对象 */ @Override public void putBitmap(String url, Bitmap bitmap) { SoftReference softRef = new SoftReference (bitmap); map.put(url, softRef); } }
(3).图片错位
关于使用该框架造成图片错位问题,App使用了ImageLoader下载图片,当ListView滚动很快时,还是会发生错位!记得听别人提起过,使用该框架可以避免图片错位,但是结果还是会发生错位,查看源码并没有找到相关避免错位的措施,不知道是不是自己的使用方法不对,如您知晓,麻烦告知博主一下~
这里通过覆写ImageListener方法,通过ImageView为其指定Tag标签,防止图片错位,即比对下载完图片的ImageUrl和当前该ImageView的Tag是否相等~
public class ImageListenerFactory{ 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) { if(view.getTag().toString() == response.getRequestUrl()){ view.setImageBitmap(response.getBitmap()); }else{ LogUtil.i(TAG, "图片错位"); } } else if (defaultImageResId != 0) { view.setImageResource(defaultImageResId); } } }; } }
(1).StringRequest
public void testStringRequest(){ String url = "http://www.baidu.com"; //如果出现乱码,应该修改StringRequest的parseNetworkResponse()方法,指定byte[]-->String 编码 MyVolley.getRequestQueue().add(new StringRequest(url, new Response.Listener
() { @Override public void onResponse(String response) { System.out.println("response:"+response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } })); } (2).JsonObjectRequest
public void testJsonObjectRequest(){ String url = "http://api.mobile.meituan.com/group/v1/deal/new-cate-list/android/4.1?cityId=1"; //如果出现乱码,应该修改StringRequest的parseNetworkResponse()方法,指定byte[]-->String 编码 MyVolley.getRequestQueue().add( new JsonObjectRequest(url, null, new Response.Listener
() { @Override public void onResponse(JSONObject response) { //该JSONObject为android系统提供的,如果希望得到一个已经解析完的对象,可以继承JsonRequest //根据response,解析数据 try { System.out.println(response.get("stid")); } catch (JSONException e) { e.printStackTrace(); } } },new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { System.out.println(error); } }) ); } (3).自定义Request(Json解析)
/** * MyJsonRequest.java * * @author zimo2013 */ public class MyJsonListRequest
extends JsonRequest { private Gson gson; private Class clazz; private String mKey; /** * GET请求方式,直接将json字符串解析为 对应的clazz对象 * * @param url * 请求url * @param clazz * 解析的class字节码 * @param listener * 请求成功监听器 * @param errorListener * 请求失败监听器 */ public MyJsonListRequest(String url, Class clazz, Listener listener, ErrorListener errorListener) { this(url, null, clazz, listener, errorListener); } /** * GET请求方式,将json中的key对应的value解析为 对应的clazz对象 * * @param url * 请求url * @param key * 取得指定的key,NOTE:只支持 root-key,所有子key均错误 * @param clazz * 解析的class字节码 * @param listener * 请求成功监听器 * @param errorListener * 请求失败监听器 */ public MyJsonListRequest(String url, String key, Class clazz, Listener listener, ErrorListener errorListener) { this(Method.GET, url, null, key, clazz, listener, errorListener); } /** * * @param method * 请求方法 Use {@link com.android.volley.Request.Method}. * @param url * @param requestBody * 如果是POST请求,可以提交form表单字符串,比如 name=zhangsan&age=20 * @param key * 取得指定的key,NOTE:只支持 root-key,所有子key均错误 * @param clazz * 解析的class字节码 * @param listener * 请求成功监听器 * @param errorListener * 请求失败监听器 */ public MyJsonListRequest(int method, String url, String requestBody, String key, Class clazz, Listener listener, ErrorListener errorListener) { super(method, url, null, listener, errorListener); this.clazz = clazz; mKey = key; gson = new Gson(); } @Override protected Response parseNetworkResponse(NetworkResponse response) { try { String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); T t = null; if(mKey == null){ t = gson.fromJson(json, clazz); }else{ JsonObject jsonObject = gson.fromJson(json, JsonObject.class); t = gson.fromJson(jsonObject.get(mKey), clazz); } return Response.success(t, HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (JsonSyntaxException e) { return Response.error(new ParseError(e)); } } } public void testCustomRequest(){ String urlString = "http://192.168.117.120:8080/news/json.html"; MyJsonRequest
request = new MyJsonRequest (urlString, NewsInfo.class, new Listener () { @Override public void onResponse(NewsInfo response) { System.out.println(response.getClass()); System.out.println(response); } }, null); MyVolley.getRequestQueue().add(request); } (4).POST表单提交
public void testRequestByPost(){ String urlString = "http://192.168.43.240:8080/news/servlet/Post"; StringRequest request = new StringRequest(Method.POST, urlString, new Listener
() { @Override public void onResponse(String response) { } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }){ //覆写getBody()方法提交表单数据 @Override public byte[] getBody() { return "name=zhangsan&age=15".getBytes(); } }; MyVolley.getRequestQueue().add(request); } (5).ImageLoader异步加载图片(防错位)
public void testImageLoader(){ MyVolley.getImageLoader().get(imgUrl, //ImageListenerFactory为自定义类,封装后即可获取图片资源,完成UI更新 ImageListenerFactory.getImageListener(//参考ImageLoader.getImageListener() imageView, // ImageView对象 R.drawable.ic_launcher, // 默认Image,如果不设应置为0 R.drawable.ic_launcher)); // 错误Image,如果不设应置为0 }