Retrofit與Rxjava的探索實踐

本文主要參考此篇文章力作,原文鏈接
[給 Android 開發(fā)者的 RxJava 詳解]
(http://gank.io/post/560e15be2dca930e00da1083#toc_3)


寫在前面
最近在摸索著Rxjava筒主,學(xué)了一大半,但是深知要實踐與理論結(jié)合才能學(xué)得快也記得牢斩个,然而最好的實踐是什么呢?可能是我學(xué)得還不夠深,覺得它的好處在網(wǎng)絡(luò)請求這邊特別明顯雁仲,于是網(wǎng)絡(luò)請求網(wǎng)絡(luò)請求網(wǎng)絡(luò)請求疗我。咆畏。。
發(fā)現(xiàn)有做得更好的東西吴裤,那就是Retrofit與Rxjava這兩個小情侶特別好旧找,所以就再次看了一下這兩個的實踐,發(fā)現(xiàn)麦牺,真的有了一個新大陸~然后就沒什么好說的了钮蛛,搞起唄。
本次實踐基于androidstudio剖膳,所以很多庫的依賴都使用gradle來配置


實踐1 庫的安裝

首先依賴rxjava

  compile 'io.reactivex:rxjava:x.y.z'
  compile 'io.reactivex:rxandroid:1.0.1'

接下來依賴retrofit

compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'//使用Gson解析
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'//異常時的處理

實踐2 處理場景

我們假設(shè)使用的場景是輸入賬號密碼魏颓,請求網(wǎng)絡(luò)進行賬號驗證,驗證成功就直接登錄吱晒,不過在登錄之前需要獲取到Token琼开,根據(jù)Token和輸入的賬號密碼進行登錄驗證。
所以我們需要兩個方法枕荞,一個獲取token柜候,一個登錄返回結(jié)果。
假設(shè)我們返回的數(shù)據(jù)結(jié)構(gòu)是固定的躏精,就像以下:

{
  "code":0,
  "message":"success",
  "data":{
  ...
  }
}

實踐3 代碼實現(xiàn)

  • 首先有基礎(chǔ)的Retrofit和rxjava的請求渣刷,這邊叫RxReService
public interface RxReService {
@POST("user/login")
    Observable<String> login(
            @Query("token") String token,
            @Query("username") String name,
            @Query("password") String psd);
}
@POST("token")
    Observable<String> getToken();

這是一個接口,通用標(biāo)注的方式傳入url矗烛,使用query方式添加參數(shù)

  • 接口寫好了辅柴,看一下實際的調(diào)用箩溃,叫RxReRequest
public class RxReRequest{
    private static String BASEURL = "https://www.xxxx.com/";
    private static RxReService mRxReService;
}
     public static void initRxRe() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASEURL)
                .build();
        mRxReService = retrofit.create(RxReService.class);
    }
     public static Observable<String> getToken(){
        return  mRxReService.getToken();
    }
     public static Observable<String> login(String name,String psd,String token){
        return mRxReService.login(token,name,psd);
    }

其實就兩個方法,getToken和login碌嘀,這邊是對其請求進行簡單封裝

  • 簡單使用
RxReRequestHelper.login("", "", new ProgressSubscribe<String>(new SubscriberOnNextListener<String>() {
            @Override
            public void onNext(String result) {

            }
        }, MainActivity.this));
        RxReRequest.getToken().subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(String userTokenResultData) {
                //對String進行解析
                ...
                //解析完得到toekn
                String token = token.getToken();
                RxReRequest.login("name","psd",token).subscribe(new Subscriber<String>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(String comResultResultData) {
 //再次解析
                        ...
                    }
                });
            }
        });

一句我的天啊涣旨,日了狗,這代碼股冗。霹陡。。
莫急莫急止状,待我慢慢道來烹棉。
我們這邊是用比較復(fù)雜的請求才能看出rxjava和retrofit的便利之處

實踐4 封裝

我們首先來說一下
首先是使用gson解析的話,retrofit已經(jīng)做了很好的處理怯疤,只需在initRxRe這個地方添加一個參數(shù)

 .addConverterFactory(GsonConverterFactory.create())//添加Gson解析庫

順便說一下這個

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加取消訂閱時取消http請求

