App网络请求Rxjava+Retrofit的初步封装
现在的App在网络请求方面都是用Rxjava和Retrofit这一套东西。来,下面我们按照下面的思路来想。为什么要封装–>决定要封装了,该从哪方面入手,或者说应该怎么封装。注意:本文默认你会使用Rxjava和Retrofit,重在探讨封装思路,皮得我们就不谈了。
老规矩上图
为什么要封装
想一想一个App里面,起码得有几十个Api请求吧? 如果不封装,相当于每一个请求都要进行判断,比如对返回码code进行判断。这样不仅很烦,而且重复代码很多,所以需要进行封装。又因为每个请求返回的格式都是一样的,返回的状态码也就那些,所以我们可以分别针对请求成功和请求失败进行封装,通俗点就是统一处理。
如何封装
1.首先我们要想,我们要的是数据对吧,那么首先我们对网络请求返回的数据进行分类。如下图所示:
说白了,我们只需要把握住两个关口。一个是成功,一个是失败。失败又包含:服务端返回码不等于1和其他的原因导致,如连接超时,解析错误等。
2.嘿嘿嘿,似乎有点思路了哦,那我们该怎么对所有的返回数据进行统一处理呢? 毕竟每个请求返回的数据又不是一样的。那我们能不能用一个实体类来表示所有的返回数据呢,如果能的话,那么就肯定能统一处理了。是的,java里真的有这个东西叫泛型。那就创建一个BaseResponse类,如下所示:
/**
* <pre>
* 作者 : 肖坤
* 时间 : 2018/04/19
* 描述 : 这个需要注意的是,Code Message Data必须和后台定义的属性名称一致
* 版本 : 1.0
* </pre>
*/
public class BaseResponse<T>
{
private int Code;
private String Message;
private T Data;
public int getCode()
{
return Code;
}
public void setCode(int code)
{
Code = code;
}
public String getMessage()
{
return Message;
}
public void setMessage(String message)
{
Message = message;
}
public T getData()
{
return Data;
}
public void setData(T data)
{
Data = data;
}
}
需要注意的是,这个需要后台配合的。就是说每个请求返回的数据格式必须得和上面一样,否则会有问题。如果后台不配合的话,那就干死他,嘿嘿嘿。上面是表示的是,我们最终需要的那个对象T中的数据。比如说我们有一个返回的实体类是这个样子的:
/**
* <pre>
* 作者 : 肖坤
* 时间 : 2018/04/19
* 描述 :
* 版本 : 1.0
* </pre>
*/
public class ResEntity1
{
/**
* Data : {"res":"返回成功 "}
* Code : 1
* Message : ok
*/
private DataBean Data;
private int Code;
private String Message;
public DataBean getData()
{
return Data;
}
public void setData(DataBean Data)
{
this.Data = Data;
}
public int getCode()
{
return Code;
}
public void setCode(int Code)
{
this.Code = Code;
}
public String getMessage()
{
return Message;
}
public void setMessage(String Message)
{
this.Message = Message;
}
public static class DataBean
{
/**
* res : 返回成功
*/
private String res;
public String getRes()
{
return res;
}
public void setRes(String res)
{
this.res = res;
}
}
@Override
public String toString()
{
return "ResEntity1{" +
"Data=" + Data +
", Code=" + Code +
", Message='" + Message + '\'' +
'}';
}
}
那么我们在ApiService中可以这么写了哦:
@GET("tools/mockapi/440/yx0419")
Observable<BaseResponse<ResEntity1.DataBean>> getHttpData1();
然后在项目中可以这么写:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ApiService.baseUrl)
//这里的client当然可以自己配置
.client(new OkHttpClient())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
apiService = retrofit.create(ApiService.class);
Observable<BaseResponse<ResEntity1.DataBean>> httpData1 = apiService.getHttpData1();
3.好像是那个意思,但是,等等!上面那句代码的意思是期望网络请求返回一个泛型是ResEntity1.Data的BaseResponse对象。但是我们最终希望返回的是ResEntity.Data数据,那有没有一种方法是这样的呢,即输入BaseResponse输出ResEntity1.Data。如果你知道Rxjava,你肯定知道是有的。Rxjava1中叫Func1,Rxjava2中叫Function,利用map操作符进行转换。
/**
* A functional interface that takes a value and returns another value, possibly with a
* different type and allows throwing a checked exception.
*
* @param <T> the input value type
* @param <R> the output value type
*/
public interface Function<T, R> {
/**
* Apply some calculation to the input value and return some other value.
* @param t the input value
* @return the output value
* @throws Exception on error
*/
R apply(T t) throws Exception;
}
所以我们可以写一个处理BaseResponse的类,如下所示:
/**
* <pre>
* 作者 : 肖坤
* 时间 : 2018/04/19
* 描述 :
* 版本 : 1.0
* </pre>
*/
public class HttpResultFunc<T> implements Function<BaseResponse<T>, T>
{
@Override
public T apply(BaseResponse<T> response) throws Exception
{
//只有当返回的code==success时才成功,其余情况全部抛出错误
if (response.getCode() == Constants.HTTP_SUCCESS)
{
return response.getData();
} else
{
//抛出异常,让rxjava捕获,便于统一处理
throw new ApiException.ServerException(response.getCode(), response.getMessage());
}
}
}
只有当响应码等于代表成功时,才返回;否则都抛出异常,而这个异常会被发送到onError中。注意哦,这个自定义的异常属于服务端返回的响应码不等于1的异常。我们还有一种异常是非服务端异常。所以我们必须写一个错误处理类来进行统一处理,如下所示:
/**
* <pre>
* 作者 : 肖坤
* 时间 : 2018/04/19
* 描述 :
* 版本 : 1.0
* </pre>
*/
public class ApiException extends Exception
{
private int errorCode;
private String msg;
private ApiException(Throwable throwable, int errorCode)
{
super(throwable);
this.errorCode = errorCode;
this.msg = throwable.getMessage();
}
public static ApiException handlerException(Throwable throwable)
{
ApiException exception = null;
if (throwable instanceof HttpException)
{
HttpException httpException = (HttpException) throwable;
exception = new ApiException(httpException, httpException.code());
try
{
exception.setMsg(httpException.response().errorBody().string());
} catch (IOException e)
{
e.printStackTrace();
exception.setMsg(e.getMessage());
}
} else if (throwable instanceof SocketTimeoutException || throwable instanceof ConnectException ||
throwable instanceof ConnectTimeoutException || throwable instanceof UnknownHostException)
{
exception = new ApiException(throwable, TIMEOUT_ERROR);
exception.setMsg("网络连接超时,请检查您的网络状态,稍后重试!");
} else if (throwable instanceof NullPointerException)
{
exception = new ApiException(throwable, NULL_POINTER_EXCEPTION);
exception.setMsg("空指针异常");
} else if (throwable instanceof SSLHandshakeException)
{
exception = new ApiException(throwable, SSL_ERROR);
exception.setMsg("证书验证失败");
} else if (throwable instanceof ClassCastException)
{
exception = new ApiException(throwable, CAST_ERROR);
exception.setMsg("类型转换错误");
} else if (throwable instanceof IllegalStateException)
{
exception = new ApiException(throwable, ILLEGAL_STATE_ERROR);
exception.setMsg(throwable.getMessage());
} else if (throwable instanceof JsonParseException || throwable instanceof JSONException
|| throwable instanceof JsonSyntaxException || throwable instanceof JsonSerializer
|| throwable instanceof NotSerializableException || throwable instanceof ParseException)
{
exception = new ApiException(throwable, PARSE_ERROR);
exception.setMsg("解析错误");
} else if (throwable instanceof ServerException)
{
int errorCode = ((ServerException) throwable).getErrorCode();
String msg = ((ServerException) throwable).getErrorMsg();
exception = new ApiException(throwable, errorCode);
exception.setMsg(msg);
} else
{
exception = new ApiException(throwable, UNKNOWN);
exception.setMsg("未知错误");
}
return exception;
}
private static String getErrorMsgByErrorCode(int errorCode)
{
String msg = "";
switch (errorCode)
{
case Constants.HTTP_NO_LOGIN:
msg = "未登录";
return msg;
default:
return "未知错误";
}
}
public int getErrorCode()
{
return errorCode;
}
public void setErrorCode(int errorCode)
{
this.errorCode = errorCode;
}
public String getMsg()
{
return msg;
}
public void setMsg(String msg)
{
this.msg = msg;
}
@Override
public String toString()
{
return "ApiException{" +
"errorCode=" + errorCode +
", msg='" + msg + '\'' +
'}';
}
/**
* 约定异常
*/
public static class ERROR
{
/**
* 未知错误
*/
public static final int UNKNOWN = 1000;
/**
* 连接超时
*/
public static final int TIMEOUT_ERROR = 1001;
/**
* 空指针错误
*/
public static final int NULL_POINTER_EXCEPTION = 1002;
/**
* 证书出错
*/
public static final int SSL_ERROR = 1003;
/**
* 类转换错误
*/
public static final int CAST_ERROR = 1004;
/**
* 解析错误
*/
public static final int PARSE_ERROR = 1005;
/**
* 非法数据异常
*/
public static final int ILLEGAL_STATE_ERROR = 1006;
}
public static class ServerException extends RuntimeException
{
private int errorCode;
private String errorMsg;
public ServerException(int errorCode, String errorMsg)
{
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public int getErrorCode()
{
return this.errorCode;
}
public String getErrorMsg()
{
return this.errorMsg;
}
}
}
能懂不,胸低们? 这个时候我们就可以在项目中这么写了:
Observable<ResEntity1.DataBean> compose = apiService.getHttpData1()
.map(new HttpResultFunc<ResEntity1.DataBean>())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
线程切换占了两行,尼玛不爽。注意到Observable提供了一个compose方法:
/**
* Transform an ObservableSource by applying a particular Transformer function to it.
*/
@SchedulerSupport(SchedulerSupport.NONE)
public final <R> Observable<R> compose(ObservableTransformer<T, R> composer) {
return wrap(composer.apply(this));
}
大意就是通过我们指定的转换操作来对流进行转换。参数是一个实现了ObservableTransformer接口的类,来看下ObservableTransformer是神魔恋:
/**
* Interface to compose Observables.
*
* @param <Upstream> the upstream value type
* @param <Downstream> the downstream value type
*/
public interface ObservableTransformer<Upstream, Downstream> {
/**
* Applies a function to the upstream Observable and returns an ObservableSource with
* optionally different element type.
* @param upstream the upstream Observable instance
* @return the transformed ObservableSource instance
*/
ObservableSource<Downstream> apply(Observable<Upstream> upstream);
}
擦,又是输入输出。注意下我们这里不想改变流了,皮得我们就不谈了,所以我们可以这么写:
/**
* <pre>
* 作者 : 肖坤
* 时间 : 2018/04/19
* 描述 :
* 版本 : 1.0
* </pre>
*/
public class SchedulerTransformer<T> implements ObservableTransformer<T, T>
{
@Override
public ObservableSource<T> apply(Observable<T> upstream)
{
return upstream
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
输入和输出都是T。所以一番改进后,项目里的切换线程可以这么写了:
Observable<ResEntity1.DataBean> compose = apiService.getHttpData1()
.map(new HttpResultFunc<ResEntity1.DataBean>())
.compose(RxSchedulers.<ResEntity1.DataBean>io_main());
上游事件到此结束咯,接下来搞下游事件。
4.接下来看一看,下游事件是神魔恋:
首先我们看看最原始的写法:
compose.subscribe(new Observer<ResEntity1.DataBean>()
{
@Override
public void onSubscribe(Disposable d)
{
}
@Override
public void onNext(ResEntity1.DataBean value)
{
}
@Override
public void onError(Throwable e)
{
}
@Override
public void onComplete()
{
}
});
我滴个龟龟,这要是在项目里这么写,那代码还不得几千行啊。所以需要去除那些重复的和我们不怎么关心的方法,这里需要关注下onSubscribe(Disposable d),我们可以利用这个来取消网络。所以我们可以写一个抽象类来实现这个Observer接口,如下所示:
/**
* <pre>
* 作者 : 肖坤
* 时间 : 2018/04/19
* 描述 :
* 版本 : 1.0
* </pre>
*/
public abstract class BaseObserver<T> implements Observer<T>
{
protected RxManager rxManager;
public BaseObserver(RxManager rxManager)
{
this.rxManager = rxManager;
}
@Override
public void onSubscribe(Disposable d)
{
rxManager.add(d);
}
@Override
public void onComplete()
{
}
@Override
public void onError(Throwable e)
{
//统一处理错误
String msg = ApiException.handlerException(e).getMsg();
int errorCode = ApiException.handlerException(e).getErrorCode();
if (msg.length() > 64)
{
msg = msg.substring(0, 64);
}
if (errorCode == Constants.HTTP_NO_LOGIN)
{
//跳转至登录页面
Intent intent = new Intent(App.getAppContext(), LoginActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
App.getAppContext().startActivity(intent);
}
onErrorMsg("错误码:" + errorCode + "\n" + msg);
}
/**
* 返回错误字符串
*
* @param msg
*/
protected abstract void onErrorMsg(String msg);
@Override
public abstract void onNext(T t);
}
注意我们在onError里面统一进行错误处理,包含服务端的和非服务端的,还有一个我们需要单独处理。就是当errorCode等于未登录时,我们需要跳转登录页,并clear掉当前任务栈。当然这里还有一些其他的处理方式,并不一定非要用app来跳转。当判断errorMsg的长度大于64时,我就截取前64个字符串,免得错误字符串太长。还有一个地方就是用Rxmanager来管理网络请求,这个可以防止当Activity销毁时,网络还在请求的,导致出现空指针异常。综上项目中我们可以这么写:
Observable<ResEntity1.DataBean> compose = apiService.getHttpData1()
.map(new HttpResultFunc<ResEntity1.DataBean>())
.compose(RxSchedulers.<ResEntity1.DataBean>io_main());
compose.subscribe(new BaseObserver<ResEntity1.DataBean>(rxManager)
{
@Override
protected void onErrorMsg(String msg)
{
}
@Override
public void onNext(ResEntity1.DataBean dataBean)
{
mTextView.setText(dataBean.getRes());
Toast.makeText(MainActivity.this, dataBean.getRes(), Toast.LENGTH_SHORT).show();
}
});
5.到这里,基本上一个简单的封装就已经完成了。当然啦,如果全部的请求都在Activity或者Fragment写,会造成臃肿以及难以维护的现象。后面我会写,token的刷新,以及activity太过于臃肿而引入P层等。
github地址:https://github.com/xiaokun19931126/HttpExceptionDemo
以上。