Retrofit自定義GsonConverter處理全部請求錯誤狀況解決方案

直接進正題

一般從服務端拿到的JSON數據格式大概以下:java

{
    "code":1,
    "message":"查詢成功",
    "detail":{"aa":"123","bb":"123","cc":"123"}
  }
複製代碼

所以一般咱們會定義一個實體類來解析對應的json: ​json

public class Response {
    @SerializedName("code")
    private int code;
    @SerializedName("message")
    private String message;
    @SerializedName("detail")
    private DetailBean detail;
    //省略getter和setter方法...

    public static class DetailBean {
        @SerializedName("aa")
        private String aa;
        @SerializedName("bb")
        private String bb;
        @SerializedName("cc")
        private String cc;
        //省略getter和setter方法...
    }
}
複製代碼

其中的code字段表示狀態,好比如下值可能表明了不一樣的含義api

  • code = 1, 表示成功, 不等於1表明錯誤
  • code = -101, 表示token過時
  • code = -102, 表示手機號碼已經註冊
  • 等等等

若是咱們按照正常的Retrofit+RxJava邏輯來處理,寫出來的代碼以下所示:bash

//ApiService.java
public interface ApiService {
    String ENDPOINT = Constants.END_POINT;

    @POST("app/api")
    Observable<Response1> request1(@Body Request1 request);

    @POST("app/api")
    Observable<Response2> request2(@Body Request2 request);
    /**
     * Create a new ApiService
     */
    class Factory {
        private Factory() {  }

        public static ApiService createService( ) {
            OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
            builder.readTimeout(10, TimeUnit.SECONDS);
            builder.connectTimeout(9, TimeUnit.SECONDS);

            if (BuildConfig.DEBUG) {
                HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
                interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
                builder.addInterceptor(interceptor);
            }

            builder.addInterceptor(new HeaderInterceptor());
            OkHttpClient client = builder.build();
            Retrofit retrofit =
                    new Retrofit.Builder().baseUrl(ApiService.ENDPOINT)
                            .client(client)
                            .addConverterFactory(GsonConverterFactory.create())
                            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                            .build();
            return retrofit.create(ApiService.class);
        }
    }
}
複製代碼

使用的時候:app

ApiService mApiService = ApiService.Factory.createService();
mApiService.request1(request)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Response1>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Response1 response) {
                        int code = response.getCode();
                        switch (code) {
                            case 1: //do something
                                break;
                            case -101://do something
                                break;
                            case -102: //do something
                                break;
                            default:
                                break;
                        }
                    }
                });
複製代碼

若是對每個請求都這麼作,那不是寫死我的嗎, 萬一哪天這些值變了, 好比從-102 變成了 -105 , 那你不是每一個地方所有都得改, 想一想就可怕!ide

解決辦法

Retrofit retrofit =
                    new Retrofit.Builder().baseUrl(ApiService.ENDPOINT)
                            .client(client)
                            .addConverterFactory(GsonConverterFactory.create())
                            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                            .build();
複製代碼

addConverterFactory(GsonConverterFactory.create()) 這句代碼是爲了用Gson把服務端返回的json數據解析成實體的, 那就從這裏入手,能夠本身定義一個GsonConverter,擴展一下原來的功能ui

先分析一下默認的GsonConverter怎麼寫的, 由三個類組成:this

  • GsonConverterFactory // GsonConverter 工廠類, 用來建立GsonConverter
  • GsonResponseBodyConverter // 處理ResponseBody
  • GsonRequestBodyConverter // 處理RequestBody

從名字就很容易看出每一個類是幹嗎的, GsonResponseBodyConverter這個類確定是關鍵, 看一下這個類:spa

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();
    }
  }
}
複製代碼

你沒有看錯,就是這麼幾行代碼... 這個convert()方法就是要擴展的地方了,code

只須要在原來的邏輯上面添加上處理code ! = 1 的狀況, 若是code ! = 1,就拋出異常,

先直接上代碼:

//CustomGsonConverterFactory.java
public class CustomGsonConverterFactory extends Converter.Factory {

    private final Gson gson;

    private CustomGsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    public static CustomGsonConverterFactory create() {
        return create(new Gson());
    }

    public static CustomGsonConverterFactory create(Gson gson) {
        return new CustomGsonConverterFactory(gson);
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new CustomGsonResponseBodyConverter<>(gson, adapter);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new CustomGsonRequestBodyConverter<>(gson, adapter);
    }
}
複製代碼
//CustomGsonRequestBodyConverter.java
final class CustomGsonRequestBodyConverter<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;

    CustomGsonRequestBodyConverter(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());
    }
}
複製代碼
//CustomGsonResponseBodyConverter.java
final class CustomGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;

    CustomGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        String response = value.string();
        HttpStatus httpStatus = gson.fromJson(response, HttpStatus.class);
        if (httpStatus.isCodeInvalid()) {
            value.close();
            throw new ApiException(httpStatus.getCode(), httpStatus.getMessage());
        }

        MediaType contentType = value.contentType();
        Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
        InputStream inputStream = new ByteArrayInputStream(response.getBytes());
        Reader reader = new InputStreamReader(inputStream, charset);
        JsonReader jsonReader = gson.newJsonReader(reader);

        try {
            return adapter.read(jsonReader);
        } finally {
            value.close();
        }
    }
}
複製代碼

