概述
日常开发的时候,避免不了与后台打交道,最常见的就是前端发送请求,后台返回数据,然后将拿到的数据进行展示。现在我们开始模仿一个基本的网络请求,这里使用wanandroid提供的开放api作为请求对象,地址:http://www.wanandroid.com/blog/show/2 ,然后我们选择获取文章列表的一个接口 http://wanandroid.com/article/listproject/0/json
基本请求示例
1、导入Rxjava、Retrofit依赖:
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
//rxjava2
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
2、定义BaseResponse类
wanandroid返回的json数据都有一个基本的格式,即
{
data:T,
errorCode: 0,
errorMsg: ""
}
那么,我们需定义一个BaseResponse类,由于data的数据类型不确定,可能是bean,可能是list,这里使用泛型来表示,简单代码如下:
public class BaseResponse implements Parcelable {
private int errorCode;
private String error;
private T data;
// ……忽略各种setter、getter方法
/**
* 判断请求是否成功
* @return bool
*/
public boolean isSuccess(){
return getErrorCode() == 0;
}
}
3、配置Retrofit
根据文档,创建ApiService.class,并定义接口
//接口
public interface ApiService {
/**
* 获取首页文章列表
*/
@GET("/article/listproject/{pageIndex}/json")
Observable> getArticleList(@Path("pageIndex") int pageIndex);
}
//接口对象
public class ApiServiceImpl {
private ApiServiceImpl() {
throw new RuntimeException("you can't new me");
}
public static ApiService getInstance() {
return createApiService.apiService;
}
/**
* Retrofit生成接口对象.
*/
private static class createApiService {
/**
* Retrofit会根据传入的接口类.生成实例对象.
*/
private static final ApiService apiService = RetrofitClient.getInstance().getApi(ApiService.class);
}
}
Observable的ArticleBean是接口data的具体数据类型,接着我们创建Retrofit
OkHttpClient.Builder builder = new OkHttpClient.Builder()
//链接超时
.connectTimeout(90, TimeUnit.SECONDS)
//读取超时
.readTimeout(90, TimeUnit.SECONDS)
//失败自动重连
.retryOnConnectionFailure(true);
//初始化Retrofit并添加配置
Retrofit retrofit = new Retrofit.Builder()
.client(builder.build())
.baseUrl(ApiService.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
public T getApi(Class clz) {
return mRetrofit.create(clz);
}
4、发起请求
ApiServiceImpl.getInstance()
.getArticleList(pageIndex)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(BaseResponse response) {
if (response.isSuccess()){
ArticleBean articleBean = response.getData();
// do something
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
OK!一切看起来都是这么美好,不过往往事与愿违。
json解析异常
这里请求成功,后台返回的成功的数据类型为
{
data:{},
errorCode: 0,
errorMsg: ""
}
这是没问题的,但是假设请求失败,后台返回的数据类型为
{
data:[],
errorCode: 400,
errorMsg: "参数错误……"
}
请求失败的时候返回的data是一个数组,甚至一个字符串文本,当gson解析的时候,会抛一个JsonParseException,导致拿不到errorCode与errorMsg。
处理json解析异常
好在办法还是有的,对于失败时候的回调处理,有两种方法:
方法一:
让后台统一规范修改
{
data:{},
errorCode: 400,
errorMsg: "参数错误……"
}
改成无论是成功或者失败,data都返回一个对象或者数组。(当然这个是后台好说话,不打人的情况)
显然,即便后台改了,这个也是治标不治本的方法,万一新写的接口没有遵循该规范,或者是新来的同事不知道该规范,这些都是有可能的。那现在我们来看看第二种方法。
方法二:
不知道各位还记得构建Retrofit的那段代码中,有一句.addConverterFactory(GsonConverterFactory.create())
这个ConverterFactory是支持自定义的,也就是说,我们可以自定义自己的converter,不懂ConverterFactory原理的,请参考该文章 秒懂Retrofit2之GsonConverter
自定义Converter
public class ResponseBodyConverter implements Converter {
private final TypeAdapter> adapter;
ResponseBodyConverter(Gson gson, TypeToken typeToken) {
ParameterizedTypeImpl parameterizedType =
new ParameterizedTypeImpl(null, BaseResponse.class, typeToken.getType());
//noinspection unchecked
adapter = (TypeAdapter>) gson.getAdapter(TypeToken.get(parameterizedType));
}
@Override
public T convert(@NonNull ResponseBody value) throws IOException {
String json = value.string();
//第一次解析
BaseResponse obj = GsonUtils.GsonToBean(json, BaseResponse.class);
if (!obj.isSuccess()) {
//如果是服务端返回的错误码,则抛出自定义异常
throw new ApiException(obj.getErrorCode(), obj.getError());
}
//第二次解析
BaseResponse result = adapter.fromJson(json);
value.close();
return result.getData();
}
// 省略部分ParameterizedTypeImpl代码
}
我们首先看convert方法,首先获得ResponseBody 的值,用gson解析,第一次是判断code,如果服务端返回的不是成功的状态码,则抛出自定义的ApiException,该异常可以在DisposableObserver类的onError方法接收到;如果服务端返回的是成功的状态码,则对数据进行解析,最终返回data。
另外,ParameterizedTypeImpl是一个静态内部类(后面源码会放出),将基本泛型类型指定为BaseResponse,也就是说,原本
Observable> getArticleList(@Path("pageIndex") int pageIndex);
现在可以写成
Observable getArticleList(@Path("pageIndex") int pageIndex);
去掉BaseResponse这一层。然后我们现在可以将rxjava的请求订阅也统一封装一下,请求错误的处理。
自定义DisposableObserver
public abstract class BaseResourceObserver extends DisposableObserver {
/*========================= HttpException 异常 code ==========================*/
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;
@Override
protected void onStart() {
super.onStart();
}
@Override
public void onError(Throwable throwable) {
//打印日志到控制台
throwable.printStackTrace();
//如果你某个地方不想使用全局错误处理,
//则重写 onError(Throwable) 并将 super.onError(e); 删掉
//如果你不仅想使用全局错误处理,还想加入自己的逻辑,
//则重写 onError(Throwable) 并在 super.onError(e); 后面加入自己的逻辑
String msg = requestHandle(throwable);
Log.i("tag",msg);
}
@Override
public void onComplete() {
}
/**
* 统一处理Throwable
* @param e e
* @return msg
*/
private String requestHandle(Throwable e) {
String msg;
if (e instanceof HttpException) {
HttpException httpException = (HttpException) e;
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:
msg = "服务器错误";
break;
}
} else if (e instanceof ApiException) {
//后台异常,在这里你可以toast弹窗或者进行其他处理,具体需根据业务结合
ApiException apiException = (ApiException) e;
msg = apiException.getMessage();
} else if (e instanceof JsonParseException || e instanceof JSONException
|| e instanceof ParseException) {
msg = "解析错误";
} else if (e instanceof ConnectException || e instanceof SocketTimeoutException
|| e instanceof UnknownHostException) {
msg = "连接失败,请检查网络";
} else if (e instanceof NumberFormatException){
msg = "数字格式化异常";
} else {
msg = "请求失败";
}
return msg;
}
}
替换我们自定义的Converter
Retrofit retrofit = new Retrofit.Builder()
.client(builder.build())
.baseUrl(ApiService.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//.addConverterFactory(GsonConverterFactory.create())
//这里是自定义的GsonConverterFactory
.addConverterFactory(MyConverterFactory.create())
.build();
在activity请求,
Disposable disposable = ApiServiceImpl.getInstance()
.getArticleList(pageIndex)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// 这里使用的是subscribeWith操作符
.subscribeWith(new BaseResourceObserver() {
@Override
public void onNext(ArticleBean articleBean) {
// do something
}
});
在subscribeWith里面初始化我们的BaseResourceObserver,只需重写onNext方法即可,简单便捷。
OK,至此处理json解析异常算是完成了。
总结
1、请求成功基本数据类型一般是不变的;
2、请求失败,只解析code错误码,不解析data,这样就不会出现解析异常了。
最后附上simple源码 RetrofitConverterSimple