RXJava2+Retrofit2+MVP+RXLifecycle+EventBus+...之你可能需要的那些套路(二)

本文所涉及DEMO已上傳至https://github.com/LegendaryMystic/HYMVP
本人小白一個,文章廢話較多寞奸,如果你覺得talk is cheap,喜歡直接 read the fuck source code纠俭,可跳過直接前往
碼字不易妻熊,如果代碼能夠幫到你,望不吝給個鼓勵的star趁桃,感謝辽话!

本文承接上文RXJava2+Retrofit2+MVP+RXLifecycle+EventBus+...之你可能需要的那些套路(一),我們接著講RxJava+Retrofit結(jié)合MVP架構(gòu)模式在實(shí)際項(xiàng)目中你可能需要的一些基本的套路肄鸽。

套路四:異常、錯誤或解密統(tǒng)一處理

剛開始使用RxJava進(jìn)行網(wǎng)絡(luò)請求時油啤,我們常用onNext作為請求響應(yīng)的回調(diào)典徘,onError異常回調(diào)

      public interface Observer<T> {

    /**
     * Provides the Observer with the means of cancelling (disposing) the
     * connection (channel) with the Observable in both
     * synchronous (from within {@link #onNext(Object)}) and asynchronous manner.
     * @param d the Disposable instance whose {@link Disposable#dispose()} can
     * be called anytime to cancel the connection
     * @since 2.0
     */
    void onSubscribe(@NonNull Disposable d);

    /**
     * Provides the Observer with a new item to observe.
     * <p>
     * The {@link Observable} may call this method 0 or more times.
     * <p>
     * The {@code Observable} will not call this method again after it calls either {@link #onComplete} or
     * {@link #onError}.
     *
     * @param t
     *          the item emitted by the Observable
     */
    void onNext(@NonNull T t);

    /**
     * Notifies the Observer that the {@link Observable} has experienced an error condition.
     * <p>
     * If the {@link Observable} calls this method, it will not thereafter call {@link #onNext} or
     * {@link #onComplete}.
     *
     * @param e
     *          the exception encountered by the Observable
     */
    void onError(@NonNull Throwable e);

    /**
     * Notifies the Observer that the {@link Observable} has finished sending push-based notifications.
     * <p>
     * The {@link Observable} will not call this method if it calls {@link #onError}.
     */
    void onComplete();

}

然而益咬,值得一提的是逮诲,這里onError回調(diào)給我們的是一個Throwable,面對這個Throwable對象,或許我們只能log它的message幽告,而且可能反復(fù)的寫梅鹦。。這個時候就需要封裝了冗锁。

我們希望,像大多數(shù)網(wǎng)絡(luò)請求框架一樣齐唆,簡單的兩個回調(diào)函數(shù):

        /**
         * 請求成功
         *
         * @param response 服務(wù)器返回的數(shù)據(jù)
         */
        public abstract void onSuccess(T response);

        /**
         * 服務(wù)器返回數(shù)據(jù),但code不在約定成功范圍內(nèi)
         *
         * @param msg 服務(wù)器返回的數(shù)據(jù)
         */
        public abstract void onFailure(String msg);

請求成功冻河,返回給我們需要的數(shù)據(jù)展示到UI界面箍邮,請求失敗(比如參數(shù)錯誤等請求邏輯錯誤)芋绸,返回給用戶一條失敗提示媒殉,而對于請求異常(如網(wǎng)絡(luò)異常,數(shù)據(jù)解析失敗..等請求異常)摔敛,我們只需在內(nèi)部統(tǒng)一log記錄便于debug。
一般的全封,服務(wù)器返回給我們的數(shù)據(jù)可能是:

{
 "code": 200,
 "msg": "成功",
 "data": {}
}

對應(yīng)實(shí)體類:

/* http響應(yīng)參數(shù)實(shí)體類
 * 通過Gson解析屬性名稱需要與服務(wù)器返回字段對應(yīng),或者使用注解@SerializedName
 * /
public class BaseResponse<T>{

    private int code;
    private String msg;
    private T data;
    
      /**
     * 是否成功(這里約定200)
     *
     * @return
     */
    public boolean isSuccess() {
        return code == 200 ? true : false;
    }   