其餘兩個類和默認的同樣的, 只看第三個類CustomGsonResponseBodyConverter

這裏自定義了兩個類,一個是HttpStatus和ApiException,下面是這兩個類:

//HttpStatus.java
public class HttpStatus {
    @SerializedName("code")
    private int mCode;
    @SerializedName("message")
    private String mMessage;

    public int getCode() {
        return mCode;
    }

    public String getMessage() {
        return mMessage;
    }

    /**
     * API是否請求失敗
     *
     * @return 失敗返回true, 成功返回false
     */
    public boolean isCodeInvalid() {
        return mCode != Constants.WEB_RESP_CODE_SUCCESS;
    }
}
複製代碼
//ApiException.java
public class ApiException extends RuntimeException {
    private int mErrorCode;

    public ApiException(int errorCode, String errorMessage) {
        super(errorMessage);
        mErrorCode = errorCode;
    }

    /**
     * 判斷是不是token失效
     *
     * @return 失效返回true, 不然返回false;
     */
    public boolean isTokenExpried() {
        return mErrorCode == Constants.TOKEN_EXPRIED;
    }
}
複製代碼

很通俗易懂, 解釋一下其中關鍵的幾行代碼

String response = value.string(); //把responsebody轉爲string
// 這裏只是爲了檢測code是否==1,因此只解析HttpStatus中的字段,由於只要code和message就能夠了
 HttpStatus httpStatus = gson.fromJson(response, HttpStatus.class); 
 if (httpStatus.isCodeInvalid()) {
     value.close();
    //拋出一個RuntimeException, 這裏拋出的異常會到Subscriber的onError()方法中統一處理
     throw new ApiException(httpStatus.getCode(), httpStatus.getMessage());
 }
複製代碼

這裏有個關於ResponseBody的坑, 若是有人遇到過這個異常的確定就知道

java.lang.IllegalStateException: closed
            at com.squareup.okhttp.internal.http.HttpConnection$FixedLengthSource.read(HttpConnection.java:455)
            at okio.Buffer.writeAll(Buffer.java:594)
            at okio.RealBufferedSource.readByteArray(RealBufferedSource.java:87)
            at com.squareup.okhttp.ResponseBody.bytes(ResponseBody.java:56)
            at com.squareup.okhttp.ResponseBody.string(ResponseBody.java:82)
複製代碼

由於你只能對ResponseBody讀取一次 , 若是你調用了response.body().string()兩次或者response.body().charStream()兩次就會出現這個異常, 先調用string()再調用charStream()也不能夠.

因此一般的作法是讀取一次以後就保存起來,下次就不從ResponseBody裏讀取.

最後使用方法:

先創建一個BaseSubscriber

//BaseSubscriber.java
public class BaseSubscriber<T> extends Subscriber<T> {
    protected Context mContext;

    public BaseSubscriber(Context context) {
        this.mContext = context;
    }

    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(final Throwable e) {
        Log.w("Subscriber onError", e);
        if (e instanceof HttpException) {
            // We had non-2XX http error
            Toast.makeText(mContext, mContext.getString(R.string.server_internal_error), Toast.LENGTH_SHORT).show();
        } else if (e instanceof IOException) {
            // A network or conversion error happened
            Toast.makeText(mContext, mContext.getString(R.string.cannot_connected_server), Toast.LENGTH_SHORT).show();
        } else if (e instanceof ApiException) {
            ApiException exception = (ApiException) e;
            if (exception.isTokenExpried()) {
                //處理token失效對應的邏輯
            } else {
                Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_SHORT).show();
            }
        } 
    }

    @Override
    public void onNext(T t) {

    }

}
複製代碼

請求方式

ApiService mApiService = ApiService.Factory.createService();
mApiService.request1(request)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new BaseSubscriber<Response1>() {
                    @Override
                    public void onCompleted() {
                        super.onCompleted(); 
                    }

                    @Override
                    public void onError(Throwable e) {
                        super.onError(e); //這裏就所有交給基類來處理了
                    }

                    @Override
                    public void onNext(Response1 response) {
                          super.onNext(response);

                    }
                });
複製代碼

好啦,本篇文章到這裏就結束啦~

相關文章
相關標籤/搜索