這個是當(dāng)取消訂閱時自動取消http請求
有了以上這些浆洗,我們就可以進行后面的工作了

4-1 請求結(jié)束自動解析

添加以上兩個參數(shù)后,我們的RxReService就變成

Observable<ComResult> login(
            @Query("token") String token,
            @Query("username") String name,
            @Query("password") String psd);
 @POST("token")
    Observable<UserToken> getToken();

返回的類型就直接轉(zhuǎn)換成我們要的最終類型

RxReRequest的兩個方法變成

 public static Observable<UserToken> getToken(){
        return  mRxReService.getToken();
    }
    public static Observable<ComResult> login(String name, String psd, String token){
        return mRxReService.login(token,name,psd);
    }

因此最后的使用變成

 RxReRequest.getToken().subscribe(new Subscriber<UserToken>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(UserToken userTokenResultData) {
                RxReRequest.login("name","psd",userTokenResultData.getToken()).subscribe(new Subscriber<ComResult>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(ComResult comResultResultData) {
                    }
                });
            }
        });

Gson解析省略掉了集峦,不過看了還是有些別扭我們發(fā)現(xiàn)其實需要的就onErr和onNext兩個方法而已伏社,還有我們解析的會將整個返回值都每次解析出來,但是我們的返回格式是固定的呀塔淤,我每次只要根據(jù)code拿數(shù)據(jù)實例就好了洛口,那就是提前預(yù)解析

4-2 提前預(yù)解析

再啰嗦一下,假設(shè)我們的返回結(jié)果是

{
  "code":0,
  "message":"success",
  "data":{
  ...
  }
}

那我們就可以每次先對結(jié)果進行解析凯沪,如果是code不等于0那就不解析了,所以我們需要這么一個類

public class ResultData<T> {
    private int resultCode;
    private String message;
    private T data;

    public int getResultCode() {
        return resultCode;
    }

    public String getMessage() {
        return message;
    }

    public T getData() {
        return data;
    }
}

因為這個實例數(shù)據(jù)是不固定的买优,所以只能用泛型來做妨马,所以我們的RxReService又變了

@POST("user/login")
   Observable<ResultData<ComResult>> login(
           @Query("token") String token,
           @Query("username") String name,
           @Query("password") String psd);

跟著變的RxReRequest

public static Observable<ResultData<UserToken>> getToken() {
        return mRxReService.getToken();
    }

    public static Observable<ResultData<ComResult>> login(String name, String psd, String token) {
        return mRxReService.login(token, name, psd);
    }

我們在哪里進行預(yù)解析呢?當(dāng)然是用rxjava牛逼閃閃的map關(guān)鍵字了杀赢。我們預(yù)解析的目的是當(dāng)code不為0調(diào)用onErr方法烘跺,而不僅僅是訪問出錯,這樣我們在onNext那邊只需關(guān)心正確的數(shù)據(jù)就是了
我們知道m(xù)ap的參數(shù)

 public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {
        return lift(new OperatorMap<T, R>(func));
    }

所以我們需要定義一個func1來繼承這個Func1

public class HttpResultFunc<T> implements Func1<ResultData<T>, T> {
    @Override
    public T call(ResultData<T> tResultData) {
        if (tResultData.getResultCode() != 0) {
            throw new ResultException(tResultData.getResultCode(), tResultData.getMessage());
        }
        return tResultData.getData();
    }
}

我們怎么做的呢脂崔?就是當(dāng)code不等于0就拋出一個異常滤淳,讓onErr接收到這個異常,但是這個異常又要包含到數(shù)據(jù)異常的信息砌左,所以我們還需要自己定義一個異常

public class ResultException extends RuntimeException {

    private int errorCode;
    private String errMessage;

    public ResultException(int errorCode, String errMessage) {
        this.errorCode = errorCode;
        this.errMessage = errMessage;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public String getErrMessage() {
        return errMessage;
    }
}

里面包含了errCode和errMessage
好這邊我們改變一下RxReRequest

public static void getToken(Subscriber<UserToken> subscriber) {
       mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribe(subscriber);
   }

   public static void login(String name, String psd, String token, Subscriber<ComResult> subscriber) {
       mRxReService.login(token, name, psd).map(new HttpResultFunc<ComResult>()).subscribe(subscriber);
   }

我們使用了map對其進行預(yù)解析
好的脖咐,我們看看怎么使用

