想了下还是写下这篇博客,去年遇到的一个坑。
关于这个服务器返回的请求状态,我以前用的是这种方式:
给 Android 开发者的 RxJava 详解
RxJava+Retrofit,在联网返回后如何先进行统一的判断?
这种方式对服务器返回的Json格式有要求,必须是这种样子的:
{
"status": 1,
"msg": "message",
"data":{}
}
然后用这样的一个类去接收
public class Result<T> {
public int status;
public String msg;
public T data;
}
我也觉得以上的方式很好用,直到我遇到了这种Json:
{
"status": 1,
"msg": "message",
"data":{},
"otherData":{},
"adData":{}
}
上面给出的链接的方式是指定一个泛型作为接收data的实体对象,但是这里有三种data实体…… 我感觉我踩坑了
一、使用自定义ConvertAdapter
我又搜索了好久,看到了另一种方式,那就是Retrofit可以使用自定义的Converter,它的作用是可以将接受到的json转换成实体返回给我们。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ApiService.API_BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) // 就是这里
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.build();
一般情况下我们都使用GsonConverterFactory,当请求到json后,Retrofit就会调用GsonConverter将json转成我们需要的实体。
GsonConverterFactory一共只有三个类,并且代码量很少,如下:
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.
*/
public static GsonConverterFactory create(Gson gson) {
return new GsonConverterFactory(gson);
}
private final Gson gson;
private GsonConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
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);
}
}
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
return adapter.read(jsonReader);
} finally {
value.close();
}
}
}
final class GsonRequestBodyConverter<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;
GsonRequestBodyConverter(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());
}
}
当我们发出一个POST JSON请求(直接用Retrofit post一个实体给服务器,请求的时候会自动将我们的实体转成Json)的时候,Retrofit就会调用GsonRequestBodyConverter的Convert方法将我们的实体转换成json。
而接受服务器返回的数据时,Retrofit会调用GsonResponseBodyConverter将Json数据转换成我们需要的实体。
####既然如此,我们可以在Converter解析json的时候就做服务器参数的统一处理
我是将GsonConverterFactory的三个类拷贝出来修改了一下:
GsonConverterFactory和RequestBodyConverter几乎没有任何修改,我们需要更改类是GsonResponseBodyConverter,因为它才是将服务器的数据转换成实体了,我们在转换的过程中做统一处理。
/**
* Created by AItsuki on 2016/12/16.
*/
final class ResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private static final int FAILURE = 0; // 失败 提示失败msg
private static final int SUCCESS = 1; // 成功
private static final int TOKEN_EXPIRE = 2; // token过期
private static final int SERVER_EXCEPTION = 3; // 服务器异常
private final Gson gson;
private final TypeAdapter<T> adapter;
ResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String json = value.string();
try {
verify(json);
return adapter.read(gson.newJsonReader(new StringReader(json)));
} finally {
value.close();
}
}
private void verify(String json) {
Result result = gson.fromJson(json, Result.class);
if (result.state != SUCCESS) {
switch (result.state) {
case FAILURE:
case SERVER_EXCEPTION:
throw new ApiException(result.msg);
case TOKEN_EXPIRE:
throw new TokenExpireException(result.msg);
default:
throw new UndefinedStateException();
}
}
}
}
public class Result {
public String msg;
public int state;
}
将json解析成实体之前,我们先手动解析state和msg两个字段,根据状态抛出自定义异常即可。
但是我们的用于接收实体的对象也比较丑,一个类套着一个内部类!例如:
public class User {
public Data data;
public static class Data {
public String username;
public String phone;
public String address;
}
}
这也是一个蛋疼的地方,但是我没有更好的解决方式。
而处理自定义异常和上面给出的两个链接的方式也没什么不同,自定义一个Subscriber在OnError中统一接收处理即可。
二、建议
这是一个解决方案,但是将统一处理改成这种方式之前,我更推荐去找写接口文档或者服务器的哥们谈谈……
别给我们返回这种奇葩的Json:
{
"status": 1,
"msg": "message",
"data":{},
"otherData":{},
"adData":{}
}
或者这种更加奇葩的:
{
"reason": "成功的返回",
"result": {
"stat": "1",
"data": {}
}
}