我們與后臺約定狀態(tài)碼符合約定規(guī)則時為請求成功马昙,正常情況下如果某一個http請求沒有發(fā)生異常,或者網(wǎng)絡(luò)錯誤刹悴,就會走onNext回調(diào)行楞,現(xiàn)在我們要讓code不等于200的響應(yīng)進(jìn)入失敗回調(diào),

自定義一個ResultException用于捕獲服務(wù)器約定的錯誤類型

public class ResultException extends RuntimeException{


    private  int code;
    private String message;

    public ResultException(int code, String message){
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

自定義一個Gson響應(yīng)體轉(zhuǎn)換類MyGsonResponseBodyConverter


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


    public MyGsonResponseBodyConverter(Gson gson, Type type){
        this.gson = gson;
        this.type = type;
    }
    @Override
    public T convert(@NonNull ResponseBody value) throws IOException {

        String response = value.string();

        BaseResponse<T> result = gson.fromJson(response, BaseResponse.class);
        if (result.isSuccess()){
         
         //對于請求數(shù)據(jù)加密的  可以在這里做解密操作
           return gson.fromJson(jsonStr,type);

        }else {

//拋一個自定義ResultException 傳入失敗時候的狀態(tài)碼土匀,和信息
                throw new ResultException(result.getCode(),result.getMsg());

        }

    }
}

拷貝一份GsonConverterFactory子房,將GsonResponseBodyConverter替換為前面我們自定義的MyGsonResponseBodyConverter

/**
 * A {@linkplain Converter.Factory converter} which uses Gson for JSON.
 * <p>
 * Because Gson is so flexible in the types it supports, this converter assumes that it can handle
 * all types. If you are mixing JSON serialization with something else (such as protocol buffers),
 * you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this instance}
 * last to allow the other converters a chance to see their types.
 */
public final class MyGsonConverterFactory 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 MyGsonConverterFactory 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.
   */
  @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
  public static MyGsonConverterFactory create(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    return new MyGsonConverterFactory(gson);
  }

  private final Gson gson;

  private MyGsonConverterFactory(Gson gson) {
    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);
    
    //返回我們自定義的Gson響應(yīng)體變換器
        return new MyGsonResponseBodyConverter<>(gson, type);
  }

  @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);
  }
}

然后在構(gòu)建Retrofit時注冊這個自定義的MyResponseConverterFactory

        retrofit = new Retrofit.Builder()
                .baseUrl(ApiService.BASE_URL)
                .client(client)
                //然后將下面的GsonConverterFactory.create()替換成我們自定義的MyResponseConverterFactory.create()
                .addConverterFactory(MyResponseConverterFactory.create())
//                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

到此為止與后臺服務(wù)器約定的錯誤類型,拋出到onError回調(diào)中就完成了就轧,舉一反三证杭,對于請求返回Json數(shù)據(jù)加密的,以及返回Json數(shù)據(jù) 格式不固定的 比如:

{
    "result":"結(jié)果代號妒御,0表示成功",
    "msg":"成功返回時是消息數(shù)據(jù)列表解愤,失敗時是異常消息文本"
}
      這里msg究竟應(yīng)該定義為String,還是一個List呢乎莉?

等等都可以在Gson響應(yīng)體轉(zhuǎn)換類GsonResponseBodyConverter中做相應(yīng)的解密或相關(guān)判斷轉(zhuǎn)換處理送讲,這里我就不一一列舉了奸笤。

對于請求異常,為了方便調(diào)試哼鬓,自定義一個ApiException监右,對Throwable做具體的處理

public class ApiException extends Exception {


    //對應(yīng)HTTP的狀態(tài)碼
    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 int code;
    private String message;

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

    public int getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public static ApiException handleException(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:
                    //權(quán)限錯誤,需要實(shí)現(xiàn)重新登錄操作
//                    onPermissionError(ex);
                    break;
                case NOT_FOUND:
                case REQUEST_TIMEOUT:
                case GATEWAY_TIMEOUT:
                case INTERNAL_SERVER_ERROR:
                case BAD_GATEWAY:
                case SERVICE_UNAVAILABLE:
                default:
                    ex.message = "默認(rèn)網(wǎng)絡(luò)異常";  //均視為網(wǎng)絡(luò)錯誤
                    break;
            }
            return ex;
        } else if (e instanceof SocketTimeoutException) {
            ex = new ApiException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "網(wǎng)絡(luò)連接超時异希,請檢查您的網(wǎng)絡(luò)狀態(tài)健盒,稍后重試!";
            return ex;
        } else if (e instanceof ConnectException) {
            ex = new ApiException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "網(wǎng)絡(luò)連接異常宠互,請檢查您的網(wǎng)絡(luò)狀態(tài)味榛,稍后重試!";
            return ex;
        } else if (e instanceof ConnectTimeoutException) {
            ex = new ApiException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "網(wǎng)絡(luò)連接超時予跌,請檢查您的網(wǎng)絡(luò)狀態(tài)搏色,稍后重試!";
            return ex;
        } else if (e instanceof UnknownHostException) {
            ex = new ApiException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "網(wǎng)絡(luò)連接異常券册,請檢查您的網(wǎng)絡(luò)狀態(tài)频轿,稍后重試!";
            return ex;
        } else if (e instanceof NullPointerException) {
            ex = new ApiException(e, ERROR.NULL_POINTER_EXCEPTION);
            ex.message = "空指針異常";
            return ex;
        } else if (e instanceof javax.net.ssl.SSLHandshakeException) {
            ex = new ApiException(e, ERROR.SSL_ERROR);
            ex.message = "證書驗(yàn)證失敗";
            return ex;
        } else if (e instanceof ClassCastException) {
            ex = new ApiException(e, ERROR.CAST_ERROR);
            ex.message = "類型轉(zhuǎn)換錯誤";
            return ex;
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
//                || e instanceof JsonSyntaxException
                || e instanceof JsonSerializer
                || e instanceof NotSerializableException
                || e instanceof ParseException) {
            ex = new ApiException(e, ERROR.PARSE_ERROR);
            ex.message = "解析錯誤";
            return ex;
        } else if (e instanceof IllegalStateException) {
            ex = new ApiException(e, ERROR.ILLEGAL_STATE_ERROR);
            ex.message = e.getMessage();
            return ex;
        } else {
            ex = new ApiException(e, ERROR.UNKNOWN);
            ex.message = "未知錯誤";
            return ex;
        }
    }

    /**
     * 約定異常
     */
    public static class ERROR {
        /**
         * 未知錯誤
         */
        public static final int UNKNOWN = 1000;
        /**
         * 連接超時
         */
        public static final int TIMEOUT_ERROR = 1001;
        /**
         * 空指針錯誤
         */
        public static final int NULL_POINTER_EXCEPTION = 1002;

        /**
         * 證書出錯
         */
        public static final int SSL_ERROR = 1003;

        /**
         * 類轉(zhuǎn)換錯誤
         */
        public static final int CAST_ERROR = 1004;

        /**
         * 解析錯誤
         */
        public static final int PARSE_ERROR = 1005;

        /**
         * 非法數(shù)據(jù)異常
         */
        public static final int ILLEGAL_STATE_ERROR = 1006;

    }

最后烁焙,自定義一個Observer抽象類對請求回調(diào)做具體的處理

public abstract class BaseObserver<T> implements Observer<T> {

        @Override
        public void onSubscribe(Disposable d) {

        }

        @Override
        public void onNext(T response) {

            onSuccess(response);
        }

        @Override
        public void onError(Throwable e) {

//拋出的與服務(wù)器約定的錯誤類型
            if (e instanceof ResultException){
                onFailure(e.getMessage());
            }else {

                String error = ApiException.handleException(e).getMessage();

                _onError(error);
            }

        }

        @Override
        public void onComplete() {
        }

        /**
         * 請求成功
         *
         * @param response 服務(wù)器返回的數(shù)據(jù)
         */
        public abstract void onSuccess(T response);

