赞
踩
日常开发的时候,避免不了与后台打交道,最常见的就是前端发送请求,后台返回数据,然后将拿到的数据进行展示。现在我们开始模仿一个基本的网络请求,这里使用wanandroid提供的开放api作为请求对象,地址:http://www.wanandroid.com/blog/show/2 ,然后我们选择获取文章列表的一个接口 http://wanandroid.com/article/listproject/0/json
//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'
wanandroid返回的json数据都有一个基本的格式,即
{
data:T,
errorCode: 0,
errorMsg: ""
}
那么,我们需定义一个BaseResponse类,由于data的数据类型不确定,可能是bean,可能是list,这里使用泛型来表示,简单代码如下:
public class BaseResponse<T> implements Parcelable { private int errorCode; private String error; private T data; // ……忽略各种setter、getter方法 /** * 判断请求是否成功 * @return bool */ public boolean isSuccess(){ return getErrorCode() == 0; } }
根据文档,创建ApiService.class,并定义接口
//接口 public interface ApiService { /** * 获取首页文章列表 */ @GET("/article/listproject/{pageIndex}/json") Observable<BaseResponse<ArticleBean>> 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<BaseResponse>的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> T getApi(Class<T> clz) { return mRetrofit.create(clz); }
ApiServiceImpl.getInstance() .getArticleList(pageIndex) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<BaseResponse<ArticleBean>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(BaseResponse<ArticleBean> response) { if (response.isSuccess()){ ArticleBean articleBean = response.getData(); // do something } } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });
OK!一切看起来都是这么美好,不过往往事与愿违。
这里请求成功,后台返回的成功的数据类型为
{
data:{},
errorCode: 0,
errorMsg: ""
}
这是没问题的,但是假设请求失败,后台返回的数据类型为
{
data:[],
errorCode: 400,
errorMsg: "参数错误……"
}
请求失败的时候返回的data是一个数组,甚至一个字符串文本,当gson解析的时候,会抛一个JsonParseException,导致拿不到errorCode与errorMsg。
好在办法还是有的,对于失败时候的回调处理,有两种方法:
让后台统一规范修改
{
data:{},
errorCode: 400,
errorMsg: "参数错误……"
}
改成无论是成功或者失败,data都返回一个对象或者数组。(当然这个是后台好说话,不打人的情况)
显然,即便后台改了,这个也是治标不治本的方法,万一新写的接口没有遵循该规范,或者是新来的同事不知道该规范,这些都是有可能的。那现在我们来看看第二种方法。
不知道各位还记得构建Retrofit的那段代码中,有一句.addConverterFactory(GsonConverterFactory.create())
这个ConverterFactory是支持自定义的,也就是说,我们可以自定义自己的converter,不懂ConverterFactory原理的,请参考该文章 秒懂Retrofit2之GsonConverter
public class ResponseBodyConverter<T> implements Converter<ResponseBody, T> { private final TypeAdapter<BaseResponse<T>> adapter; ResponseBodyConverter(Gson gson, TypeToken<T> typeToken) { ParameterizedTypeImpl parameterizedType = new ParameterizedTypeImpl(null, BaseResponse.class, typeToken.getType()); //noinspection unchecked adapter = (TypeAdapter<BaseResponse<T>>) 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<T> result = adapter.fromJson(json); value.close(); return result.getData(); } // 省略部分ParameterizedTypeImpl代码 }
我们首先看convert方法,首先获得ResponseBody 的值,用gson解析,第一次是判断code,如果服务端返回的不是成功的状态码,则抛出自定义的ApiException,该异常可以在DisposableObserver类的onError方法接收到;如果服务端返回的是成功的状态码,则对数据进行解析,最终返回data。
另外,ParameterizedTypeImpl是一个静态内部类(后面源码会放出),将基本泛型类型指定为BaseResponse,也就是说,原本
Observable<BaseResponse<ArticleBean>> getArticleList(@Path("pageIndex") int pageIndex);
现在可以写成
Observable<ArticleBean> getArticleList(@Path("pageIndex") int pageIndex);
去掉BaseResponse这一层。然后我们现在可以将rxjava的请求订阅也统一封装一下,请求错误的处理。
public abstract class BaseResourceObserver<T> extends DisposableObserver<T> { /*========================= 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<ArticleBean>() {
@Override
public void onNext(ArticleBean articleBean) {
// do something
}
});
在subscribeWith里面初始化我们的BaseResourceObserver,只需重写onNext方法即可,简单便捷。
OK,至此处理json解析异常算是完成了。
1、请求成功基本数据类型一般是不变的;
2、请求失败,只解析code错误码,不解析data,这样就不会出现解析异常了。
最后附上simple源码 RetrofitConverterSimple
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。