android app中少不了要用到网络请求。网上已经有很多优秀的网络请求框架,谷歌推荐的okhttp、齐射并发的volley 、异步请求的asynchttp。
但是我要说的并不是重新设计网络请求框架,而是基于所有优秀网络请求框架进行的二次封装,成为一个为自己项目所用的网络请求。
不要问为什么这么做,只要你使用到了网络请求,不管有没有用第三方请求框架都必须为它(网络请求流程处理)再次设计一个简单易用的框架,我们把这个过程叫做网络请求的二次封装。
先来看看一个网络请求的流程:
1,get,post请求数据
请求数据需要url,如果是post请求还需要一个map集合在存储请求参数;
那么就可以把请求数据看作:
String url="http://192.168.1.1:8080/websservice/index/selectindex.action?";
Map<String,Object> parameters=new HashMap();
parameters.put("userinfo.name","admin");
parameters.put("userinfo.password","admins1");
这么一个数据结构。
2,发出请求
private void sendPostRequest(String url, Map params) {
//首先判断是否有网络
if (!NetWorkUtil.isNetworkConnected(mContext)) return;
HttpClient.post(Context, url, params, new HttpBack() {
@Override
public void onSuccess(int statusCode, byte[] responseBody) {
try {
String jsonSuccess = new String(responseBody, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(int statusCode, byte[] responseBody, Message error) {
try {
String jsonFinal = new String(responseBody, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
}
});
这段代码只是模拟一个post网络请求,并且经过一系列的处理返回成功或者失败到相应的接口。
3,处理结果
拿到结果后 我们一般会根据服务器返回的固定字段来解析json数据:
//状态码
String TAG_STATUS_CODE = "statusCode";
//请求失败后的失败内容
String TAG_MESSAGE = "message";
//json对象
String TAG_OBJECT = "object";
//json集合
String TAG_OBJECTS = "objects";
通过和服务器返回数据相匹配来解析json并保存到相应的实体类中。
4,显示结果
当我们拿到处理后的结果后要发送给相应的页面用于显示。这时候的处理就“八仙过海各显神通”了。不过最常见的就是通过观察者模式,让结果进入发送者内,然后给所有观察者发送网络请求的处理结果。
这就是一个网络请求的大致流程。其中具体的网络请求网上就有很多优秀的处理框架了。我们也没有必要再去自己写,况且自己写的不一定完美。
所以,我们要做的就是既要用别人的框架也要将别人的框架和自己app的耦合性降到最低。
这就是大概的一个接口对接的示意图。
而我们要做的部分就是将红色框,即app内部请求和解析进行二次封装。
—————————————————分割线———————————————–
那么我要说的就是为网络请求流程处理设计的框架。
得到处理过的结果的ResponseResult类(也就是图中的ResponseSuccess等类)可以使用观察者模式向所有的观察者发出请求返回结果。
接下来我们就一步一步的实现这个二次封装的框架。等到成品出来后,各位看客就能感受到它的简单和易用是多么的强大。
/**
* 请求的url
*/
public class RequestUrls {
public static final String IP = "http://192.168.1.1:8080/";//url
public static final String ROOT_URL = IP + "HttpService/";//项目名
/**
* 获取首页数据的URL
*/
public static final String GET_INDEX_CONTENT_URL = ROOT_URL + "index/selectIndexContent.action";
}
/**
* 服务器返回状态吗
*/
public class StatusCode {
/**
* 状态码:请求成功
*/
public static final int REQUEST_SUCCESS = 200;
/**
* 状态码:没有更多数据
*/
public static final int NOT_MORE_DATA = 201;
/**
* 状态码:服务器繁忙
*/
public static final int SERVER_BUSY = 300;
/**
* 状态码:未知错误
*/
public static final int SERVER_ERROR = 500;
}
还有MD5Util、Base64、NetWorkUtil;
这没有什么可说的。
一般来说,一个网络请求必须带的几个参数:
1,app key;服务器与前端使用同一个名字来匹配。
2,时间戳;目的是为了控制这个请求在一个时间范围内有效。例如:两分钟。如果超过了两分钟,还在用这个请求请求服务器那就不会成功。
3,接口版本号;升级专用。前端app升级后为了兼容后台接口而定制的。
4,sign(md5加密);这个加密方式每个人有每个人的做法。我的做法是将所有的请求参数加起来生成一个md5密文然后后台拿到我的请求参数后同样的方法生成一个md5密文然后匹配。
这些参数应该和服务器一起去协商定义。
那么有了这些写死的,每次请求都要有的参数我们就可以提前实例化一个map先把他们存起来。
/**
* 生成默认参数和参数map实例
*/
public class SignUtils {
public static final String KEY_SIGN = "sign";
public static final String KEY_PRIVATE = "key";
public static final String KEY_TIMESTAMP = "timestamp";
public static final String KEY_VERSION = "version";
public static String getSignMD5(Map param) {
Collection keySet = param.keySet();
List list = new ArrayList<>(keySet);
//对key键值按字典升序排序
Collections.sort(list);
String paramStr = "";
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals(KEY_SIGN)) continue;
paramStr += list.get(i) + "=" + param.get(list.get(i)) + "&";
}
if (!TextUtils.isEmpty(paramStr))
paramStr = paramStr.substring(0, paramStr.length() - 1);
return MD5.MD5(paramStr);
}
public static Map getParameters() {
Map params = new HashMap<>();
params.put(KEY_PRIVATE, "alsfoxShop_plat");
params.put(KEY_VERSION,"1.01");
params.put(KEY_TIMESTAMP, System.currentTimeMillis());
return params;
}
}
/**
* 该接口规定所有请求所需的常量和方法
*/
public interface IRequest {
//状态码
String TAG_STATUS_CODE = "statusCode";
//请求失败后的失败内容
String TAG_MESSAGE = "message";
//json对象
String TAG_OBJECT = "object";
//json集合
String TAG_OBJECTS = "objects";
//默认超时时间
int VALUE_DEFAULT_TIME_OUT = 20 * 1000;
/**
* 发送get请求
*/
void sendGetRequest(RequestAction action);
/**
* 发送post请求,包含多文件上传方式的传文件
*
* @param action 请求对象
*/
void sendPostRequest(RequestAction action);
/**
* 取消所有请求,可能中断请求
*/
void cancelAllRequests(boolean mayInterruptIfRunning);
/**
* 重新设置请求超时时间
*/
void setTimeOut(int value);
/**
* 下载文件
*/
void downloadFile(String url);
}
一般也就这些方法,如果有更多方法也没关系。
这个接口先放在这里,暂且最后在实现具体的方法。
上面我说到,请求的参数无非包含rul还有参数的map集合。
这里我们也做一下封装。
/**
* 外界将请求的地址,请求结果的解析参照对象以及请求参数都保存到这个类中
*/
public class RequestContent {
/**
* 发送请求的路径
*/
private String requestUrl;
/**
* 请求结果的解析参照对象(服务器返回的数据类型)
*/
private Class> cls;
/**
* 请求参数,new出一个map对象,将默认的先添加进去
*/
private Map parameters = SignUtils.getParameters();
/**
* 外界传入参数的构造方法,没有返回结果的请求
*/
public RequestContent(String requestUrl) {
this.requestUrl = requestUrl;
}
/**
* 外界传入参数的构造方法,带返回结果的请求,需要传入一个对象依照它来解析数据
*/
public RequestContent(String requestUrl, Class> cls) {
this.requestUrl = requestUrl;
this.cls = cls;
}
//还有变量的get/set方法,这里就不贴出来了
……
}
需要注意的是,请求参数的封装中貌似多了一个class参数。其实这个参数也算是请求参数的一个。
这个参数的作用是当服务器返回了成功后的结果,json数据解析能够更具这个类来解析。
后面的代码就可以看出它的作用。
/**
* 返回结果抽象类,工厂方法模式
*/
public abstract class ResponseResult {
//请求对象
protected RequestAction requestAction;
//状态码
protected int statusCode;
}
抽象类需要三个方法继承。成功,失败和所有。类似于try/catch/finally方法。
/**
* 成功得到服务器结果后,所有的参数进入这个方法;
*/
public class ResponseSuccess extends ResponseResult {
//服务器返回的内容
private Object content;
public Object getContent() {
return content;
}
public void setContent(Object content) {
this.content = content;
}
}
/**
* 失败得到服务器结果后,所有的参数进入这个方法
*/
public class ResponseFailure extends ResponseResult {
private String message;//错误信息
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
/**
* 不管服务器的结果是成功还是失败,所有的参数进入这个方法
*/
public class ResponseComplete extends ResponseResult {
/**
* 不管成功或者失败都会进入的方法
* 类似于try/catch方法中的finally方法
*/
//服务器返回的内容
private Object content;
private String message;//错误信息
public Object getContent() {
return content;
}
public void setContent(Object content) {
this.content = content;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
这样一来,
成功的请求进ResponseSuccess;这个类中就包含了:对象,状态码和内容;
失败的请求进ResponseFailure;这个类中就包含了:对象,状态码和错误信息;
不管失败和成功都要进ResponseComplete;这个类中就包含了:对象,状态码、内容和错误信息;当然,它的参数会因为状态不同而出现null。
至于抽象类中的RequestAction 则是接下来要讲的。
/**
* 发出请求的枚举
*/
public enum RequestAction {
/**
* 枚举中实例化一个请求对象,并传入请求的url,如果该请求有返回值则还需要传入解析参照对象
*/
GET_INDEX_CONTENT(new RequestContent(RequestUrls.GET_INDEX_CONTENT_URL, String.class)),;
/**
* 构造器中传入请求对象
*/
public RequestContent requestContent;
RequestAction(RequestContent requestContent) {
this.requestContent = requestContent;
}
}
通过带有构造器的枚举在每一个枚举中都实例化一个请求内容(RequestContent )对象。
这样,当我们调用每一个枚举的时候都会得到一个RequestContent的对象,于此同时也就得到了RequestContent对象中的请求参数map集合实例(getParameters())。
有了请求参数实例我们就可以不断的往里面添加参数。这样做的目的是把填写url,给json数据一个解析参照对象(String.class)以及实例化一个参数集合的工作都交给枚举来完成。
这样,我们只需要调用枚举,传入url和数据解析参照对象的class就能够得到参数集合,就能够发出请求。
/**
* 具体的请求方法
*/
public class Requester implements IRequest {
//因为这里继承了接口所以不能通过枚举来实现单例了
private static volatile IRequest instance = null;
public synchronized static IRequest getInstance() {
if (instance == null) {
synchronized (IRequest.class) {
if (instance == null) {
instance = new Requester();
}
}
}
return instance;
}
private EventBus eventBus;
private Gson gson;
private Requester() {
//eventbus是一个三方工具库,可以代替观察者模式发送网络请求返回结果。如果不会用这个工具库,可以百度,或者自己换成可以处理返回结果的代码;例如接口,例如观察者模式
eventBus = EventBus.getDefault();
//gson是一个三方json数据解析工具库。
gson = new Gson();
}
//解析成功的参数
private ResponseResult createResponseSuccess(int statusCode, String response, RequestAction requestAction) {
ResponseSuccess responseSuccess = null;
try {
JSONObject jsonObject = new JSONObject(response);
responseSuccess = new ResponseSuccess();
responseSuccess.setStatusCode(statusCode);
responseSuccess.setRequestAction(requestAction);
if (requestAction.getClass() == null) return responseSuccess;
if (jsonObject.has(TAG_OBJECT)) {//如果json数据中包含object,说明是一个对象
Object object = gson.fromJson(jsonObject.getString(TAG_OBJECT), requestAction.getClass());//这里用到的是gson的解析数据方法
responseSuccess.setContent(object);
} else if (jsonObject.has(TAG_OBJECTS)) {//如果json数据中包含objects,说明是一个对象集合
JSONArray array = jsonObject.getJSONArray(TAG_OBJECTS);
List
其中HttpClient是模拟代码。它指你使用的一切网络请求框架。
因为框架的设计无法用具体代码表达出来,所以这里我选择使用模拟逻辑处理过程。
到这里,整个框架的设计就完成了。那么我们应该怎么去使用呢?
使用网络请求:
//使用方法
public void test() {
//实例化请求方法
IRequest request = Requester.getInstance();
//post请求
//通过枚举获得请求参数的map实例
Map params = RequestAction.GET_INDEX_CONTENT.requestContent.getParameters();
//添加参数
params.put("userinfo.name", "admin");
params.put("userinfo.pwd", "admins1");
//发送网络请求
request.sendPostRequest(RequestAction.GET_INDEX_CONTENT);
//get请求
request.sendGetRequest(RequestAction.GET_INDEX_CONTENT);
}
不管你在哪里提交网络请求,都只需要这么几行代码就搞定。
那么我们再来看如何添加新的rul:
首先在RequestUrls类中添加一个url:
public static final String GET_USER_CONTENT_URL = ROOT_URL + "index/selectIndexContent.action";
然后在RequestAction枚举中添加一个新枚举:
GET_USER_CONTENT(new RequestContent(RequestUrls.GET_USER_CONTENT_URL, UserInfoBean.class)),
这样,添加一个新的请求就完成了。
不管是提交请求,还是添加新请求都是几行代码就解决的问题。
那么我们如何去接收返回结果呢?
不管你是用的是eventbus.jar还是接口还是观察者模式得到返回结果后都可以这样处理:
//成功
public void onRequestSuccess(ResponseSuccess success) {
switch (success.getRequestAction()) {
case GET_INDEX_CONTENT:
success.getContent();//内容
success.getStatusCode();//返回码
success.getRequestAction();//请求枚举
success.getClass();//参照的解析类型
break;
}
}
//失败
public void onRequestFailure(ResponseFailure failure) {
switch (failure.getRequestAction()) {
case GET_INDEX_CONTENT:
failure.getMessage();//错误信息
failure.getStatusCode();//返回码
failure.getRequestAction();//请求枚举
failure.getClass();//参照的解析类型
break;
}
}
//所有
public void onRequestCompleted(ResponseComplete complete) {
switch (complete.getRequestAction()) {
case GET_INDEX_CONTENT:
//注意:解析的内容可能为null,因为成功或者失败都会进来
complete.getContent();//内容,可能为null
complete.getMessage();//错误信息,可能为null
complete.getStatusCode();//返回码
complete.getRequestAction();//请求枚举
complete.getClass();//参照的解析类型
break;
}
}
拿到结果后就可以自己处理一些逻辑了。
整个二次封装的流程就是这样。
这个二次封装的框架目的在于简化请求接收代码,并且留下接口(onSuccess,onFailure)以供第三方框架使用。
现在再返回来看这张图:
我们二次封装网络请求的目的就是做红色的框所做的工作。并且把整个网络请求流程分开来,解耦。
就像几个楔形:服务器<第三方框架<二次封装框架<数据请求;
这样才能构成一个完整的app网络请求流程。而且这样分为几个楔形,成功的达到了解耦的作用。
这样,不管你换哪个第三方请求框架,只要把第三方请求框架的onSuccess()和onFailure()方法给接上去就可以照常使用。
这也是二次封装网络请求而设计框架的最重要的目的。
这里写成博客以供大家前来学习。