RxJava結(jié)合Retrofit對(duì)網(wǎng)絡(luò)請(qǐng)求結(jié)果的統(tǒng)一處理

不同的網(wǎng)絡(luò)請(qǐng)求有不同的返回結(jié)果撬腾,當(dāng)同時(shí)也有很多相同的地方耕拷,比如數(shù)據(jù)的整體結(jié)構(gòu)可以是這樣:

{
    "status": 1000, 
    "msg": "調(diào)用權(quán)限失敗", 
    "data": {
            ***
            ***
    }
}

如果接口數(shù)據(jù)的設(shè)計(jì)如上舒帮,那么每個(gè)請(qǐng)求都會(huì)有如下三點(diǎn)相同的部分

  1. 狀態(tài)碼
  2. 網(wǎng)絡(luò)異常
  3. 相同的網(wǎng)絡(luò)請(qǐng)求策略

既然有相同的部分儿惫,那么就有必要對(duì)相同的部分統(tǒng)一處理

主要功能圖解

整體采用MVP設(shè)計(jì)模式如下


MVP架構(gòu)

其中ModelPresenter為所有網(wǎng)絡(luò)請(qǐng)求的Presenter嗦随,如下

ModelPresenter

DataSevice為Retrofit請(qǐng)求接口如下

DataService

網(wǎng)絡(luò)層的整體流程如下

網(wǎng)絡(luò)層流程

其中第三層返回的是HttpBean<T>列荔,第二層返回的是業(yè)務(wù)層需要的T類型

具體實(shí)現(xiàn)

模型設(shè)計(jì)

在和后臺(tái)對(duì)接的時(shí)候,定義一個(gè)統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)枚尼,這樣才好統(tǒng)一處理狀態(tài)碼贴浙,利用泛型,我們可以設(shè)計(jì)接口返回的數(shù)據(jù)模型為

public class HttpBean<T> {
    private String msg;
    private T data;
    private int status;
}

不同的網(wǎng)絡(luò)請(qǐng)求只需要傳入相應(yīng)的數(shù)據(jù)模型即可署恍,那么利用retrofit請(qǐng)求數(shù)據(jù)的接口如下

public interface DataService {
    @GET(RequestCons.MY_BOX)
    Observable<HttpBean<BoxData>> getBox(@Query("client_id") String client_id, @Query("client_secret") String secret, @Query("visit_user_id") long user_id);

    @GET(RequestCons.COMMENTS_LIST)
    Observable<HttpBean<CommentData>> getComments(@Query("client_id") String client_id, @Query("client_secret") String secret, @Query("object_id") long object_id);

    @GET(RequestCons.TOPIC)
    Observable<HttpBean<TopicData>> getTopic(@Query("client_id") String client_id, @Query("client_secret") String secret, @Query("id") long id);
}

業(yè)務(wù)層向模型層請(qǐng)求數(shù)據(jù)的接口如下

public interface ModelPresenter {
    /**     * 下載box數(shù)據(jù)接口     */
    Observable<BoxData> loadBoxData(String client_id, String secret, long user_id);

    /**     * 下載評(píng)論數(shù)據(jù)接口     */
    Observable<CommentData> loadCommentData(String client_id, String secret, long object_id);

    /**     * 下載Topic商品     */
    Observable<TopicData> loadTopic(String client_id, String secret, long id);
}

通過對(duì)比兩個(gè)接口崎溃,可以發(fā)現(xiàn)業(yè)務(wù)層無(wú)需關(guān)心狀態(tài)碼了,只會(huì)拿到Observable<T>而不是Obervable<HttpBean<T>>

ModelPresenterImpl的實(shí)現(xiàn)

ModelPresenterImpl繼承自BaseModelImpl,本身的實(shí)現(xiàn)其實(shí)很簡(jiǎn)單锭汛,主要工作就是調(diào)用DataService對(duì)應(yīng)的方法笨奠,然后過濾狀態(tài)碼,代碼如下