        /**
         * 服務(wù)器返回數(shù)據(jù)航邢,但code不在約定成功范圍內(nèi)
         *
         * @param msg 服務(wù)器返回的數(shù)據(jù)
         */
        public abstract void onFailure(String msg);


//        public abstract void onError(String errorMsg);

        private void _onSuccess(T responce){

        }

        private void _onFailure(String msg) {

            if (TextUtils.isEmpty(msg)) {
//                ToastUtils.show(R.string.response_return_error);
            } else {
//                ToastUtils.show(msg);
            }
        }
    private void _onError(String err ){

            Log.e("APIException",err);

    }

}

使用的時候在presenter里subscribe自定義的BaseObserver即可


      mModel.getSurvey(did)
                .compose(RxTransformer.transformWithLoading(mView))
                .subscribe(new BaseObserver<Survey>() {
                    @Override
                    public void onSuccess(Survey response) {
                        mView.onGetSurvey(response);
                    }

                    @Override
                    public void onFailure(String msg) {

                    }
                });

廢話有點(diǎn)多了,詳情請見源代碼HYMVP由于篇幅問題骄蝇,后續(xù)諸多套路膳殷,請聽下回分解。

附上源代碼地址:https://github.com/LegendaryMystic/HYMVP

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末九火,一起剝皮案震驚了整個濱河市赚窃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岔激,老刑警劉巖勒极,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異虑鼎,居然都是意外死亡辱匿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門炫彩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匾七,“玉大人,你說我怎么就攤上這事媒楼±肿穑” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵划址,是天一觀的道長扔嵌。 經(jīng)常有香客問我限府,道長,這世上最難降的妖魔是什么痢缎? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任胁勺,我火速辦了婚禮,結(jié)果婚禮上独旷,老公的妹妹穿的比我還像新娘署穗。我一直安慰自己,他們只是感情好嵌洼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布案疲。 她就那樣靜靜地躺著,像睡著了一般麻养。 火紅的嫁衣襯著肌膚如雪褐啡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天鳖昌,我揣著相機(jī)與錄音备畦,去河邊找鬼。 笑死许昨,一個胖子當(dāng)著我的面吹牛懂盐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播糕档,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼莉恼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了速那?” 一聲冷哼從身側(cè)響起类垫,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎琅坡,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體残家,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榆俺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坞淮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茴晋。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖回窘,靈堂內(nèi)的尸體忽然破棺而出诺擅,到底是詐尸還是另有隱情,我是刑警寧澤啡直,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布烁涌,位于F島的核電站苍碟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏撮执。R本人自食惡果不足惜微峰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抒钱。 院中可真熱鬧蜓肆,春花似錦、人聲如沸谋币。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕾额。三九已至早芭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凡简,已是汗流浹背逼友。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秤涩,地道東北人帜乞。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像筐眷,于是被迫代替她去往敵國和親黎烈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 我從去年開始使用 RxJava 匀谣,到現(xiàn)在一年多了照棋。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy閱讀 5,468評論 7 62
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理武翎,服務(wù)發(fā)現(xiàn)烈炭,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 我就是那個愛夢想宝恶,愛追求符隙,愛上進(jìn),愛一切進(jìn)步的正能量的所謂上進(jìn)的人垫毙。我愛一切有上進(jìn)心的人和事霹疫,偏偏這個時候...
    蝸蝸牛1閱讀 167評論 0 0
  • 文/粥小唯 下班溜進(jìn)家門丽蝎,發(fā)現(xiàn)七大姑八大姨還有一個我不認(rèn)識的阿姨整整齊齊圍的坐在沙發(fā)邊,貌似在商量著什么膀藐,看到我回...
    妖精的小尾巴閱讀 469評論 2 6
  • 文/小頤媽 剛過完年屠阻,我的一個下屬跟我說红省,“姐,聽說小靜要離職”栏笆。 “哦类腮,知道了,那按工作安排蛉加,你繼續(xù)做好你的工作...
    小頤媽閱讀 214評論 0 0