一、前言
目前好像使用okHttp网络请求框架的人越来越多了,比如大家会说它使用高效,安全等很多优点。毫无疑问,本人也使用一段时间了,不过可能很多人和我一样,在使用okHttp框架之前,可能是使用android-async-http这类框架的,相比前者,在使用okHttp框架完成一次请求,还是需要写蛮多代码的,而且挺多一部分是重复的代码,所以这也就是我准备对它进行二次包装的原因。(另外,本文是在熟悉或了解okHttp写的,如果是想对okHttp框架使用说明了解的,可以阅读下这篇文章:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0106/2275.html)
二、概述
二次封装okHttp网络请求框架,目的是简化一些代码的重复编写,使得整个使用过程更加简单易操作,那么从我们最容易变化的几个地方来进行封装:
1)、请求的参数
2)、API的调用
3)、回调接口
三、参数的封装
先来看下okHttp在传递普通类型参数和文件上传的时候,参数是如何接收的。
A、普通参数
RequestBody formBody = new FormEncodingBuilder() .add("name", "姓名") .add("age", 26) .build(); Request request = new Request.Builder() .type(MultipartBuilder.FORM) Request request = = new Request.Builder() 虽然,有FormEncodingBuilder和MultipartBuilder这两个类 .url("http://www.baidu.com") .post(formBody) .build();
B、文件类
RequestBody requestBody = new MultipartBuilder() .addPart( Headers.of("Content-Disposition", "form-data; name=\"file\""), RequestBody.create(MEDIA_TYPE_PNG, new File("upload.png"))) .build(); .url("http://www.baidu.com") .post(requestBody) .build();
虽然,已经提供了
FormEncodingBuilder和
MultipartBuilder这两个类来去组装参数,但后面写那么一大串,感觉还是有些繁琐,其实如果我们可以类似前面的那些框架一样,把参数都封装在一个Map这样的集合中,不就简单了么。
public class RequestParams {
private static final MediaType MEDIA_TYPE_IMAGE = MediaType.parse("image/png");
private static final MediaType MEDIA_TYPE_STREAM = MediaType.parse("text/x-markdown; charset=utf-8");
private static final MediaType MEDIA_TYPE_STRING = MediaType.parse("text/plain;charset=utf-8");
protected ConcurrentHashMap<String, String> urlParams;
protected ConcurrentHashMap<String, FileWrapper> fileParams;
public RequestParams(){
init();
}
public RequestParams(Map<String, String> source) {
init();
for(Map.Entry<String, String> entry : source.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
public RequestParams(String key, String value) {
init();
put(key, value);
}
public RequestParams(Object... keysAndValues) {
init();
int len = keysAndValues.length;
if (len % 2 != 0)
throw new IllegalArgumentException("Supplied arguments must be even");
for (int i = 0; i < len; i += 2) {
String key = String.valueOf(keysAndValues[i]);
String val = String.valueOf(keysAndValues[i + 1]);
put(key, val);
}
}
private void init(){
urlParams = new ConcurrentHashMap<String, String>();
fileParams = new ConcurrentHashMap<String, FileWrapper>();
}
public void put(String key, String value){
if(key != null && value != null) {
urlParams.put(key, value);
}
}
public void put(String key,int value){
if(key != null && String.valueOf(value) != null) {
urlParams.put(key, String.valueOf(value));
}
}
public void put(String key,float value){
if(key != null && String.valueOf(value) != null) {
urlParams.put(key, String.valueOf(value));
}
}
public void put(String key,double value){
if(key != null && String.valueOf(value) != null) {
urlParams.put(key, String.valueOf(value));
}
}
public void put(String key,boolean value){
if(key != null && String.valueOf(value) != null) {
urlParams.put(key, String.valueOf(value));
}
}
public void put(String key, File file) throws FileNotFoundException {
put(key, new FileWrapper(file, FileWrapperType.FILE));
}
public void put(String key, byte[] bytes) throws FileNotFoundException {
put(key, new FileWrapper(bytes, FileWrapperType.BYTE));
}
public void put(String key, FileWrapper fileWrapper){
if(key != null && fileWrapper != null){
fileParams.put(key,fileWrapper);
}
}
public void remove(String key){
urlParams.remove(key);
fileParams.remove(key);
}
private enum FileWrapperType{
FILE(0,"文件"),
BYTE(1,"字节流");
protected int code;
protected String desc;
FileWrapperType(int code,String desc){
this.code = code;
this.desc = desc;
}
}
private static class FileWrapper {
public File file = null;
public byte[] fileBytes = null;
public FileWrapperType contentType;
public FileWrapper(File file,FileWrapperType contentType) {
this.file = file;
this.contentType = contentType;
}
public FileWrapper(byte[] fileBytes,FileWrapperType contentType) {
this.fileBytes = fileBytes;
this.contentType = contentType;
}
}
public String getParamString() {
if (!urlParams.isEmpty()) {
StringBuffer sb = null;
for(ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
if (sb == null) {
sb = new StringBuffer();
sb.append("?");
} else {
sb.append("&");
}
sb.append(entry.getKey());
sb.append("=");
sb.append(entry.getValue());
}
return sb.toString();
}
return null;
}
public RequestBody paramsBody(){
//处理文件参数
if(!fileParams.isEmpty()){
MultipartBuilder multipartBuilder = new MultipartBuilder();
multipartBuilder.type(MultipartBuilder.FORM);
if(!urlParams.isEmpty()) {
for (ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
multipartBuilder.addFormDataPart(entry.getKey(), entry.getValue());
}
}
for(ConcurrentHashMap.Entry<String, FileWrapper> entry : fileParams.entrySet()) {
FileWrapper value = entry.getValue();
switch (value.contentType){
case FILE:
multipartBuilder.addFormDataPart(entry.getKey(), value.file.getName(),
RequestBody.create(MEDIA_TYPE_IMAGE, value.file));
break;
case BYTE:
multipartBuilder.addFormDataPart(entry.getKey(), "test.jpg",
RequestBody.create(MEDIA_TYPE_STREAM, value.fileBytes));
break;
}
}//end for
return multipartBuilder.build();
}//end if
//处理普通参数
if(!urlParams.isEmpty()) {
FormEncodingBuilder formEncodingBuilder = new FormEncodingBuilder();
for (ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
formEncodingBuilder.add(entry.getKey(), entry.getValue());
}
return formEncodingBuilder.build();
}
return null;
}
}
上面定义的RequestParams这个类就是通过key-value这么一种方式专门包装参数的,value接收基本数据类型和File文件类型以及字符流。这样,普通的使用者就更容易上手了。
四、Http接口的封装
okHttp作为一个流行的框架,当然是提供了很多种请求的方式,但往往我们在项目开发中,用得最多了还是只有get和post这两种方式,所以我这里也只提炼这两种方式的接口就可以。
public static <T> void get(String url,final ResultCallBack<T> resultCallBack){
getInstance().handlerRequest(getInstance().buildGetRequest(url, null), resultCallBack);
}
public static <T> void get(String url,RequestParams params,final ResultCallBack<T> resultCallBack){
getInstance().handlerRequest(getInstance().buildGetRequest(url, params), resultCallBack);
}
public static void post(String url){
getInstance().handlerRequest(getInstance().buildPostRequest(url, null), null);
}
public static <T> void post(String url,final ResultCallBack<T> resultCallBack){
getInstance().handlerRequest(getInstance().buildPostRequest(url, null), resultCallBack);
}
public static <T> void post(String url,RequestParams params,final ResultCallBack<T> resultCallBack){
getInstance().handlerRequest(getInstance().buildPostRequest(url, params), resultCallBack);
}
/**
* 获取基础请求参数,对于一些公共的参数,我们可以统一写在这里
* @param params
* @return
*/
private RequestParams getBaseParams(RequestParams params){
if(params == null){
params = new RequestParams();
}
params.put("appKey", AccountPreference.getInstance().getAppKey(""));
params.put("deviceType", "Android");
params.put("pageSize", TDevice.getPageSize());
params.put("v", PackageUtils.getCurrentVersionName(BaseApplication.context()));
return params;
}
/**
* 组装GET请求
* @param url
* @param params
* @return
*/
private Request buildGetRequest(String url,RequestParams params){
Request.Builder reqBuilder = new Request.Builder();
params = getBaseParams(params);
String paramsStr = params.getParamString();
if(paramsStr != null){
url += paramsStr;
}
LogUtils.e("GET方法请求:" + url);
reqBuilder.url(url);
return reqBuilder.build();
}
/**
* 组装POST请求
* @param url
* @param params
* @return
*/
private Request buildPostRequest(String url,RequestParams params){
Request.Builder reqBuilder = new Request.Builder();
reqBuilder.url(url);
params = getBaseParams(params);
LogUtils.e("POST方法请求:" + url+params.getParamString());
RequestBody requestBody = params.paramsBody();
if(requestBody != null){
reqBuilder.post(requestBody);
}
return reqBuilder.build();
}
五、CallBack回调的封装
由于这里我们请求回来的数据,通常都是希望按开发者需要的结果返回的,而除了文件下载之外,普通网络请求回来的数据,我们都是使用json的格式进行传输的,我们的接口使用的是泛型,即可接收任意类型的数据,所以这里需要对我们回来的json数据进行格式化。格式化库我采用的是fastjson,未接触过的可以网上搜索下。要想格式化出是我们期待的数据类型,得先知道类型,所以这里重新定义了一个回调类
public abstract class ResultCallBack<T> {
Type mType;
static Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
return ((java.lang.reflect.ParameterizedType)superclass).getActualTypeArguments()[0];
}
public ResultCallBack(){
mType = getSuperclassTypeParameter(getClass());
}
public void onPreExecute(){};
public void onSuccess(T t){};
public void onFailure(Request request, AppException e){};
}
结合定义的ResultCallBack类,最终处理Http的请求结果
/**
* 处理HTTP请求
* @param request
* @param resultCallBack
* @param <T>
*/
private <T> void handlerRequest(Request request,final ResultCallBack<T> resultCallBack){
//判断当前网络情况
if(!NetworkUtils.isNetworkAvailable(BaseApplication.context())){
BaseApplication.showToast(R.string.netword_error);
if (resultCallBack != null) {
resultCallBack.onFailure(request, AppException.getAppExceptionHandler());
}
return;
}
if(resultCallBack != null){
resultCallBack.onPreExecute();
}
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(final Request request, final IOException e) {
LogUtils.e("请求失败打印:"+e.toString());
uiHandler.post(new Runnable() {
@Override
public void run() {
if (resultCallBack != null) {
resultCallBack.onFailure(request, AppException.io(e));
}
}
});
}
@Override
public void onResponse(final Response response) {
try {
if(response.isSuccessful()){
final String strBody = response.body().string();
LogUtils.e("请求结果打印:" + strBody);
if(StringUtils.isVoid(strBody)){
this.onFailure(response.request(),new IOException("http request data empty"));
}else {
final JSONObject json = JSON.parseObject(strBody);
//可以根据项目的约定来统一处理一些全局的请求码
if(json.getIntValue("result") == -5){
this.onFailure(response.request(),new IOException("http request time out"));
uiHandler.post(new Runnable() {
@Override
public void run() {
EventBus.getDefault().post(true, BusTags.LoginTimeout);
}
});
return;
}
if (resultCallBack != null) {
if (resultCallBack.mType == String.class) {
uiHandler.post(new Runnable() {
@Override
public void run() {
resultCallBack.onSuccess((T) strBody);
}
});
} else {
final T t = JSON.parseObject(strBody, resultCallBack.mType);
uiHandler.post(new Runnable() {
@Override
public void run() {
resultCallBack.onSuccess(t);
}
});
}
}
}
}else{
this.onFailure(response.request(),new IOException("http request failure"));
}
}catch (final Exception e){
this.onFailure(response.request(),new IOException(e.getCause()));
}
}
});
}
上面不管是在
onFailure
,还是
onResponse的回调方法里面,我们是通过handle传结果回去的,这是因为okHttp的回调是不是在UI主线程的,所以这里可以根据我们自己项目的需求来去做不同的反应。
六、实践使用
RequestParams params = new RequestParams();
params.put("name", "张三");//普通字符串类型
params.put("age", 26);//普通数据类型
params.put("file", new File("sdcard/upload.png"));//文件类型
String partUrl = "user/save";
HttpApi.getInstance().post(partUrl, params, new ResultCallBack<UserResult>(){
@Override
public void onSuccess(final UserResult bean) {
super.onSuccess(bean);
//处理回来的结果
}
@Override
public void onPreExecute() {
super.onPreExecute();
//请求前的ui工作
}
@Override
public void onFailure(Request request, AppException e) {
super.onFailure(request, e);
//请求失败处理
}
});