 RxReRequest.getToken(new Subscriber<UserToken>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(UserToken userToken) {
                RxReRequest.login("", "", userToken.getToken(), new Subscriber<ComResult>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(ComResult comResult) {

                    }
                });
            }
        });

這邊onNext里面都是實在的數(shù)據(jù),不會再有數(shù)據(jù)為空時會跑進去了
還有一個小東西汇歹,我們發(fā)現(xiàn)onComplete是沒用的屁擅,那我們也給他去掉吧~~怎么做,自己定義咯

4-3 去掉onComplete

我們自定義一個觀察者产弹,也是抽象類

public abstract class ResutSubscriber<T> extends Subscriber<T>{
    @Override
    public void onCompleted() {
        //結(jié)束
    }
}

當(dāng)然繼承免不了派歌,這邊可以做個打印啊還是啥的,當(dāng)然如果這個有用到,就不能這樣做啦
好的胶果,跟著改變的是這邊RxReRequest

    public static void getToken(ResutSubscriber<UserToken> subscriber) {
        mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribe(subscriber);
    }

    public static void login(String name, String psd, String token, ResutSubscriber<ComResult> subscriber) {
        mRxReService.login(token, name, psd).map(new HttpResultFunc<ComResult>()).subscribe(subscriber);
    }

這樣使用的話就簡便了一點點

RxReRequest.getToken(new ResutSubscriber<UserToken>() {

            @Override
            public void onError(Throwable e) {
                if (e instanceof ResultException){
                    Log.e("err",((ResultException)e).getErrMessage() +((ResultException)e).getErrorCode() );
                }else {
                    Log.e("err","請求異常");
                }
            }

            @Override
            public void onNext(UserToken userToken) {
                RxReRequest.login("", "", userToken.getToken(), new ResutSubscriber<ComResult>() {

                    @Override
                    public void onError(Throwable e) {
                        if (e instanceof ResultException){
                            Log.e("err",((ResultException)e).getErrMessage() +((ResultException)e).getErrorCode() );
                        }else {
                            Log.e("err","請求異常");
                        }
                    }

                    @Override
                    public void onNext(ComResult comResult) {

                    }
                });
            }
        });

這樣沒用的代碼就沒掉了匾嘱,不過這個嵌套的網(wǎng)絡(luò)請求看了總是不開心,怎么辦呢早抠。霎烙。。我們知道rxjava還有flatMap贝或,那就用上吧吼过。

4-4使用flatmap處理需要兩級請求的情況

flatmap的解釋很不好說很不好說也不知道怎么說,在這個場景具體的我們可以理解為咪奖,請求登錄的話會先要求獲取token盗忱,獲取到了在執(zhí)行登錄的請求,那就上代碼吧羊赵。
我們把兩個方法合成一個方法
于是RxReRequest的登錄方法里面要做兩件事趟佃,一件是獲取token,一件是登錄昧捷,所以

  public static void login(final String name, final String psd, final ResutSubscriber<ComResult> subscriber) {
        mRxReService.getToken().map(new HttpResultFunc<UserToken>()).flatMap(new Func1<UserToken, Observable<ComResult>>() {
            @Override
            public Observable<ComResult> call(UserToken userToken) {
                return mRxReService.login(userToken.getToken(), name, psd).map(new HttpResultFunc<ComResult>());
            }
        }).subscribe(subscriber);
    }

最后的使用

    RxReRequest.login("", "", new ResutSubscriber<ComResult>() {
            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(ComResult comResult) {

            }
        });

這下闲昭,整個世界都清凈了~~~~

4-5 線程切換

我們知道安卓是不能在主線程進行耗時操作的,包括網(wǎng)絡(luò)請求靡挥,所以我們?nèi)绻瓷厦娴膩碜龅脑挿址昼姃伄惓P蚓兀赃€需要一把殺手锏,就是線程切換跋破。
使用Rxjava可以很方便進行線程切換簸淀,當(dāng)進行網(wǎng)絡(luò)請求時,線程切換到子線程(另外新建一個線程)毒返,請求結(jié)束后切換到主線程
RxReRequest里面的請求

 public static void getToken(ResutSubscriber<UserToken> subscriber) {
        mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);
    }
 public static void login(final String name, final String psd, final ResutSubscriber<ComResult> subscriber) {
        mRxReService.getToken().map(new HttpResultFunc<UserToken>()).flatMap(new Func1<UserToken, Observable<ComResult>>() {
            @Override
            public Observable<ComResult> call(UserToken userToken) {
                return mRxReService.login(userToken.getToken(), name, psd).map(new HttpResultFunc<ComResult>());
            }
        }).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);
    }

