最近看到一本书,其中有一句话写的不错:实际上,将第三方API打包是个良好的实践手段,当你打包一个第三方API,你就降低了对它的依赖,未来你可以不太痛苦地改用其他代码库,在你测试自己的代码时,打包也有助于模拟第三方调用。打包的好处还在于你不必帮死在某个特定的API设计上。你可以定义自己感觉舒服的API。
现在我们公司的应用就有这个问题,老大不知道出于什么考虑,使用的是AQuery框架进行的网络访问和图片加载,很多人可能没有听说过这个库,因为github上显示它已经在2013年停止更新了。那么现在如果需要更新整个应用的框架成volley怎么办?答案是很难,非常难,整个应用和AQuery框架耦合非常严重,修改起来的难度和时间成本都不可估计。
基于以上上下文,Volley尽管已经很完善了,但是为了以后的考虑,还是需要将Volley进行简单封装,外部不能直接使用Volley相关类,只能使用通过封装之后提供的api进行网络访问和图片加载。
在封装一个api之前需要详细了解一下这个api,网上的Volley介绍太多了,我列出几个讲的比较好的:
https://bxbxbai.github.io/2014/09/14/android-working-with-volley/ 这个主要是讲了一下Volley的用法
http://code.tutsplus.com/tutorials/an-introduction-to-volley--cms-23800 这个介绍了Volley的一些实用功能,比如header,cookies和访问优先级
http://www.androidhive.info/2014/05/android-working-with-volley-library-1/ 这个博客将Volley进行了简单的封装
看完这些博客之后,应该对使用有了初步了解,下面是对Volley源码的详细分析:
http://www.chengxuyuans.com/Android/90526.html
还有一个比较重要和实用的是Volley有一个判断缓存是否失效的功能,这个功能主要是通过Request类的parseNetworkResponse()方法来获取网络数据的header HttpHeaderParser.parseCacheHeaders(response) 根据header里面的信息来判断缓存的失效 现在我的初步想法是有两种方案,第一种是将Volley只进行简单的封装,方便使用,这种封装方式的优点是可以使用Volley的所有功能,缺点是会造成整个项目和Volley第三方API的耦合程度很高,不方便以后的修改;第二种就是将Volley进行高程度封装,外部不允许调用任何原生Volley的功能,只能使用封装类提供的接口,这种方案优点是可以非常方便的修改框架,缺点就是无法完全使用Volley的各种功能,只能使用最基本的功能。 这是我的想法,不知道是不是错的,小白一个,只能想到这种拙劣的解决方案,望指点。
有了这两种方案,于是就开始着手写代码封装了
第一种方案: BaseVolleyApi.class类
public abstract class BaseVolleyApi { private static RequestQueue requestQueue; private static ImageLoader imageLoader; public static RequestQueue getRequestQueue() { if (requestQueue == null) { synchronized (BaseVolleyApi.class){ if (requestQueue == null) requestQueue = Volley.newRequestQueue(RootApplication.getInstance()); } } return requestQueue; } public static ImageLoader getImageLoader() { if (imageLoader == null) { synchronized (BaseVolleyApi.class) { if (imageLoader == null){ VolleyLruCache cache = new VolleyLruCache(); imageLoader = new ImageLoader(getRequestQueue(), cache); } } } return imageLoader; } }
VolleyLruCache.class类
public class VolleyLruCache extends LruCache<String, Bitmap> implements ImageLoader.ImageCache{ private static int getCacheSize(){ return (int)(Runtime.getRuntime().maxMemory()/1024/8); } public VolleyLruCache() { this(getCacheSize()); } private VolleyLruCache(int size){ super(size); } @Override public Bitmap getBitmap(String url) { return get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); } }
上面的实现方案就是第一种,简单封装,方便使用,耦合度很大,不方便以后的框架修改
第二种方案:
第二种方案就比较复杂了,主要是要对Volley的功能进行抽取,选出最基本的功能进行封装,这样就能够相对轻松的更换框架。这种方案的代码复杂度就比较高了,网络访问和图片加载就需要分成两个单独的类去处理。
BaseNetApi.class类
public abstract class BaseNetApi { /** 网络访问requestQueue */ private RequestQueue requestQueue; private RequestQueue getRequestQueue(int maxDiskCacheBytes){ if (requestQueue == null) requestQueue = Volley.newRequestQueue(RootApplication.getInstance(), maxDiskCacheBytes); return requestQueue; } protected RequestQueue getRequestQueue(){ return getRequestQueue(-1); } /** * 回调接口 */ public interface OnNetCallback<T>{ void onSuccess(T result); void onFail(NetError error); } private boolean checkIfExtendsRequest(Class clazz){ while (clazz.getSuperclass() != null){ clazz = clazz.getSuperclass(); if (clazz == Request.class) return true; } return false; } /** * 网络请求 */ protected <T> void makeRequest(final Context context, Class<?> clazz, String url, final Map<String, String> params, final OnNetCallback<T> callback){ //网络请求 Request request = null; //失败回调 Response.ErrorListener errorListener = null; //成功回调 Response.Listener listener = null; //判空 if (callback != null) { errorListener = new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { if (context instanceof Activity && (((Activity)(context)).isFinishing())) { L.i("activity finish, not callback"); return ; } NetError netError = new NetError(); netError.transferVolleyError(error); callback.onFail(netError); } }; listener = new Response.Listener<T>() { @Override public void onResponse(T response) { if (context instanceof Activity && (((Activity)(context)).isFinishing())) { L.i("activity finish, not callback"); return ; } callback.onSuccess(response); } }; } //启动网络请求 if (clazz == ImageRequest.class){ throw new IllegalArgumentException("please use imageloader"); }else if (checkIfExtendsRequest(clazz)) { try { Constructor constructor = clazz.getConstructor(int.class, String.class, Response.Listener.class, Response.ErrorListener.class, Map.class); int method = Request.Method.GET; if (params != null) method = Request.Method.POST; request = (Request) constructor.newInstance(method, url, listener, errorListener, params); } catch (Exception e) { L.e("error reflect", e); return; } }else { throw new IllegalArgumentException("unsupported type"); } //自定义超时时间,重试次数 // request.setRetryPolicy(new DefaultRetryPolicy()); getRequestQueue().add(request); } /** * 对{@linkplain StringRequest}的封装类 */ private static class StringRequestImpl extends StringRequest{ private Map<String, String> params; public StringRequestImpl(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener, Map<String, String> params) { super(method, url, listener, errorListener); this.params = params; } @Override protected Map<String, String> getParams() throws AuthFailureError { return params; } } /** * 对{@linkplain JsonObjectRequest}的封装类 */ private static class JsonObjectRequestImpl extends JsonObjectRequest{ private Map<String, String> params; public JsonObjectRequestImpl(int method, String url, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener, Map<String, String> params) { super(method, url, listener, errorListener); this.params = params; } @Override protected Map<String, String> getParams() throws AuthFailureError { return params; } } /** * 对{@linkplain JsonArrayRequest}的封装类 */ private static class JsonArrayRequestImpl extends JsonArrayRequest{ private Map<String, String> params; public JsonArrayRequestImpl(int method, String url, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener, Map<String, String> params) { super(method, url, listener, errorListener); this.params = params; } @Override protected Map<String, String> getParams() throws AuthFailureError { return params; } } /** * string 请求 * @param context 相关上下文 * @param url 网络访问url * @param params 网络请求参数 * @param callback 网络请求回调 */ public void stringRequest(Context context, String url, Map<String, String> params, OnNetCallback<String> callback){ makeRequest(context, StringRequestImpl.class, url, params, callback); } /** * jsonObject 请求 * @param context 相关上下文 * @param url 网络访问url * @param params 网络请求参数 * @param callback 网络请求回调 */ public void jsonObjectRequest(Context context, String url, Map<String, String> params, OnNetCallback<JSONObject> callback){ makeRequest(context, JsonObjectRequestImpl.class, url, params, callback); } /** * jsonArray 请求 * @param context 相关上下文 * @param url 网络访问url * @param params 网络请求参数 * @param callback 网络请求回调 */ public void jsonArrayRequest(Context context, String url, Map<String, String> params, OnNetCallback<JSONArray> callback){ makeRequest(context, JsonArrayRequestImpl.class, url, params, callback); } }
ImageLoader.class类
public class ImageLoader { /** 最大的图片缓存大小 */ private final int MAXDISKCACHEBYTES = 10 * 1024 *1024; private static volatile ImageLoader instance; private com.android.volley.toolbox.ImageLoader imageLoader; private ImageLoader(){ RequestQueue requestQueue = Volley.newRequestQueue(RootApplication.getInstance(), MAXDISKCACHEBYTES); VolleyLruCache lruCache = new VolleyLruCache(); imageLoader = new com.android.volley.toolbox.ImageLoader(requestQueue, lruCache); } public static ImageLoader getInstance(){ if (instance == null){ synchronized (ImageLoader.class){ if (instance == null) instance = new ImageLoader(); } } return instance; } /** 通过反射获取imageview的大小 */ private int getImageViewFieldValue(Object object, String fieldName) { int value = 0; try { Field field = ImageView.class.getDeclaredField(fieldName); field.setAccessible(true); int fieldValue = (Integer) field.get(object); if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) { value = fieldValue; } } catch (Exception e) { L.e(e); } return value; } /** * 加载图片 * @param url 图片url * @param imageView 需要加载图片的视图 */ public void loadImage(String url, final ImageView imageView){ loadImage(url, imageView, null); } /** * 只带回调的图片加载 * @param url 图片url * @param listener 图片加载回调 */ public void loadImage(String url, final OnLoadCallBack listener){ loadImage(url, 0, 0, listener); } /** * 带回调的加载图片 * @param url 图片url * @param width 需要加载的图片宽 * @param height 需要加载的图片高 * @param listener 加载图片完成回调 */ public void loadImage(String url, int width, int height, final OnLoadCallBack listener){ loadImage(url, null, width, height, listener); } /** * 带回调的加载图片 * @param url 图片url * @param imageView 需要加载图片的视图 * @param listener 加载图片的回调 */ public void loadImage(String url, final ImageView imageView, final OnLoadCallBack listener){ int width = getImageViewFieldValue(imageView, "mMaxWidth"); int height = getImageViewFieldValue(imageView, "mMaxHeight"); loadImage(url, imageView, width, height, listener); } /** * 加载图片 * @param url 图片url * @param imageView 需要加载图片的视图 * @param width 需要加载视图的宽 * @param height 需要加载视图的高 * @param listener 加载图片回调 */ public void loadImage(String url, final ImageView imageView, int width, int height, final OnLoadCallBack listener){ imageLoader.get(url, new com.android.volley.toolbox.ImageLoader.ImageListener() { @Override public void onResponse(com.android.volley.toolbox.ImageLoader.ImageContainer response, boolean isImmediate) { if (imageView != null) imageView.setImageBitmap(response.getBitmap()); if (listener != null) listener.onLoadSuccess(response.getBitmap(), response.getRequestUrl()); } @Override public void onErrorResponse(VolleyError error) { if (listener != null) { NetError netError = new NetError(); netError.transferVolleyError(error); listener.onLoadFail(netError); } } }, width, height); } /** * 加载图片 * @param url 图片url * @param imageView 需要加载该图片的url * @param defaultImageResId 加载图片时的默认资源id * @param errorImageResId 加载图片失败时显示的图片资源id */ public void loadImage(String url, final ImageView imageView, int defaultImageResId, int errorImageResId){ int width = getImageViewFieldValue(imageView, "mMaxWidth"); int height = getImageViewFieldValue(imageView, "mMaxHeight"); loadImage(url, imageView, defaultImageResId, errorImageResId, width, height); } /** * 加载图片 * @param url 图片url * @param imageView 需要加载该图片的url * @param defaultImageResId 加载图片时的默认资源id * @param errorImageResId 加载图片失败时显示的图片资源id * @param width 加载图片的宽度 * @param height 加载图片的高度 */ public void loadImage(String url, final ImageView imageView, int defaultImageResId, int errorImageResId, int width, int height){ com.android.volley.toolbox.ImageLoader.ImageListener listener = com.android.volley.toolbox.ImageLoader.getImageListener(imageView, defaultImageResId, errorImageResId); imageLoader.get(url, listener, width, height); } /** * 加载图片回调 */ public interface OnLoadCallBack { void onLoadSuccess(Bitmap bitmap, String url); void onLoadFail(NetError error); } }
上面的两个类,就是最基本的网络访问和图片加载
基于此,还封装了基本的错误类,主要是用来将Volley的exception转换成通用的exception
NetError.class类
public class NetError extends Exception{ public int errorCode; public String errorMessage; /** * 将volley的错误信息转换成通用的信息 */ public void transferVolleyError(VolleyError error){ if (error.networkResponse != null) this.errorCode = error.networkResponse.statusCode; this.errorMessage = error.toString(); } }
上面就是列举的第二种封装方式,封装了最基本的功能。
以上就是我最基本的想法,写在这里来抛砖引玉,不知道这种思想是否正确
源码在我的github中,点我
希望大家多多评论