public class ModelPresenterImpl extends BaseModelImpl implements ModelPresenter {
    @Override
    public Observable<BoxData> loadBoxData(String client_id, String secret, long user_id) {
        return filterStatus(mDataService.getBox(client_id,secret,user_id));
    }
    @Override
    public Observable<CommentData> loadCommentData(String client_id, String secret, long object_id) {
        return filterStatus(mDataService.getComments(client_id,secret,object_id));
    }
    @Override
    public Observable<TopicData> loadTopic(String client_id, String secret, long id) {
        return filterStatus(mDataService.getTopic(client_id,secret,id));
    }
}
BaseModelImpl的實(shí)現(xiàn)

BaseModelImpl做了以下兩點(diǎn)工作

  1. 創(chuàng)建OkHttpClient唤殴、Retrofit般婆、DataService
public BaseModelImpl() {
    this.baseUrl = RequestCons.BASE_URL;
    OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .build();
    mRetrofit = new Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();
    mDataService = mRetrofit.create(DataService.class);
}
  1. 利用Rxjava的map操作符過濾狀態(tài)碼
/** * 給返回結(jié)果去掉狀態(tài)碼等屬性,
 * 如果是查詢出錯(cuò),則返回狀態(tài)碼對(duì)應(yīng)的描述給用戶
 * @param observable
 * @return
 */
public Observable filterStatus(Observable observable){
    return observable.map(new ResultFilter());
}
private class ResultFilter<T> implements Func1<HttpBean<T>, T> {
    @Override
    public T call(HttpBean<T> tHttpBean) {
        if (tHttpBean.getStatus() != 1){
            throw new ApiException(tHttpBean.getStatus());
        }
        return tHttpBean.getData();
    }
}

此處代碼是一個(gè)關(guān)鍵點(diǎn),利用操作符map給請(qǐng)求的數(shù)據(jù)"去殼"朵逝,只返回給業(yè)務(wù)層所需要的模型蔚袍,如果當(dāng)前請(qǐng)求的狀態(tài)碼不是成功的標(biāo)志,那么拋出異常,交給應(yīng)用層的OnError處理啤咽,確保應(yīng)用層的onNext方法只處理成功的結(jié)果晋辆,純粹專一。

配置狀態(tài)碼過濾器

狀態(tài)碼過濾器一共需要2個(gè)類

  1. 常量說(shuō)明類
public class ResponseCons {
    public static final int STATUS_SUCCESS  = 1;
    public static final String SUCCESS_MSG = "成功";

    public static final int STATU_1000 = 1000;
    public static final String FAILURE_1000 = "調(diào)用權(quán)限失敗";
}
  1. 狀態(tài)碼匹配工具類
public class StatusUtils {
    public static class StatusResult{
        public int status;
        public String desc;
        public boolean isSuccess;
    }
    private static StatusResult mStatusResult = new StatusResult();
    public static StatusResult judgeStatus(int status) {
        String desc = "";
        boolean isSuccess = false;
        switch (status) {
            case ResponseCons.STATUS_SUCCESS:
                desc = ResponseCons.SUCCESS_MSG;
                isSuccess = true;
                break;
            case ResponseCons.STATU_1000:
                desc = ResponseCons.FAILURE_1000;
                break;
        }
        mStatusResult.status = status;
        mStatusResult.desc = desc;
        mStatusResult.isSuccess = isSuccess;
        return mStatusResult;
    }
}

在BaseModelImpl中對(duì)網(wǎng)絡(luò)請(qǐng)求結(jié)果的狀態(tài)碼進(jìn)行判斷宇整,如果不是標(biāo)志成功的狀態(tài)碼瓶佳,那么就拋出一個(gè)異常,在異常中利用狀態(tài)碼匹配工具類找到對(duì)應(yīng)錯(cuò)誤描述并且返回