subscribeOn表示事件發(fā)生時所在的線程租幕,這邊指定在新建的線程,observeOn則指定觀察者發(fā)生的事件的線程拧簸,我們指定在主線程劲绪。

當(dāng)需要loading的時候

因為我們網(wǎng)絡(luò)請求的觀察者都發(fā)生在主線程,所以我們還是自己定義一個觀察者盆赤,里面包含開始和結(jié)束贾富,在開始的地方顯示dialog,在結(jié)束或者出錯的地方做響應(yīng)的取消dialog或者顯示錯誤信息牺六,還可以根據(jù)出錯的errcode進行靈活展示

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

    private Context mContext;
    private Handler mHandler;

    public ProgressSubscribe(Context mContext) {
        this.mContext = mContext;
        mHandler = new Handler();
    }

    @Override
    public void onCompleted() {
        //dismissDialog
    }
    @Override
    public void onStart() {
        //showDialog

    }\ @Override
    public void onError(Throwable e) {
        //錯誤時的處理
    }
}

這樣錯誤的進行統(tǒng)一處理祷安,我們就只要關(guān)注有數(shù)據(jù)的業(yè)務(wù)邏輯就行了。兔乞。汇鞭。凉唐。



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市霍骄,隨后出現(xiàn)的幾起案子台囱,更是在濱河造成了極大的恐慌,老刑警劉巖读整,帶你破解...
    沈念sama閱讀 223,207評論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件簿训,死亡現(xiàn)場離奇詭異,居然都是意外死亡米间,警方通過查閱死者的電腦和手機强品,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評論 3 400
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屈糊,“玉大人的榛,你說我怎么就攤上這事÷呷瘢” “怎么了夫晌?”我有些...
    開封第一講書人閱讀 170,031評論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昧诱。 經(jīng)常有香客問我晓淀,道長,這世上最難降的妖魔是什么盏档? 我笑而不...
    開封第一講書人閱讀 60,334評論 1 300
  • 正文 為了忘掉前任凶掰,我火速辦了婚禮,結(jié)果婚禮上蜈亩,老公的妹妹穿的比我還像新娘懦窘。我一直安慰自己,他們只是感情好勺拣,可當(dāng)我...
    茶點故事閱讀 69,322評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鱼填,像睡著了一般药有。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苹丸,一...
    開封第一講書人閱讀 52,895評論 1 314
  • 那天愤惰,我揣著相機與錄音,去河邊找鬼赘理。 笑死宦言,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的商模。 我是一名探鬼主播奠旺,決...
    沈念sama閱讀 41,300評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼蜘澜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了响疚?” 一聲冷哼從身側(cè)響起鄙信,我...
    開封第一講書人閱讀 40,264評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忿晕,沒想到半個月后装诡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,784評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡践盼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,870評論 3 343
  • 正文 我和宋清朗相戀三年鸦采,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咕幻。...
    茶點故事閱讀 40,989評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡渔伯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谅河,到底是詐尸還是另有隱情咱旱,我是刑警寧澤,帶...
    沈念sama閱讀 36,649評論 5 351
  • 正文 年R本政府宣布绷耍,位于F島的核電站吐限,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏褂始。R本人自食惡果不足惜诸典,卻給世界環(huán)境...
    茶點故事閱讀 42,331評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望崎苗。 院中可真熱鬧狐粱,春花似錦、人聲如沸胆数。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽必尼。三九已至蒋搜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間判莉,已是汗流浹背豆挽。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評論 1 275
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留券盅,地道東北人帮哈。 一個月前我還...
    沈念sama閱讀 49,452評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像锰镀,于是被迫代替她去往敵國和親娘侍。 傳聞我的和親對象是個殘疾皇子咖刃,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,995評論 2 361

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