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

技术实现之http请求封装

2014-09-03 22:27 736 查看
        前面的2篇文章对现有工作项目进行了一个简要的介绍,从这篇文章开始,将会对现在项目中用到的开发模式进行详细的总结和概括。

如题,本篇文章将总结项目中http请求的封装。

       由于公司的api接口的特殊设计的缘故,为了使得在开发的工程中不至于浪费太多的时间在http请求编写上,所以我对http请求进行了封装。

1、接口的调用方式

curl http://192.168.1.23:9000/dataSearch -H "Content-Type:application/json;charset:utf-8" -d '{"ReqHeader":{"appId" : "257741","appVersion" : "0.1","deployId" : "257741","sessionId" : "1234567890","appKey" : "1cc201ee-a182-9d1b-8d94-ce1f3dec0661","uHelpVer" : "1.2","reqTime" : "2013-08-21 09:00:00"},"action":"littleGameImeiLogin","params":{"imei":"121231.com","lat":31.24312312,"lon":121.50912342}}'


     所有的api接口都有一个公共的入口 http://192.168.1.23:9000/dataSearch ,ReqHeader部分是特殊的格式,基本上是固定的,而各个接口之间是通过其中的action参数来进

行划分的,而params则表示客户端部分需要向服务端传递的参数,可以理解为get请求中?后面的参数集合。而返回的格式如下

 {
<span style="white-space:pre">	</span>"RespHeader": {
<span style="white-space:pre">	</span>    "hostId": null,
<span style="white-space:pre">	</span>    "result": "success",
<span style="white-space:pre">	</span>    "errorCode": "0",
<span style="white-space:pre">	</span>    "errorNote": "成功",
<span style="white-space:pre">	</span>    "respTime": "2014-06-30 10:43:03"
<span style="white-space:pre">	</span>},
"RespBody": {
<span style="white-space:pre">	</span>    "rtnCode": 0
<span style="white-space:pre">	</span>}
}


其中 RespHeader是头部信息,客户端调用成功与否是通过RespBody中的rtnCode字段来进行判断的,0表示成功,其余的都表示失败。

2、http请求的封装底层基类

客户端的http请求是采用AsynTask来调用的,所有的http请求都有一个公共的基类DataSearchApiCallAsynTask,代码如下

package com.uwen.melo.themonkey.http;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;

import android.os.AsyncTask;
import android.util.Log;

import com.uwen.melo.themonkey.common.ActionConstant;
import com.uwen.melo.themonkey.model.common.BasicJsonVo;
import com.uwen.melo.themonkey.util.CommonUtil;