public class ApiException extends RuntimeException {
    public ApiException(int status) {
        super(getErrorDesc(status));
    }
    private static String getErrorDesc(int status){
        return StatusUtils.judgeStatus(status).desc;
    }
}

隨著業(yè)務(wù)的擴(kuò)展鳞青,如出現(xiàn)新的狀態(tài)碼霸饲,那么只需要往常量類和匹配工具類增加狀態(tài)碼和錯(cuò)誤描述即可,不需要更改網(wǎng)絡(luò)層其它代碼臂拓,還可以拓展成將錯(cuò)誤碼和對(duì)應(yīng)描述信息存儲(chǔ)在本地厚脉,當(dāng)成配置文件,那么當(dāng)產(chǎn)品發(fā)布之后胶惰,如果后臺(tái)增加錯(cuò)誤碼傻工,只需要download新的狀態(tài)碼配置文件即可,不需要發(fā)布新版本應(yīng)用孵滞。

其它網(wǎng)絡(luò)錯(cuò)誤處理

以上已經(jīng)實(shí)現(xiàn)了網(wǎng)絡(luò)層的功能中捆,包括發(fā)起請(qǐng)求,解析返回結(jié)果并且統(tǒng)一過濾狀態(tài)碼剃斧,將請(qǐng)求成功的結(jié)果返回到Observable.onNext()轨香,將失敗結(jié)果返回到observable.onError()忽你。

然而網(wǎng)絡(luò)請(qǐng)求并不是一直穩(wěn)定的幼东,所以所有網(wǎng)絡(luò)請(qǐng)求都有可能出現(xiàn)超時(shí)、無(wú)網(wǎng)絡(luò)鏈接或者其它40X科雳,50X錯(cuò)誤根蟹。

因此還需要再做一層錯(cuò)誤過濾,在Retrofit中糟秘,所有的異常都會(huì)拋出简逮,并且最終由Observable的onError接收,所以我們可以自定義一個(gè)FilterSubscriber繼承自Subscriber尿赚,實(shí)現(xiàn)onError接口散庶,對(duì)傳入的throwable參數(shù)進(jìn)行判處理,代碼如下

public abstract class FilterSubscriber<T> extends Subscriber<T> {
    public String error;
    @Override
    public abstract void onCompleted();
    @Override
    public void onError(Throwable e) {
        if (e instanceof TimeoutException || e instanceof SocketTimeoutException
            || e instanceof ConnectException){
            error = "超時(shí)了";
        }else if (e instanceof JsonSyntaxException){
            error = "Json格式出錯(cuò)了";
            //假如導(dǎo)致這個(gè)異常觸發(fā)的原因是服務(wù)器的問題凌净,那么應(yīng)該讓服務(wù)器知道悲龟,所以可以在這里
            //選擇上傳原始異常描述信息給服務(wù)器
        }else {
            error = e.getMessage();
        }
    }
}

由于我們提取出異常處理類,在異常處理類的onError( )中統(tǒng)一對(duì)所有異常進(jìn)行處理冰寻,所以當(dāng)一些異常確定是或者疑似是服務(wù)器的bug须教,抑或是未知bug,我們應(yīng)該及時(shí)上報(bào)服務(wù)器,讓服務(wù)器收集錯(cuò)誤信息轻腺,及時(shí)修復(fù)乐疆,所以在onError( )中選擇上傳數(shù)據(jù)請(qǐng)求的異常信息是一個(gè)不錯(cuò)的選擇。當(dāng)然服務(wù)器的異常也可以后臺(tái)自己收集贬养,這里只是提供一種策略而已挤土。

應(yīng)用層調(diào)用

做完了發(fā)送請(qǐng)求,解析數(shù)據(jù)误算,錯(cuò)誤處理耕挨,最后就是應(yīng)用層調(diào)用了,代碼如下:

