1. 程式人生 > >Retrofit請求資料對錯誤以及網路異常的處理

Retrofit請求資料對錯誤以及網路異常的處理

異常處理

Retrofit本身會丟擲HttpException,Gson解析會丟擲解析異常,
此外我們還應該處理與伺服器約定好的“異常”,即上一篇提到的返回資料中result欄位值不會0的情況

這裡要先解決一個問題,就是Gson構建的物件,通過註解定義key名,以變數的型別定value的型別,
但如果同樣的key在不同情況下屬於不同的資料型別,就會出問題。

假如伺服器返回格式是

{
    "result":"結果代號,0表示成功",
    "msg":"成功返回時是訊息資料列表,失敗時是異常訊息文字"
}

麼msg究竟應該定義為String,還是一個List呢

我找到的解決方法就是:
註冊一個自定義的轉換類GsonResponseBodyConverter
先用一個只含result變數的Model類去解析獲得result值
如果失敗,則用msg是String的Model類再去解析msg值,然後組成一個ResultException
如果成功,則按照原本的Model類解析

程式碼如下:

class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final Type type;

    GsonResponseBodyConverter(Gson gson, Type type) {
        this.gson = gson;
        this.type = type;
    }

    @Override
    public T convert(ResponseBody value) throws IOException{
        String response = value.string();
        try
{ Log.d("Network", "response>>" + response); //ResultResponse 只解析result欄位 ResultResponse resultResponse = gson.fromJson(response, ResultResponse.class); if (resultResponse.getResult() == 0){ //result==0表示成功返回,繼續用本來的Model類解析 return
gson.fromJson(response, type); } else { //ErrResponse 將msg解析為異常訊息文字 ErrResponse errResponse = gson.fromJson(response, ErrResponse.class); throw new ResultException(resultResponse.getResult(), errResponse.getMsg()); } } finally { } } }

這個類用於捕獲伺服器約定的錯誤型別

public class ResultException extends RuntimeException {

    private int errCode = 0;

    public ResultException(int errCode, String msg) {
        super(msg);
        this.errCode = errCode;
    }

    public int getErrCode() {
        return errCode;
    }
}

拷貝原生的ResponseConverterFactory,將GsonResponseBodyConverter替換為前面我們自定義的

public class ResponseConverterFactory extends Converter.Factory {

    ...
    ...

    @Override
    public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) {
        return new GsonResponseBodyConverter<>(gson, type);
    }

    @Override
    public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) {
        return new GsonRequestBodyConverter<>(gson, type);
    }

}

然後在構建Retrofit時註冊這個工廠類

Retrofit = new Retrofit.Builder()
                .baseUrl(API_SERVER + "/")
                //註冊自定義的工廠類
                .addConverterFactory(ResponseConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(mOkHttpClient)
                .build();

這樣就完成了Retrofit也可以丟擲伺服器約定異常

然後就是具體的處理:

public abstract class AbsAPICallback<T> extends Subscriber<T> {

    //對應HTTP的狀態碼
    private static final int UNAUTHORIZED = 401;
    private static final int FORBIDDEN = 403;
    private static final int NOT_FOUND = 404;
    private static final int REQUEST_TIMEOUT = 408;
    private static final int INTERNAL_SERVER_ERROR = 500;
    private static final int BAD_GATEWAY = 502;
    private static final int SERVICE_UNAVAILABLE = 503;
    private static final int GATEWAY_TIMEOUT = 504;
    //出錯提示
    private final String networkMsg;
    private final String parseMsg;
    private final String unknownMsg;

    protected AbsAPICallback(String networkMsg, String parseMsg, String unknownMsg) {
        this.networkMsg = networkMsg;
        this.parseMsg = parseMsg;
        this.unknownMsg = unknownMsg;
    }


    @Override
    public void onError(Throwable e) {
        Throwable throwable = e;
        //獲取最根源的異常
        while(throwable.getCause() != null){
            e = throwable;
            throwable = throwable.getCause();
        }

        ApiException ex;
        if (e instanceof HttpException){             //HTTP錯誤
            HttpException httpException = (HttpException) e;
            ex = new ApiException(e, httpException.code());
            switch(httpException.code()){
                case UNAUTHORIZED:
                case FORBIDDEN:
                    onPermissionError(ex);          //許可權錯誤,需要實現
                    break;
                case NOT_FOUND:
                case REQUEST_TIMEOUT:
                case GATEWAY_TIMEOUT:
                case INTERNAL_SERVER_ERROR:
                case BAD_GATEWAY:
                case SERVICE_UNAVAILABLE:
                default:
                    ex.setDisplayMessage(networkMsg);  //均視為網路錯誤
                    onError(ex);
                    break;
            }
        } else if (e instanceof ResultException){    //伺服器返回的錯誤
            ResultException resultException = (ResultException) e;
            ex = new ApiException(resultException, resultException.getErrCode());               
            onResultError(ex);  
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException){
            ex = new ApiException(e, ApiException.PARSE_ERROR);
            ex.setDisplayMessage(parseMsg);            //均視為解析錯誤
            onError(ex);
        } else {
            ex = new ApiException(e, ApiException.UNKNOWN);
            ex.setDisplayMessage(unknownMsg);          //未知錯誤
            onError(ex);
        }
    }


    /**
     * 錯誤回撥
     */
    protected abstract void onError(ApiException ex);

    /**
     * 許可權錯誤,需要實現重新登入操作
     */
    protected abstract void onPermissionError(ApiException ex);

    /**
     * 伺服器返回的錯誤
     */
    protected abstract void onResultError(ApiException ex);

    @Override
    public void onCompleted() {

    }

}

自定義ApiException,攜帶了異常程式碼和資訊,以及根源Throwable,足夠呼叫者需要

public class ApiException extends Exception {

    private final int code;
    private String displayMessage;

    public static final int UNKNOWN = 1000;
    public static final int PARSE_ERROR = 1001;

    public ApiException(Throwable throwable, int code) {
        super(throwable);
        this.code = code;
    }

    public int getCode() {
        return code;
    }
    public String getDisplayMessage() {
        return displayMessage;
    }
    public void setDisplayMessage(String msg) {
        this.displayMessage = msg + "(code:" + code + ")";
    }
}