开发当中,在请求网络的时候,大家或多或少都会使用一些第三方框架,Android-Async-Http、 Volley、XUtils、Okhttp、Retrofit 等。这些框架减少了我们的很多工作量,同时也对侵入了我们的项目。
大家回顾一下手头上的项目代码,是不是或多或少存在这样那样的历史遗留问题,第三方框架调用混乱,没有封装,或者封装不测底。如果要替换框架,很有可能要对项目大动干戈。
或许你会认为对第三方框架再进行一次封装,没有必要。那是你没有尝试过一行行复制张贴代码,进行替换。
有人可能会想, AS 不是有批量替换的功能吗,干嘛还要封装?
接下来我们先来看一下 okHttp 的使用
OkHttpClient.Builder mBuilder= new OkHttpClient.Builder(). connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(new LoggingInterceptor()) .cache(new Cache(mContext.getExternalFilesDir("okhttp"),cacheSize)); client=mBuilder.build(); Request.Builder builder = new Request.Builder().url(url).tag(option.mTag); builder=configHeaders(builder,option); Request build = builder.build(); client.newCall(build).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e, iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response, iResponseListener); } }); 复制代码
如果不进行封装,okHttp 请求网络大概是这样的,想一下,如果我们在项目中都这样使用,要替换框架,那花费的工作量要多大。
不过这种方法,在项目中大多数人不会这样使用,至少都会封装成为一个工具类。封装完成之后如下。
public static void doGet(Context context,String url, Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener){ OkHttpClient.Builder mBuilder= new OkHttpClient.Builder(). connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(new LoggingInterceptor()) .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize)); OkHttpClient cilent = mBuilder.build(); Request.Builder builder = new Request.Builder().url(url); Request build = builder.build(); cilent.newCall(build).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e, iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response, iResponseListener); } }); } public static void doPost(Context context,String url, Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener) { OkHttpClient.Builder mBuilder= new OkHttpClient.Builder(). connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(new LoggingInterceptor()) .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize)); OkHttpClient cilent = mBuilder.build(); url= NetUtils.checkUrl(url); final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url); FormBody.Builder builder = new FormBody.Builder(); FormBody formBody = builder.build(); Request.Builder requestBuilder = new Request.Builder().url(url).post(formBody).tag(option.mTag); Request request = requestBuilder.build(); cilent.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e,iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response,iResponseListener); } }); } 复制代码
这种封装成工具类的比完全没有封装的好了很多,但是还是存在一定的问题的。封装成工具类的话,别人完全有权限访问你这个工具类,他可以随时修改你工具类里面的实现,这给维护带来了一定的成本。那有没有更好的方法呢?
大多数人都会想到的是封装统一网络接口,没错,确实是这样。于是,经过一番思考以后,我们可能写出以下的代码。
void doGet(Context context,String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener); 复制代码
这时候接口可能如下:
public interface NetRequest{ void doGet(Context context,String url, final Map<String, String> paramsMap,final Map<String, String> headMap, String tag,final IResponseListener iResponseListener); --- } 复制代码
那以后如果要配置缓存路径呢,配置请求超时时间,读取超时时间呢,直接在方法中增加相应的参数?
这样的做法是不太明智的,会导致接口越来越臃肿。
既然这样,那有没有办法解决呢?
必要选项
非必要选项
了解完必要参数和非必要参数之后,我们的接口要怎样提取呢?
不知道大家有没有注意到 okHttpClient 的构建,他将所有的网络配置都提取封装在 OkHttpClient,Request 中,在请求网络的时候减少了相应的参数,简洁灵活。
OkHttpClient.Builder mBuilder= new OkHttpClient.Builder(). connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(new LoggingInterceptor()) .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize)); OkHttpClient cilent = mBuilder.build(); Request.Builder builder = new Request.Builder().url(url); client.newCall(builder.build()).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e, iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response, iResponseListener); } }); 复制代码
看了 OKhttp 的代码,我们也可以依样画葫芦,我们可以将非必要参数封装在一个实体类 NetworkOption 当中,必要的参数作为方法参数,这样接口变成以下的形式。
void doGet(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener); void doGet(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener); 复制代码
对比直接在方法中增加相应的参数,是不是简洁很多。
接着,我们一起来看一下 NetworkOption 的属性。基本上,只要 okhttp 可以配置的,我们都可以往里面配置。这里列举了一些常用的字段 ,baseUrl,请求标志 tag,请求头 mHeaders。-connectTimeout 连接超时时间,readTimeout 读取超时时间,writeTimeout 写入超时时间就不一一列举了。
public class NetworkOption { /** * 网络请求的 TAG */ public String mBaseUrl; public String mTag; public Map<String,String> mHeaders; public NetworkOption(String tag) { this.mTag = tag; } public static final class Builder{ public String tag; public Map<String,String> mHeaders; public String mBaseUrl; public Builder setTag(String tag){ this.tag=tag; return this; } public Builder setHeaders(Map<String,String> headers){ mHeaders=headers; return this; } public Builder setBaseUrl(String baseUrl) { mBaseUrl = baseUrl; return this; } public NetworkOption build(){ NetworkOption networkOption = new NetworkOption(tag); networkOption.mHeaders=mHeaders; networkOption.mBaseUrl=mBaseUrl; return networkOption; } } } 复制代码
同时,考虑到 NetworkOption 对象的配置会比较复杂,这里我们采用了建造者模式来构建。有兴趣的话,可以参考我的这一篇博客。 建造者模式(Builder)及其应用
public interface NetRequest { void init(Context context); void doGet(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener); void doGet(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener); void doPost(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener); void doPost(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener); void cancel(Object tag); } 复制代码
可以看到,我们主要有几个方法
OkHttp 的配置是非常灵活的,这样我们主要看一下怎么配置请求头,请求参数,以及怎样取消网络请求。
public class OKHttpRequest implements NetRequest { // ----- 省略若干方法,有兴趣的话上 github 查阅 @Override public void doGet(String url, Map<String, String> paramsMap, final IResponseListener iResponseListener) { doGet(url,paramsMap,null,iResponseListener); } @Override public void doGet(String url, Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener) { url= NetUtils.checkUrl(url); url=NetUtils.appendUrl(url,paramsMap); final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url); Request.Builder builder = new Request.Builder().url(url).tag(option.mTag); builder=configHeaders(builder,option); Request build = builder.build(); getCilent().newCall(build).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e, iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response, iResponseListener); } }); } private Request.Builder configHeaders(Request.Builder builder, NetworkOption option) { Map<String, String> headers = option.mHeaders; if(headers==null || headers.size()==0){ return builder; } Set<Map.Entry<String, String>> entries = headers.entrySet(); for(Map.Entry<String, String> entry: entries){ String key = entry.getKey(); String value = entry.getValue(); // 添加请求头 builder.addHeader(key,value); } return builder; } @Override public void doPost(String url, Map<String, String> paramsMap, final IResponseListener iResponseListener) { doPost(url,paramsMap,null,iResponseListener); } private FormBody.Builder configPushParam(FormBody.Builder builder, Map<String, String> paramsMap) { if(paramsMap!=null){ Set<Map.Entry<String, String>> entries = paramsMap.entrySet(); for(Map.Entry<String,String> entry:entries ){ String key = entry.getKey(); String value = entry.getValue(); builder.add(key,value); } } return builder; } @Override public void doPost(String url, Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener) { url= NetUtils.checkUrl(url); final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url); // 以表单的形式提交 FormBody.Builder builder = new FormBody.Builder(); builder=configPushParam(builder,paramsMap); FormBody formBody = builder.build(); Request.Builder requestBuilder = new Request.Builder().url(url).post(formBody).tag(option.mTag); requestBuilder=configHeaders(requestBuilder,option); Request request = requestBuilder.build(); getCilent().newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e,iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response,iResponseListener); } }); } @Override public void cancel(Object tag) { if(client!=null){ if(client != null) { // 在等待队列中查找是否有相应的请求 for(Call call : client.dispatcher().queuedCalls()) { if(call.request().tag().equals(tag)) call.cancel(); } // 在正在请求的请求队列中查找是否有相应的请求 for(Call call : client.dispatcher().runningCalls()) { if(call.request().tag().equals(tag)) call.cancel(); } } } } } 复制代码
OKHttpRequest 的实现其实很就简单,主要是根据 NetworkOption 做相应的配置,不熟悉 okhttpRequest 的用法的可以参考该博客。OkHttp使用完全教程
VolleyRequest 的实现也不说了,也是根据 NetworkOption 做相应的配置,有兴趣的话可以点击查看 Networklibrary
考虑到项目当中有可能要切换框架,这里我们使用简单工厂模式来实现,方便我们框架的随时切换。
UMl 类图如下
最后考虑到网络加载在项目中是经常用到的,为了节省资源,提高速度,我们结合了单例模式,最终的实现如下:
public class NetManger { private static NetRequest instance; private static Context mContext; public static NetRequest getRequest(){ return instance; } static HashMap<String,NetRequest> mMap=new HashMap<>(); public static void init(Context context){ instance = OKHttpRequest.getInstance(); mContext = context.getApplicationContext(); instance.init(NetManger.mContext); } // 采用反射的形式实现,这样有一个好处是,以后增加新的实现类的话,我们只需要传递相应 的 Class, //而不需要更改 NetManger 的代码 public static <T extends NetRequest> NetRequest getRequest(Class<T> clz){ String simpleName = clz.getSimpleName(); NetRequest request = mMap.get(simpleName); if(request ==null){ try { Constructor<T> constructor = clz.getDeclaredConstructor(); constructor.setAccessible(true); request = constructor.newInstance(); request.init(mContext); mMap.put(simpleName,request); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } instance=request; return request; } } 复制代码
1) NetManger 怎么使用
首先你需要在 Application 中调用 NetManger 的 init 方法
NetManger.init(application); 复制代码
默认的实现是使用 okhttp 实现,采用单例模式
NetManger.getRequest().doGet(url, mMap, new IResponseListener() { @Override public void onResponse(String response) { LogUtil.i(TAG,"onResponse: response ="+response); } @Override public void onFail(HttpException httpException) { Log.i(TAG, "onFail: httpException=" +httpException.toString()); } }); 复制代码
2) NetManger 怎么切换具体的实现
加入我们想切换成 Volley,那么我们在传递参数的时候只需要传递VolleyRequest.class 即可
NetManger.getRequest(VolleyRequest.class).doPost(url, mMap, new IResponseListener() { @Override public void onResponse(String response) { mTv.setText("post 请求/n"+response); LogUtil.i(TAG,"onResponse: response ="+response); } @Override public void onFail(HttpException httpException) { Log.i(TAG, "onFail: httpException=" +httpException.toString()); } }); 复制代码
答案是坑定的,我们只需要自己增加一个实现类 implement NetRequest 接口即可。然后在使用传递参数的时候传递相应的 Class 即可。
NetManger.getRequest(XUtilsRequest.class).doPost(url, mMap, new IResponseListener() { @Override public void onResponse(String response) { mTv.setText("post 请求/n"+response); LogUtil.i(TAG,"onResponse: response ="+response); } @Override public void onFail(HttpException httpException) { Log.i(TAG, "onFail: httpException=" +httpException.toString()); } }) 复制代码
看了上面网络框架的二次封装,对图片框架,json 解析框架的封装,你是不是也想到了什么,懂得怎样封装了吧。
Networklibrary : github.com/gdutxiaoxu/…
观察者设计模式 Vs 事件委托(java)
装饰者模式及其应用
建造者模式(Builder)及其应用
二次封装图片第三方框架——简单工厂模式的运用
Android 二次封装网络加载框架
java 代理模式详解
最后的最后,卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。