Retrofit自定義GsonConverter處理所有請求錯誤情況解決方案
直接進正題
通常從服務端拿到的JSON資料格式大概如下:
{ "code":1, "message":"查詢成功", "detail":{"aa":"123","bb":"123","cc":"123"} }
因此通常我們會定義一個實體類來解析對應的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欄位表示狀態,比如以下值可能代表了不同的含義
- code = 1, 表示成功, 不等於1代表錯誤
- code = -101, 表示token過期
- code = -102, 表示手機號碼已經註冊
- 等等等
如果我們按照正常的Retrofit+RxJava邏輯來處理,寫出來的程式碼如下所示:
//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); } } }
使用的時候:
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 , 那你不是每個地方全部都得改, 想想就可怕!

解決辦法
Retrofit retrofit = new Retrofit.Builder().baseUrl(ApiService.ENDPOINT) .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build();
addConverterFactory(GsonConverterFactory.create()) 這句程式碼是為了用Gson把服務端返回的json資料解析成實體的, 那就從這裡入手,可以自己定義一個GsonConverter,擴充套件一下原來的功能
先分析一下預設的GsonConverter怎麼寫的, 由三個類組成:
- GsonConverterFactory // GsonConverter 工廠類, 用來建立GsonConverter
- GsonResponseBodyConverter // 處理ResponseBody
- GsonRequestBodyConverter // 處理RequestBody
從名字就很容易看出每個類是幹嘛的, GsonResponseBodyConverter這個類肯定是關鍵, 看一下這個類:
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 ! = 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); } });
好啦,本篇文章到這裡就結束啦~