@Overridepublic void loadTopicSuccess() {
    Observable<TopicData> observable = mModelPresenter.loadTopic("bt_app_ios", "9c1e6634ce1c5098e056628cd66a17a5", 1346);
    observable.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new FilterSubscriber<TopicData>() {
                @Override
                public void onCompleted() {
                    MLog.d("Topic信息下載完畢");
                }
                @Override
                public void onNext(TopicData data) {
                    mMainView.showSuccess(data);
                }
                @Override
                public void onError(Throwable e) {
                    super.onError(e);
                    mMainView.showError(error);
                }
            });
}

需要注意的是尉桩,在onError(Throwable e){ }中第一行代碼需要super.onError(e),然后接下去的異常信息的描述是error字符串筒占。

做完以上工作之后,往后如果需要添加新的接口蜘犁,那么只需要以下幾步

  1. 在requestCons添加新的接口的文件路徑
  2. 增加相應(yīng)的bean文件
  3. 在DataService中添加新的接口方法
  4. 在ModelPresenter添加新的接口方法并且在Impl中實(shí)現(xiàn)

而不需要再處理以下內(nèi)容

  1. 客戶端的創(chuàng)建
  2. 狀態(tài)碼過濾
  3. 網(wǎng)絡(luò)異常過濾

上傳的源碼使用MVP設(shè)計(jì)模式的思想翰苫,如果想了解如何使用MVP的同學(xué)可以下載看看。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末这橙,一起剝皮案震驚了整個(gè)濱河市奏窑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屈扎,老刑警劉巖埃唯,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鹰晨,居然都是意外死亡墨叛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門模蜡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)漠趁,“玉大人,你說(shuō)我怎么就攤上這事忍疾〈炒” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵卤妒,是天一觀的道長(zhǎng)甥绿。 經(jīng)常有香客問我,道長(zhǎng)则披,這世上最難降的妖魔是什么共缕? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮收叶,結(jié)果婚禮上骄呼,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好蜓萄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布隅茎。 她就那樣靜靜地躺著,像睡著了一般嫉沽。 火紅的嫁衣襯著肌膚如雪辟犀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天绸硕,我揣著相機(jī)與錄音堂竟,去河邊找鬼。 笑死玻佩,一個(gè)胖子當(dāng)著我的面吹牛出嘹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咬崔,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼税稼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了垮斯?” 一聲冷哼從身側(cè)響起郎仆,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兜蠕,沒想到半個(gè)月后扰肌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熊杨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年曙旭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猴凹。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夷狰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出郊霎,到底是詐尸還是另有隱情,我是刑警寧澤爷绘,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布书劝,位于F島的核電站,受9級(jí)特大地震影響土至,放射性物質(zhì)發(fā)生泄漏购对。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一陶因、第九天 我趴在偏房一處隱蔽的房頂上張望骡苞。 院中可真熱鬧,春花似錦、人聲如沸解幽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)躲株。三九已至片部,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霜定,已是汗流浹背档悠。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留望浩,地道東北人辖所。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像磨德,于是被迫代替她去往敵國(guó)和親奴烙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理剖张,服務(wù)發(fā)現(xiàn)切诀,斷路器,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • 我從去年開始使用 RxJava 搔弄,到現(xiàn)在一年多了幅虑。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy閱讀 5,473評(píng)論 7 62
  • 前言我從去年開始使用 RxJava 顾犹,到現(xiàn)在一年多了倒庵。今年加入了 Flipboard 后,看到 Flipboard...
    占導(dǎo)zqq閱讀 9,164評(píng)論 6 151
  • 文章轉(zhuǎn)自:http://gank.io/post/560e15be2dca930e00da1083作者:扔物線在正...
    xpengb閱讀 7,032評(píng)論 9 73
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法炫刷,類相關(guān)的語(yǔ)法擎宝,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法浑玛,異常的語(yǔ)法绍申,線程的語(yǔ)...
    子非魚_t_閱讀 31,633評(píng)論 18 399