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": {}
}
}