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<String, Object> 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<String, Object> param) {
Collection<String> keySet = param.keySet();
List<String> 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<String, Object> getParameters() {
Map<String, Object> 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<String, Object> 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<Object> data = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
data.add(gson.fromJson(array.getString(i), requestAction.getClass()));
}
responseSuccess.setContent(data);
}
} catch (Exception e) {
e.printStackTrace();
}
return responseSuccess;
}
//解析失败的参数
private ResponseResult createResponseFailure(int statusCode, String message, RequestAction requestAction) {
ResponseFailure responseFailure = new ResponseFailure();
responseFailure.setStatusCode(statusCode);
responseFailure.setMessage(message);
responseFailure.setRequestAction(requestAction);
return responseFailure;
}
//不管成功还是失败都进该方法进行解析
private ResponseResult createResponseComplete(int statusCode, String responseString, RequestAction requestAction) {
ResponseComplete responseComplete = new ResponseComplete();
responseComplete.setStatusCode(statusCode);
responseComplete.setMessage(responseString);
responseComplete.setRequestAction(requestAction);
return responseComplete;
}
/** * post请求 */
@Override
public void sendPostRequest(final RequestAction action) {
if (!NetWorkUtil.isNetworkConnected(UIAppliaction.getInstance())) return;
if (action.requestContent.getParameters() == null) {
HttpClient.get(action.requestContent.getRequestUrl(), httpBackState(action));
} else {
HttpClient.post(action.requestContent.getRequestUrl(), action.requestContent.getParameters(), httpBackState(action));
}
}
//服务器返回结果处理
private void httpBackState(final RequestAction action) {
//该方法是网络请求框架中的回调方法,这里只是模拟代码
new HttpBack() {
@Override
public void onSuccess(int statusCode, byte[] responseBody) {
try {
String jsonSuccess = new String(responseBody, "UTF-8");
ResponseResult success = createResponseSuccess(statusCode, jsonSuccess, action);
ResponseResult complete = createResponseComplete(statusCode, null, action);
//给发送了网络请求的类返回请求结果
eventBus.post(success);
eventBus.post(complete);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(int statusCode, byte[] responseBody, String error) {
try {
ResponseResult failure = createResponseFailure(statusCode, error, action);
ResponseResult complete = createResponseComplete(statusCode, error, action);
//给发送了网络请求的类返回请求结果
eventBus.post(failure);
eventBus.post(complete);
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
/** * get请求 */
@Override
public void sendGetRequest(final RequestAction action) {
sendPostRequest(action);
}
/** * 取消所有请求 * * @param mayInterruptIfRunning */
@Override
public void cancelAllRequests(boolean mayInterruptIfRunning) {
HttpClient.cancelAllRequests(mayInterruptIfRunning);
}
/** * 设置超时时间 * * @param value 毫秒 */
@Override
public void setTimeOut(int value) {
HttpClient.setOutTime(value);
}
/** * 下载文件 * * @param url */
@Override
public void downloadFile(String url) {
//模拟下载文件
HttpClient.downloadFile(url, new HttpFileBack() {
@Override
public void onStart(int statusCode, byte[] responseBody) {
}
@Override
public void onOngoing(int statusCode, byte[] responseBody, double schedule) {
}
@Override
public void onError(int statusCode, byte[] responseBody, String message) {
}
});
}
}
其中HttpClient是模拟代码。它指你使用的一切网络请求框架。
因为框架的设计无法用具体代码表达出来,所以这里我选择使用模拟逻辑处理过程。
到这里,整个框架的设计就完成了。那么我们应该怎么去使用呢?
使用网络请求:
//使用方法
public void test() {
//实例化请求方法
IRequest request = Requester.getInstance();
//post请求
//通过枚举获得请求参数的map实例
Map<String, Object> 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()方法给接上去就可以照常使用。
这也是二次封装网络请求而设计框架的最重要的目的。
这里写成博客以供大家前来学习。