Rxjava+Retrofit的强大以及优点我就我多说了,本篇文章主要是解决一下在使用过程中遇到的一个常见的bug,数据转换异常
场景:在请求后台数据的时候,经常会有,因为异常情况,导致后台Success,然后返回一个错误码和错误信息,
而返回的主体数据结构就可能会是千奇百怪了,经常和预设的结构不符合,这时,就会出现数据结构异常的报错。
处理起来很麻烦,无法将其中的真正的错误原因展现给用户。本章主要解决这类问题
我先将报错给大家发一下
03-29 16:36:14.624 8589 8589 W System.err: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 83 path $.result.data
03-29 16:36:14.625 8589 8589 W System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:224)
03-29 16:36:14.625 8589 8589 W System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
03-29 16:36:14.625 8589 8589 W System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
03-29 16:36:14.615 8804 8804 W netd : type=1400 audit(0.0:37704): avc: denied { search } for name="com.zte.heartyservice" dev="mmcblk0p30" ino=1095623 scontext=u:r:netd:s0 tcontext=u:object_r:app_data_file:s0:c512,c768 tclass=dir permissive=0
03-29 16:36:14.625 8589 8589 W System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
03-29 16:36:14.626 8589 8589 W System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
03-29 16:36:14.626 8589 8589 W System.err: at com.google.gson.Gson.fromJson(Gson.java:887)
03-29 16:36:14.627 8589 8589 W System.err: at com.google.gson.Gson.fromJson(Gson.java:852)
03-29 16:36:14.628 8589 8589 W System.err: at com.google.gson.Gson.fromJson(Gson.java:801)
03-29 16:36:14.628 8589 8589 W System.err: at com.weinong.business.api.func.CustomResponseBodyConverter.convert(CustomResponseBodyConverter.java:38)
03-29 16:36:14.628 8589 8589 W System.err: at com.weinong.business.api.func.CustomResponseBodyConverter.convert(CustomResponseBodyConverter.java:21)
03-29 16:36:14.628 8589 8589 W System.err: at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:119)
03-29 16:36:14.628 8589 8589 W System.err: at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:218)
03-29 16:36:14.628 8589 8589 W System.err: at retrofit2.OkHttpCall.execute(OkHttpCall.java:180)
03-29 16:36:14.628 8589 8589 W System.err: at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:41)
03-29 16:36:14.628 8589 8589 W System.err: at io.reactivex.Observable.subscribe(Observable.java:10903)
03-29 16:36:14.628 8589 8589 W System.err: at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
03-29 16:36:14.629 8589 8589 W System.err: at io.reactivex.Observable.subscribe(Observable.java:10903)
03-29 16:36:14.629 8589 8589 W System.err: at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.subscribeInner(ObservableFlatMap.java:162)
03-29 16:36:14.629 8589 8589 W System.err: at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.onNext(ObservableFlatMap.java:139)
03-29 16:36:14.629 8589 8589 W System.err: at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:64)
03-29 16:36:14.629 8589 8589 W System.err: at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
03-29 16:36:14.629 8589 8589 W System.err: at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onNext(BodyObservable.java:51)
03-29 16:36:14.629 8589 8589 W System.err: at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onNext(BodyObservable.java:37)
03-29 16:36:14.630 8589 8589 W System.err: at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:43)
03-29 16:36:14.630 8589 8589 W System.err: at io.reactivex.Observable.subscribe(Observable.java:10903)
03-29 16:36:14.630 8589 8589 W System.err: at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
03-29 16:36:14.630 8589 8589 W System.err: at io.reactivex.Observable.subscribe(Observable.java:10903)
03-29 16:36:14.630 8589 8589 W System.err: at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
03-29 16:36:14.630 8589 8589 W System.err: at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452)
03-29 16:36:14.630 8589 8589 W System.err: at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:61)
03-29 16:36:14.630 8589 8589 W System.err: at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:52)
03-29 16:36:14.630 8589 8589 W System.err: at java.util.concurrent.FutureTask.run(FutureTask.java:237)
03-29 16:36:14.630 8589 8589 W System.err: at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)
03-29 16:36:14.630 8589 8589 W System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
03-29 16:36:14.630 8589 8589 W System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
03-29 16:36:14.630 8589 8589 W System.err: at java.lang.Thread.run(Thread.java:818)
03-29 16:36:14.630 8589 8589 W System.err: Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 83 path $.result.data
03-29 16:36:14.631 8589 8589 W System.err: at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:385)
03-29 16:36:14.631 8589 8589 W System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:213)
03-29 16:36:14.631 8589 8589 W System.err: ... 35 more
首先我先用我的代码预设一下场景,方便不太了解的朋友更好的理解一下,如果已经了解的同行,可以跳过往下看了!
通常从服务端拿到的JSON数据格式大概如下:
{
"code":1,
"msg":"查询成功",
"result":{
"total":0,
"data":{}
}
}
然后呢,我们就会将code、msg、result这一层抽出来,构造一个类,因为它们是不会变的,只有result里面的内容会变
public class StatusVo<T> {
private Long timestamp;
private int code;
private String msg;
private T result;
public StatusVo(int code, String msg) {
this.code = code;
this.msg = msg;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
@Override
public String toString() {
return "StatusVo{" +
"timestamp='" + timestamp + '\'' +
", code=" + code +
", msg='" + msg + '\'' +
", result=" + result +
'}';
}
}
接下来我们再构建一下Retrofit(一下仅仅是部分重要代码)
private static Converter.Factory gsonConverterFactory = GsonConverterFactory.create();
private static Converter.Factory scalarsConverterFactory = ScalarsConverterFactory.create();
private static CallAdapter.Factory rxJavaCallAdapterFactory = RxJava2CallAdapterFactory.create();
//省略大量代码
public static <S> S createTokenService(Class<S> service) {
Retrofit retrofit = new Retrofit.Builder()
.client(tokenOkHttpClient)
.baseUrl(BASE_URL)
.addConverterFactory(gsonConverterFactory)
.addConverterFactory(scalarsConverterFactory)
.addCallAdapterFactory(rxJavaCallAdapterFactory)
.build();
return retrofit.create(service);
}
构建一下访问的API
@FormUrlEncoded
@POST("xxxxxx")
Observable<StatusVo<Object>> query(@FieldMap Map<String, String> params);
实现一个Function 来专门帮我们来将我们定义的Object给拆出来
public class StatusFunc<T> implements Function<StatusVo<T>, T> {
@Override
public T apply(StatusVo<T> statusVo) throws Exception {
if (statusVo != null && statusVo.getCode() == Status.SUCCESS.getCode()) {
if (statusVo.getResult() != null) {
return statusVo.getResult();
} else {
throw new ApiException(statusVo.getMsg());
}
} else if (statusVo != null && statusVo.getCode() == Status.TOKEN_Invalid.getCode()) {
throw new ApiException(Status.TOKEN_Invalid.getCode());
} else if (statusVo != null) {
throw new ApiException(statusVo);
} else {
throw new ApiException(Status.SYSTEM_ERROR.getCode());
}
}
}
上面有一些东西,不是关键的代码,比如ApiException 等,主要的功能就是将StatusVo中的T对象给转换出来,因为我们不关心StatusVo中的东西
终于,要请求接口了
Network.createTokenService(NetWorkService.CaseService.class)
.query(params)
.map(new StatusFunc<>())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Object>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Object o) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
上面的代码就是我的请求代码了,因为是整体的一个框架,所以有很多代码没贴出来,如果有看不懂的可以留言。
通过上面的代码,我们就可以应对一般的错误了,但是如果后台因为某些原因还还是会导致数据返回的结果跟我们需要的不一致,比我我们需要List,结果他返回了一个“{}”——空对象,这时候,就会我最开始贴出来的错。那么我们怎么做呢?
一般有两种解决方案:
1,跟后台讲,让他们务必返回给我们正确的格式,但是这样的话,后台就得小心翼翼的做每一个接口了,在每一个可能返回你数据的地方都要做处理,同时如果后台出现问题了,那这个处理也许就不是那么好做的了。工作量之大,无法想象
2,我们前端做处理,对该异常做相应的处理,但是如果这样的话,为了确保每个接口都足够健壮,那么我们付出的代价,那也是相当的大的
3,自定义GsonConverterFactory,这个方案才是正确的做法,可以完美的解决这个问题。我们主要来说一下这个方案。
其实就是在构建Retrofit的时候,我们发现一下代码
private static Converter.Factory gsonConverterFactory = GsonConverterFactory.create();
public static <S> S createTokenService(Class<S> service) {
Retrofit retrofit = new Retrofit.Builder()
.client(tokenOkHttpClient)
.baseUrl(BASE_URL)
.addConverterFactory(gsonConverterFactory)//这个是关键
.addConverterFactory(scalarsConverterFactory)
.addCallAdapterFactory(rxJavaCallAdapterFactory)
.build();
return retrofit.create(service);
}
点进去看了一下GsonConverterFactory 的源码,发现啥也没有
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit2.converter.gson;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
/**
* A {@linkplain Converter.Factory converter} which uses Gson for JSON.
* <p>
* Because Gson is so flexible in the types it supports, this converter assumes that it can handle
* all types. If you are mixing JSON serialization with something else (such as protocol buffers),
* you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this instance}
* last to allow the other converters a chance to see their types.
*/
public final class GsonConverterFactory extends Converter.Factory {
/**
* Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static GsonConverterFactory create() {
return create(new Gson());
}
/**
* Create an instance using {@code gson} for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static GsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new GsonConverterFactory(gson);
}
private final Gson gson;
private GsonConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
}
}
只有两个关键性的方法 responseBodyConverter() 和 requestBodyConverter(),根据方法名称我们就可以知道,一个是response一个是request,而且从它们代码中我们也不难发现,它们是对数据做了转换,那我们就可以很容易的想到,复写它们,不要让他们做gson转换,因为它们的gson转换数据才导致的我们的报错
于是我就试着复写了一下
/**
* A {@linkplain Converter.Factory converter} which uses Gson for JSON.
* <p>
* Because Gson is so flexible in the types it supports, this converter assumes that it can handle
* all types. If you are mixing JSON serialization with something else (such as protocol buffers),
* you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this instance}
* last to allow the other converters a chance to see their types.
*/
public class ResponseConverterFactory extends Converter.Factory {
private final Gson gson;
/**
* Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
public static ResponseConverterFactory create() {
return create(new Gson());
}
/**
* Create an instance using {@code gson} for conversion. Encoding to JSON and
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
*/
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static ResponseConverterFactory create(Gson gson) {
return new ResponseConverterFactory(gson);
}
private ResponseConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return new CustomResponseBodyConverter<>(gson, type);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
//返回我们自定义的Gson响应体变换器
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new CustomRequestBodyConverter(gson, adapter);
}
}
然后复写了一个CustomResponseBodyConverter 和 CustomRequestBodyConverter
final class CustomRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
public CustomRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
public class CustomResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final Type type;
public CustomResponseBodyConverter(Gson gson, Type type) {
this.gson = gson;
this.type = type;
}
@Override
public T convert(ResponseBody value) throws IOException {
//先将返回的json数据解析到Response中,如果code==SUCCESS,则解析到我们的实体基类中,否则抛异常
String response = value.string();
try {
StatusVo statusVo = gson.fromJson(response, StatusVo.class);
if (statusVo != null && statusVo.getCode() == Status.SUCCESS.getCode()) {
//成功的时候就直接解析,不可能出现解析异常。因为我们实体基类中传入的泛型,就是数据成功时候的格式
return gson.fromJson(response, type);
} else {
//抛一个自定义ResultException 传入失败时候的状态码,和信息
throw new ApiException(statusVo.getCode(), statusVo.getMsg());
}
} finally {
value.close();
}
}
}
关键的代码在CustomResponseBodyConverter 类中,先将我们的代码转化成StatusVo中,注意这个地方我没有带范型T,所以只要是后台返回的数据,都能接收到,然后判断是否是success,如果成功了,那我们就可以放心的转换了,这时候要是转换了,如果再转换错误,那么你就可以理直气壮的去找后台了
然后,如果返回的结果不成功的话,那么我们只要去得到的code 和msg 就够我们用的了,直接带着code 和msg 抛出一个异常,然后在Error()中接一下就好了!
结语
刚刚遇到这个问题,我还在想为啥这么强大的框架会出现这种问题呢!果然它没让我失望,好好阅读源码,好好享受人生