1. 程式人生 > >Retrofit統一處理伺服器返回引數

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