public class DataSearchApiCallAsynTask extends
AsyncTask<String, Integer, String> {
private OnDataSearchApiCompleted listener;
private String action;
private Map<String, Object> params;
// private String result;
HttpClient httpclient;

public DataSearchApiCallAsynTask(OnDataSearchApiCompleted listener,
String action, Map<String, Object> params) {
this.listener = listener;
this.action = action;
this.params = params;
}

protected void onProgressUpdate(Integer... progress) {
// update progress here
}

@Override
protected String doInBackground(String... requestParams) {
int len = requestParams.length;

String url = requestParams[0];
Log.i("action=====>", action);
Log.d("params=====>", params.toString());
httpclient = new DefaultHttpClient();

// 设置超时
int connectOverTime = 2000;
int requestOverTime = 5000;
if (len >= 3) {
if (requestParams[1] != null) {
connectOverTime = Integer.parseInt(requestParams[1]); // 链接超时时间
}
if (requestParams[2] != null) {
requestOverTime = Integer.parseInt(requestParams[2]); // 链接超时时间
}
}

httpclient.getParams().setIntParameter(HttpConnectionParams.SO_TIMEOUT,connectOverTime); // 超时设置
httpclient.getParams().setIntParameter(HttpConnectionParams.CONNECTION_TIMEOUT, requestOverTime);// 连接超时
HttpPost post = new HttpPost(url);
Map<String, String> reqHeader = getReqHeader();
Map<String, Object> mp = new HashMap<String, Object>();
mp.put("ReqHeader", reqHeader);
mp.put("action", action);
mp.put("params", params);

String reqHeaderJSonStr = new JSONObject(reqHeader).toString();
String paramsJsonStr = new JSONObject(params).toString();
String actionJsonStr = "\"" + action + "\"";
String reqJson = "{" + "\"ReqHeader\" : " + reqHeaderJSonStr
+ ", \"params\" : " + paramsJsonStr + ", \"action\" : "
+ actionJsonStr + "}";
String resultStr = null;
try {
JSONObject jsonObj = new JSONObject(reqJson);
StringEntity se = new StringEntity(jsonObj.toString(), "UTF-8");
se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
"application/json;charset=utf-8"));

post.addHeader("Content-Type", "application/json;charset=utf-8");
post.setEntity(se);
HttpResponse response = httpclient.execute(post);
// int code = response.getStatusLine().getStatusCode();
resultStr = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (UnsupportedEncodingException e) {
System.out.println("连接超时UnsupportedEncodingException");
resultStr = CommonUtil.getBasicHttpJsonStr(
ActionConstant.CONNECT_FAILED,
"连接错误UnsupportedEncodingException");
} catch (ClientProtocolException e) {
System.out.println("连接超时ClientProtocolException");
resultStr = CommonUtil.getBasicHttpJsonStr(
ActionConstant.CONNECT_TIME_OUT, "请求超时");
} catch (IllegalStateException e) {
System.out.println("进入了连接错误IllegalStateException");
resultStr = CommonUtil.getBasicHttpJsonStr(
ActionConstant.CONNECT_FAILED, "连接错误IllegalStateException");
e.printStackTrace();
} catch (JSONException e) {
System.out.println("进入了连接错误JSONException");
resultStr = CommonUtil.getBasicHttpJsonStr(
ActionConstant.CONNECT_FAILED, "连接错误JSONException");
} catch (IOException e) {
System.out.println("进入了连接错误IOException");
resultStr = CommonUtil.getBasicHttpJsonStr(
ActionConstant.CONNECT_FAILED, "连接错误IOException");
}
return resultStr;
}

// called after doInBackground finishes
@Override
protected void onPostExecute(String result) {
try {
// System.out.println("onPostExecute result=====>"+result);
JSONObject jsonObject = new JSONObject(result);
if (!jsonObject.has("state")) {
jsonObject.put("state", ActionConstant.CONNECT_OK);
}
listener.onDataTaskInvokeCompleted(jsonObject);

} catch (JSONException e) {
JSONObject errJson = null;
try {
/**
{
"RespHeader": {
"hostId": null,
"result": "success",
"errorCode": "0",
"errorNote": "成功",
"respTime": "2014-06-30 10:43:03"
},
"RespBody": {
"rtnCode": 0
}
}
*/
// 构造一个错误的Json格式信息
String errorStr = " {	  \"RespHeader\": {	    \"hostId\": null,	    \"result\": \"failure\",	    \"errorCode\": \"-1111\",	    \"errorNote\": \""
+ result
+ "\",	    \"respTime\": \"2014-06-30 10:43:03\"	  },	  \"RespBody\": {	    \"rtnCode\": -1111	  }   }";

errJson = new JSONObject(errorStr);
} catch (JSONException e1) {
e1.printStackTrace();
}
// 错误json回调
listener.onDataTaskInvokeCompleted(errJson);
}
}

