您的位置:首页 > 理论基础 > 计算机网络

android http——网络请求二次封装的框架设计

2016-05-17 18:51 1171 查看
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等类)可以使用观察者模式向所有的观察者发出请求返回结果。

接下来我们就一步一步的实现这个二次封装的框架。等到成品出来后,各位看客就能感受到它的简单和易用是多么的强大。

1,准备工作

/**
* 请求的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;

这没有什么可说的。

2,生成默认参数和参数map实例

一般来说,一个网络请求必须带的几个参数:

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;
}
}


3,定义网络请求方法接口

/**
* 该接口规定所有请求所需的常量和方法
*/
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);
}


一般也就这些方法,如果有更多方法也没关系。

这个接口先放在这里,暂且最后在实现具体的方法。

4,请求参数封装

上面我说到,请求的参数无非包含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数据解析能够更具这个类来解析。

后面的代码就可以看出它的作用。

5,返回结果封装

/**
* 返回结果抽象类,工厂方法模式
*/
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 则是接下来要讲的。

6,发出请求的枚举

/**
* 发出请求的枚举
*/
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就能够得到参数集合,就能够发出请求。

7,继承网络请求接口的具体实现

/**
* 具体的请求方法
*/
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()方法给接上去就可以照常使用

这也是二次封装网络请求而设计框架的最重要的目的。

这里写成博客以供大家前来学习。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: