Retrofit是Square公司开发的一款针对Android网络请求的框架,Retrofit2底层基于OkHttp实现的,OkHttp现在已经得到Google官方认可,大量的app都采用OkHttp做网络请求,其源码详见OkHttp Github。
RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。RxJava在处理异步操作时,能够让异步代码异常简洁,且不会随着程序逻辑的复杂性增加而丢失其简洁性。同时Rxjava在涉及到操作的线程切换时也非常的简洁和方便。
这篇文章主要针对已对Retrofit 和RxJava有基本了解的Developer,在OkHttp和RxJava结合使用时,项目应用中的普遍存在的一些问题的解决方案进行介绍。Retrofit和RxJava 基本用法这里不再介绍,感兴趣的童鞋请自行搜索或点击文章最后的推荐链接查阅。项目中用到的Retrofit 和Rxjava版本和配置如下:
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
compile 'io.reactivex:rxjava:1.3.0'
compile 'io.reactivex:rxandroid:1.2.1'
在新项目中发现原来的网络库在使用Retrofit时,是使用Retrofit的同步请求方式,外层通过AsyncTask进行线程异步。调用方式比较繁琐和麻烦。后来决定重新做个网络库,就有了这篇文章。Retrofit本身提供同步和异步调用方式。
同步请求:
BookSearchResponse response =call.execute().body();
网络请求需要在子线程中完成,不能直接在UI线程执行,不然会crash
异步请求:
call.enqueue(newCallback() {
@Override
publicvoid onResponse(Call call,Respons eresponse) {
asyncText.setText("异步请求结果: "+response.body().books.get(0).altTitle);
}
@Override
publicvoid onFailure(Callcall, Throwable t) {
}
});
异步请求相对同步请求更简便和快捷,开发者只需要再onResponse和OnFailure中处理对应回调即可。但是这种回调方式本身也有不方便的地方。因为回调直接是在UI线程,如果在OnResponse中回调的数据还要进行耗时操作,比如和数据库中的数据对比,或者返回结果是图片的Url 需要再次通过网络请求得到网络图片,上述回调的方式就需要再开线程来处理,而使用RxJava的话,其优点在于异步操作和线程切换,我们就可以比较优雅和轻松的解决上述问题。
网络库架构图如下:
public class OKHttpClientUtils {
public static OkHttpClient sOkHttpClient;
private static Converter.Factory sGsonConverterFactory = GsonConverterFactory.create();
private static Converter.Factory sStringConverterFactory = StringConverterFactory.create();
private static CallAdapter.Factory sRXJavaCallAdapterFactory =
RxJavaCallAdapterFactory.create();
private static Context sContext; //这里的Context必须是applicationContext
public static void init(CustomContext context) {
if (sOkHttpClient == null) {
sOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.cookieJar(new CommonCookieJar())
.addInterceptor(new CommonAppInterceptor())
.build();
sContext = context.getAppContext().getApplicationContext();
}
}
public static class CommonCookieJar implements CookieJar {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
Log.v("OKHttpClientUtils", "response cookieHeader---->" + cookies);
CookieHelper.saveCookies(cookies);
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
Log.v("OKHttpClientUtils", "requestCookie---->" +
CookieHelper.getCookieHeader(url.uri()));
return CookieHelper.getCookieHeader(url.uri());
}
}
public static class CommonAppInterceptor implements Interceptor {
...//处理公共请求参数统一添加
...//处理公共请求Header统一添加
}
public static <T> T createService(Class<T> clazz) {
Retrofit retrofit =
new Retrofit.Builder()
.client(sOkHttpClient)
.baseUrl(getAndroidHost(clazz))
.addConverterFactory(sStringConverterFactory)
.addConverterFactory(sGsonConverterFactory)
.addCallAdapterFactory(sRXJavaCallAdapterFactory)
.build();
return retrofit.create(clazz);
}
/**
* 获取host retrofit2 baseUrl 需要以 "/" 结尾
*/
public static <T> String getAndroidHost(Class<T> clazz) {
//通过注解拿到各个微服务配置的host
}
}
上面显示的OkHttpClientUtil中的各项配置下文会介绍。
public class StatusResponse<Result> implements Serializable {
private static final long serialVersionUID = 6316903436640469387L;
/**
* code 取值 说明
* 0 成功
* < 0 通用错误码,与具体业务无关
* > 0 业务错误码
*/
public int code = 0;
public String msg;
public String errorMsg;
/**
* showType 说明
* 0 Toast 形式
* 1 Alert 形式
*/
public int showType = -1;
Result result;
public boolean isOK() {
return code == 0;
}
}
客户端跟服务器端定义的规则为,所有的请求数据包含code,msg,errorMsg,和showType。 Result泛型为各接口返回的数据。其中当code==0 时为正常情况,code<0 时客户端需根据showType 及errorMsg分别用弹框或toast方式提示对应错误信息,code>0客户端需要自行处理对应情况。后续所有网络请求返回数据均按照StatusResponse的形式返回数据。
因为上面提到客户端需要统一处理code<0的异常情况,所以想要用一种比较优雅的方式来全局处理。查阅了相关资料,发现基本是将code <0 作为一种自定义异常情况来处理。但是报出异常的方式有几种。
一种做法是通过重写GsonConverterFactory,在服务器数据进行Gson转化时,重写GsonResponseBodyConverter 类。
class MyGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final Type type;
MyGsonResponseBodyConverter(Gson gson, Type type) {
this.gson = gson;
this.type = type;
}
@Override
public T convert(ResponseBody value) throws IOException {
try {
String response = value.string();
StatusResponse<T> resultResponse = JsonUtil.fromJson(response,type);
//对返回码进行判断,如果是0,便返回object
if (resultResponse.code == 0) {
return resultResponse.infos;
} else {
//抛出自定义服务器异常
throw new ServerException(resultResponse.state, resultResponse.error);
}
}finally {
// Utils.closeQuietly(reader);
}
}
}
在convert时 resultResponse.code是否等于0来判断是否抛出自定义的ServerException。但是我觉得这种方式需要重写GsonConverterFactory GsonResponseBodyConverter 等相关类,在使用时还是有不安全性和不便捷性。所以还是选择通过Rxjava的Map方式实现的code码判断和异常抛出。
我们先来看调用的时候如何调用,可以先不用管MapTransformer 而只看call 方法里的内容
public class MapTransformer<T> implements
Observable.Transformer<StatusResponse<T>,StatusResponse<T>> {
@Override
public Observable<StatusResponse<T>> call(Observable<StatusResponse<T>>
statusResponseObservable) {
return statusResponseObservable.subscribeOn(Schedulers.io())
.map(new ServerResultFunc<T>())
// Instructs an ObservableSource to pass control to another ObservableSource
// rather than invoking onError if it encounters an error.
.onErrorResumeNext(new HttpResultFunc<StatusResponse<T>>())
.observeOn(AndroidSchedulers.mainThread());
}
}
主要包括这几个类:
进行Map操作的类,主要是在进行转化的时候,通过判断tStatusResponse.getCode() 是否<0 来决定是否抛出自定义的ServerException 异常。
这里自己也思考了很久,主要包括两个问题。 一个问题是code >0 是否应该作为异常处理,第二个问题是在进行转化的时候,是否应该将StatusResponse去 掉,即 ServerResultFunc implements Func1, T> 直接将T
而不是StatusResponse 回调给OnNext(参数...) 作为回调参数,这两个问题我们后面解答。
public class ServerResultFunc<T> implements Func1<StatusResponse<T>, StatusResponse<T>> {
@Override
public StatusResponse<T> call(StatusResponse<T> tStatusResponse) {
if (tStatusResponse.getCode() < 0) {
throw new ServerException(tStatusResponse.getCode(),tStatusResponse.getErrorMsg(),
tStatusResponse.getShowType());
}
return tStatusResponse;
}
}
public class ServerException extends RuntimeException {
private static final long serialVersionUID = 8484806560666715715L;
private int code;
private String errorMsg;
private int showType = -1;
public ServerException(int code, String msg,int showType) {
this.code = code;
this.errorMsg = msg;
this.showType = showType;
}
public int getCode() {
return code;
}
public String getErrorMsg() {
return errorMsg;
}
public int getShowType() {
return showType;
}
}
这个类主要是onErrorResumeNext时触发,作用是当遇到error时不会直接触发onError而是先走到HttpResultFunc call方法,即在上面进行Map时,ServerResultFunc中code <0 抛出ServerException时,截获这个exception 使其先到HttpResultFunc 的call方法中,通过ExceptionEngine.handleException(throwable)构造我们的自定义的ApiException再将ApiException 交给OnError进行回调。
public class HttpResultFunc <T> implements Func1<Throwable, Observable<T>> {
@Override
public Observable<T> call(Throwable throwable) {
// Returns an Observable that invokes an Observer's onError method when the Observer subscribes to it.
return Observable.error(ExceptionEngine.handleException(throwable));
}
}
public class ExceptionEngine {
//对应HTTP的状态码
private static final int UNAUTHORIZED = 401;
private static final int FORBIDDEN = 403;
private static final int NOT_FOUND = 404;
private static final int REQUEST_TIMEOUT = 408;
private static final int INTERNAL_SERVER_ERROR = 500;
private static final int BAD_GATEWAY = 502;
private static final int SERVICE_UNAVAILABLE = 503;
private static final int GATEWAY_TIMEOUT = 504;
public static ApiException handleException(Throwable e){
ApiException ex;
if (e instanceof HttpException){ //HTTP错误
HttpException httpException = (HttpException) e;
ex = new ApiException(e, ERROR.HTTP_ERROR);
switch(httpException.code()){
case UNAUTHORIZED:
case FORBIDDEN:
case NOT_FOUND:
case REQUEST_TIMEOUT:
case GATEWAY_TIMEOUT:
case INTERNAL_SERVER_ERROR:
case BAD_GATEWAY:
case SERVICE_UNAVAILABLE:
default:
ex.setErrorMsg("网络错误"); //均视为网络错误
break;
}
return ex;
} else if (e instanceof ServerException){ //服务器返回的错误
ServerException resultException = (ServerException) e;
ex = new ApiException(resultException,
resultException.getCode(),resultException.getShowType());
ex.setSpecialException(true);
ex.setErrorMsg(resultException.getErrorMsg());
return ex;
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException){
ex = new ApiException(e, ERROR.PARSE_ERROR);
ex.setErrorMsg("解析错误"); //均视为解析错误
return ex;
}else if(e instanceof ConnectException){
ex = new ApiException(e, ERROR.NETWORK_ERROR);
ex.setErrorMsg("连接失败"); //均视为网络错误
return ex;
}else {
ex = new ApiException(e, ERROR.UNKNOWN);
ex.setErrorMsg("未知错误"); //未知错误
return ex;
}
}
}
/**
* 与服务器约定好的异常 100000以上为客户端定义的错误码code
*/
public class ERROR {
/**
* 未知错误
*/
public static final int UNKNOWN = 100000;
/**
* 解析错误
*/
public static final int PARSE_ERROR = 100001;
/**
* 网络错误
*/
public static final int NETWORK_ERROR = 100002;
/**
* 协议出错
*/
public static final int HTTP_ERROR = 100003;
}
* code 取值 说明
* 0 成功
* < 0 通用错误码,与具体业务无关
* > 0 业务错误码
* <p>
* showType 说明
* 0 Toast 形式
* 1 Alert 形式
* msg 无意义。
* <p>
* code < 0,框架处理,有errorMsg返回时,参考showType使用Toast或者Alert提示,无errorMsg时,使用客户端内置的出错提示,区分红包、
* 收银台、主站等不同系统内置提示。code > 0,交由业务逻辑处理,框架不处理。
*/
public class ApiException extends Exception {
private static final long serialVersionUID = 4932302602588317500L;
private boolean isSpecialException = false;
private int code;
private String errorMsg;
private int showType = -1;
public ApiException(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
public ApiException(Throwable throwable, int code, int showType) {
this(throwable, code);
this.showType = showType;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public String getErrorMsg() {
return errorMsg;
}
public int getCode() {
return code;
}
public int getShowType() {
return showType;
}
public boolean isSpecialException() {
return isSpecialException;
}
public void setSpecialException(boolean specialException) {
isSpecialException = specialException;
}
}
public abstract class BaseSubscriber<T> extends Subscriber<T> {
public BaseSubscriber(CustomContext tag) {
SubscriptionManager.getInstance().add(tag, this);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
if (e instanceof ApiException) {
ApiException apiException = (ApiException) e;
int code = apiException.getCode();
if (code < 0) {
String errorMsg = apiException.getErrorMsg();
int showType = apiException.getShowType();
//为了和APP主项目解耦,采用EventBus发送消息给MainActivity来进行对应提示
SubscriberEvent subscriberEvent = new SubscriberEvent(showType, errorMsg);
EventBus.getDefault().post(subscriberEvent);
Log.i("network", "onError--errorMsg->" + errorMsg);
Log.i("network", "onError--code->" + apiException.getCode());
Log.i("network", "onError--showType->" + showType);
if (code == -200) {
EventBus.getDefault().post(new AuthEvent(false));
}
}
onError((ApiException) e);
} else {
onError(new ApiException(e, ERROR.UNKNOWN));
Log.i("network", "onError-otherError->" + e.toString());
}
Crashlytics.logException(e);
Log.e("network", "exception-->" + e.toString());
}
/**
* 错误回调
*/
protected abstract void onError(ApiException ex);
}
通过在BaseSubscriber的OnError中统一处理code <0的情况,而 code==0即正常情况,会回调到BaseSubscriber的onNext中,而code>0也是走到onNext的回调。
到这里统一错误码自定义异常处理就完成了,这里我们回到开头提的两个问题
第一 code >0是否应该算作异常,后来经过实践,code>0 最好不算做异常,因为这里要客户端根据不同的code做业务处理,放在onNext处理比较方便,而且onError中无法获取StatusResponse,也就无法满足客户端根据code处理各种业务的需求(各种业务中需要用到StatusResponse的数据)。
第二 在进行转化的时候,是否应该将StatusResponse去掉,即 ServerResultFunc implements Func1, T> 直接将T而不是StatusResponse 回调给OnNext(参数...) 作为回调参数。如果这样做有个坏处是,OnNext中无法拿到StatusResponse也就无法拿到StatusResponse.getCode()。这个跟我们code>0时客户端自定义处理业务的需求相违背,所以这里仍然保留StatusResponse。
相关阅读:Retrofit + OkHttp +RxJava 网络库构建及项目实践(下篇)
本文来自网易实践者社区,经作者朱强龙授权发布。