public static void testJSON() {
BasicJsonVo basicJson = new BasicJsonVo(200, "成功");
try {
System.out.println("basic json====>" + basicJson.toString());
JSONObject obj = new JSONObject(basicJson.toString());
System.out.println("测试的json====>" + obj);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public static Map<String, String> getReqHeader() {
Map<String, String> reqHeader = new HashMap<String, String>();
reqHeader.put("appId", "257741");
reqHeader.put("appVersion", "0.1");
reqHeader.put("deployId", "257741");
reqHeader.put("sessionId", "1234567890");
reqHeader.put("appKey", "1cc201ee-a182-9d1b-8d94-ce1f3dec0661");
reqHeader.put("uHelpVer", "1.2");
reqHeader.put("reqTime", null);
return reqHeader;
}

/**
* 取消网络请求
*/
public void shutdownHttpClient() {

if (httpclient != null) {
// 这个shutdown并不是将手机网络断掉,而是将建立Http连接请求时所分配的资源释放掉
httpclient.getConnectionManager().shutdown();
}

}

}


具体单个接口的调用则是需要new 一个OnDataSearchApiCompleted 回调函数对象,其代码如下

package com.uwen.melo.themonkey.http;

import org.json.JSONObject;

public interface OnDataSearchApiCompleted{
/**
* 执行异步线程回调之后的方法
* @param result
*/
void onDataTaskInvokeCompleted(JSONObject result);
}



3、http请求的封装类

      开始封装上面这个基类时候,因为没有考虑到实际中的调用情况,所以在实际调用的时候有很多重复多余的代码,基于这一点,我再一次对调用进行了封装,新增一个

Abstract类,代码如下

package com.uwen.melo.themonkey.views.creates.impl.base;

import java.util.Map;

import org.json.JSONObject;

import android.content.Context;

import com.uwen.melo.themonkey.common.ActionConstant;
import com.uwen.melo.themonkey.common.CommonInfo;
import com.uwen.melo.themonkey.http.DataSearchApiCallAsynTask;
import com.uwen.melo.themonkey.http.OnDataSearchApiCompleted;
import com.uwen.melo.themonkey.parse.ParseJsonServcie;
import com.uwen.melo.themonkey.util.CommonUtil;
import com.uwen.melo.themonkey.util.ViewUtil;
import com.uwen.melo.themonkey.views.exceptions.RuntimeMessageException;
import com.uwen.melo.themonkey.views.interfaze.IViewBuilder;
import com.uwen.melo.themonkey.views.interfaze.SimpleCallback;

/**
* 所有网络请求解析的基类
* @author liao
*
*/
public abstract class AbstractInvokeApiBuilder implements IViewBuilder{

@Override
public void build() {
init();
}

@Override
public void init() {
invoke();
}

@Override
public void release() {

}

/**
*api的入口
* @param finalRp
*/
public void invoke() {
final Map<String, Object> params = buildMap();
this.doBusinessByNet(new SimpleCallback() {
@Override
public void onDone(int state, Object result) {

//如果是离线登录
if(CommonInfo.isWithoutWifi()) {

handleOtherRtnCode(ActionConstant.WITHOU_TWIFI);
return;
}

try{
if(ActionConstant.CONNECT_OK == state){
try {
doParseResponse(result);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}else if(ActionConstant.CONNECT_TIME_OUT == state){
throw new RuntimeMessageException("网络连接超时");
}else{
throw new RuntimeMessageException("未知异常,请联系管理员");
}
}catch(Exception e){
ViewUtil.handlerNormalException(getBusinessContext(),e);
}
}
},params);
}

/**
* 解析返回的参数
* @param result
* @throws Exception
*/
private void doParseResponse(Object result) throws Exception {
if(!(result instanceof JSONObject)){
responseEmptyData();
}
JSONObject jsonObj = (JSONObject)result;
int rtnCode = jsonObj.getInt("rtnCode");
System.out.println("current RtnCode ===>"+rtnCode);
if (rtnCode == 0) {  //增加成功
handleSuccessRtnCode(jsonObj);
}else{
handleOtherRtnCode(rtnCode);
}
}

/**
* 调用接口的网络操作
* @param finishCallbcak
* @param params
*/
private void doBusinessByNet(final SimpleCallback finishCallbcak, Map<String, Object> params) {
try {
new DataSearchApiCallAsynTask(new OnDataSearchApiCompleted() {
@Override
public void onDataTaskInvokeCompleted(JSONObject result) {
int state = -1;
JSONObject jsonObj = null;
try {
state = result.getInt("state");
if (state == ActionConstant.CONNECT_OK) {
jsonObj = ParseJsonServcie.getResponBodyJson(result);
}
} catch (Exception e) {
state = -1;
} finally{
if(null != finishCallbcak){
finishCallbcak.onDone(state, jsonObj);
}
}
}

}, getActionName(), params)
.execute(
CommonUtil.getDataSearchUrl(getBusinessContext()),
ActionConstant.DEFAULT_CONNECT_OVER_TIME,
ActionConstant.DEFAULT_REQUEST_OVER_TIME
);
} catch (Exception e) {
if(null != finishCallbcak){
finishCallbcak.onDone(-1, null);
}
}
}
/**
* 获取context对象,必须重写
* @return
*/
public abstract Context getBusinessContext();
/**
*请求的参数 ,必须重写
* @return
*/
public abstract Map<String, Object> buildMap() ;

/**
* action的名字,必须重写
* @return
*/
public abstract String getActionName();
/**
* 返回空数据的处理,可以不做任何处理
*/
public abstract void responseEmptyData();

/**
* 处理正确返回时候的json数据,必须重写
* @param jsonResult
*/
public abstract void handleSuccessRtnCode(JSONObject jsonResult);

/**
* 处理其他返回码的数据,必须重写
* @param jsonResult
*/
public abstract void handleOtherRtnCode(int rtnCode);

}


    有了以上的基类,在实际写新的接口的时候只需要直接继承这个抽象类,并实现该类中后面的6个抽象方法就可以,接口调用代码真正的精简了不少,看起来也更加舒服。下

面将通过一个实际的例子来调用上面封装好的请求,其中IViewBuilder接口的代码如下

package com.uwen.melo.themonkey.views.interfaze;

public interface IViewBuilder {
void build();
void init();
void release();
}


4、接口调用实例

   以下通过编写登录接口来,理解上面的封装。登录的基本调用接口类代码,InvokeUserLoginApiBuilder.java

</pre><p></p><pre>
</pre><pre name="code" class="plain">package com.uwen.melo.themonkey.views.creates.impl.user;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Context;

import com.uwen.melo.themonkey.common.ActionConstant;
import com.uwen.melo.themonkey.common.CommonInfo;
import com.uwen.melo.themonkey.common.Constant;
import com.uwen.melo.themonkey.model.user.LoginApiRtnVO;
import com.uwen.melo.themonkey.util.CommonUtil;
import com.uwen.melo.themonkey.views.creates.impl.base.AbstractInvokeApiBuilder;
import com.uwen.melo.themonkey.views.interfaze.IFinishInvokeCallback;

/**
* 调用游戏初始化接口
* @author liao
*
*/
public class InvokeUserLoginApiBuilder extends AbstractInvokeApiBuilder {

private Activity activity;
private IFinishInvokeCallback callback;
private String userName;
private String passwrod;
public InvokeUserLoginApiBuilder(){

};

public InvokeUserLoginApiBuilder(IFinishInvokeCallback callback,Activity activity, String nickName,String password){
this.callback = callback;
this.activity= activity;
this.userName= nickName;
this.passwrod = password;
}

@Override
public void responseEmptyData() {

}

@Override
public void handleSuccessRtnCode(JSONObject jsonResult) {
doSuccesBusiness(jsonResult);
}

@Override
public void handleOtherRtnCode(int rtnCode) {
doFailBusiness(rtnCode);
}

@Override
public Context getBusinessContext() {
return activity;
}

@Override
public Map<String, Object> buildMap() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("userName", this.userName);
map.put("password", this.passwrod);
return map;
}

@Override
public String getActionName() {
return ActionConstant.LITTLE_GAME_ACTION_USER_LOGIN;
}

/*"10001":{"result":"falue","errorCode":"10001","errorNote":"传入的用户id非法","rtnCode":10001},
"10002":{"result":"falue","errorCode":"10002","errorNote":"传入的用户昵称非法","rtnCode":10002},
"10004":{"result":"falue","errorCode":"10004","errorNote":"用户不存在","rtnCode":10004},
"10005":{"result":"falue","errorCode":"10005","errorNote":"密码错误","rtnCode":10005},
"11001":{"result":"falue","errorCode":"11001","errorNote":"用户名过长","rtnCode":11001},
"11002":{"result":"falue","errorCode":"11002","errorNote":"密码不能少于6位","rtnCode":11002},
"-1":{"result":"falue","errorCode":"-1","errorNote":"查询数据出错","rtnCode":-1},
"0":{"result":"success","errorCode":"","errorNote":"登录成功","rtnCode":0}
*/
private void doFailBusiness(int rtnCode) {
String msg = "";
if(rtnCode==10001|| rtnCode ==10002){
msg ="客户端传入参数异常";
}else if(rtnCode==10004){
msg ="用户不存在";
}else if(rtnCode==10005){
msg ="密码错误";
}else if(rtnCode==11001){
msg = "用户名过长";
}else if(rtnCode==11002){
msg = "密码不能少于6位";
}else{
msg = "服务端查询数据出错";
}
callback.onFail(msg);
}

/**
* 成功之后的处理
* @param jsonResult
*/
private void doSuccesBusiness(JSONObject jsonResult) {
try {
JSONObject  userInfo  = jsonResult.getJSONObject("UserInfo");
LoginApiRtnVO vo =null;
if(userInfo!=null){
vo = new LoginApiRtnVO();
int tmp = CommonUtil.getJSONKeyIntValue(userInfo, "goldNum",Constant.NUMBER_ZERO);
vo.setGoldNum(tmp);
tmp = CommonUtil.getJSONKeyIntValue(userInfo, "diamondNum",Constant.NUMBER_ZERO);
vo.setDiamondNum(tmp);
tmp = CommonUtil.getJSONKeyIntValue(userInfo, "level",Constant.NUMBER_ZERO);
vo.setLevel(tmp);
vo.setUserId(CommonInfo.USER_ID);
String nickName =  CommonUtil.getJSONKeyValue(userInfo, "nickName",Constant.DEFAULT_EMPTY_STRING_2);
vo.setNickName(nickName);
String signature = CommonUtil.getJSONKeyValue(userInfo, "personalSignature", Constant.DEFAULT_EMPTY_STRING_2);
if(StringUtils.isEmpty(signature)){
signature = "这家伙很懒,什么都没留下!";
}
vo.setPersonalSignature(signature);
int continueLoginDays = CommonUtil.getJSONKeyIntValue(jsonResult, "continueLoginDays",Constant.NUMBER_ZERO);
vo.setContinueLoginDays(continueLoginDays);
}
callback.onSuccess(vo);
} catch (JSONException e) {
e.printStackTrace();
}
}
}



这个类中最重要的一步是定义了一个IFinishInvokeCallback 回调对象,通过该回调对象的方法,客户端可以自行的处理实际的业务逻辑了,其代码如下

package com.uwen.melo.themonkey.views.interfaze;

/**
* 调用接口完成之后的回调接口
* @author user
*/
public interface IFinishInvokeCallback {
void onSuccess(Object object);
void onFail(Object object);
}

     其中就只定义了处理成功和处理失败2中情况的方法,之所以onSuccess和onFail方法中的输入参数定义为Object类型,是为了接口的统一,因为在实际中返回的接口可能是

单中类型,如String、int 、double等,也可能是List集合类型,这时候Object类型的好处就显而易见了,只需要进行强转就可以获取实际需要的类型。

5、接口调用

以上讲了这么多,终于轮到业务逻辑的处理了,登录按钮的点击事件调用如下

btnLogin.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View paramView) {

doLoginBusiness();
}

doLoginBusiness调用登录接口的方法如下

        /**
* 调用api接口
*/
protected void invokeUserLoginApi() {
new InvokeUserLoginApiBuilder(new IFinishInvokeCallback() {
@Override
public void onSuccess(Object object) {
showHideLoadingBar(false);
LoginApiRtnVO vo = (LoginApiRtnVO) object;
CommonInfo.LOGIN_USER_INFO = vo;  //设置在常规变量中
rememberAccount(); //记住用户名和密码
System.out.println(new Gson().toJson(vo));
if(StringUtils.isEmpty(vo.getNickName())){
CommonUtil.gotoActivity(activity, SelectAreaActivity.class,false);
}else{
CommonUtil.gotoActivity(activity, MainActivity.class,false);
}
}
@Override
public void onFail(Object object) {
ViewUtil.createHintDialogWindow(activity, object.toString());
showHideLoadingBar(false);
}
},activity,userName,password).build();

}
至此一个登录的实例终于完成


6、总结

   通过将http请求进行封装,使得单个接口的实现变得更加的简单,同时提高了代码的可阅读性和美观度,以后需要不断的进行尝试封装,减少重复的代码,提高代码的复用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息