RxJava 與 Retrofit 結(jié)合的最佳實踐

文章轉(zhuǎn)自:
https://gank.io/post/56e80c2c677659311bed9841

前言

RxJava和Retrofit也火了一段時間了,不過最近一直在學習ReactNative和Node相關(guān)的姿勢邑彪,一直沒有時間研究這些新東西瞧毙,最近有個項目準備寫,打算先用Android寫一個Demo出來寄症,卻發(fā)現(xiàn)Android的世界發(fā)生了天翻地覆的變化宙彪,EventBus和OKHttp啥的都不見了,RxJava和Retrofit是什么鬼有巧?
好吧释漆,到Github上耐著性子看過了RxJava和Retrofit的介紹和幾個Demo,原來Android的大神Jake Wharton為Retrofit這個項目貢獻了這么多的代碼篮迎,沒有道理不用了男图。
如果你對RxJava不熟悉請先看給 Android 開發(fā)者的 RxJava 詳解這篇文章示姿。
如果你對Retrofit不熟悉就先看Retrofit官網(wǎng)
當然也有很多RxJava與Retrofit的文章享言,但是我覺得很多大家都很糾結(jié)的功能都沒有被總結(jié)出來峻凫,所以才有了此篇文章。
歡迎大家拍磚览露。
接下來進入正文,我是從下面幾個角度去思考RxJava與Retrofit結(jié)合的譬胎。
RxJava如何與Retrofit結(jié)合
相同格式的Http請求數(shù)據(jù)該如何封裝
相同格式的Http請求數(shù)據(jù)統(tǒng)一進行預處理
如何取消一個Http請求 -- 觀察者之間的對決差牛,Oberver VS Subscriber
一個需要ProgressDialog的Subscriber該有的樣子

1.RxJava如何與Retrofit結(jié)合

1.1 基本頁面
先扔出 build.gradle文件的內(nèi)容

dependencies { 
       compile fileTree(dir: 'libs', include: ['*.jar']) 
       testCompile 'junit:junit:4.12' 
       compile 'com.android.support:appcompat-v7:23.2.0' 
       compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxandroid:1.1.0' 
       compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' 
       compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' 
       compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4' 
       compile 'com.google.code.gson:gson:2.6.2' compile 'com.jakewharton:butterknife:7.0.1'}

也就是說本文是基于RxJava1.1.0和Retrofit 2.0.0-beta4來進行的。 添加rxandroid是因為rxjava中的線程問題堰乔。
下面先搭建一個基本的頁面偏化,頁面很簡單,先來看文件目錄結(jié)構(gòu)

目錄結(jié)構(gòu)

activity_main.xml的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/ahdroid"
        xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
        android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin" 
        tools:context=".activity.MainActivity">

       <Button android:id="@+id/click_me_BN" android:layout_width="match_parent"
               android:layout_height="wrap_content" 
               android:layout_alignParentBottom="true"
               android:padding="5dp"android:text="點我" 
               android:textSize="16sp"/> 

       <TextView android:id="@+id/result_TV"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_above="@id/click_me_BN" 
             android:text="Hello World!" 
             android:textSize="16sp"/>
</RelativeLayout>

MainActivity.java的代碼如下:

package com.queen.rxjavaretrofitdemo.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.TextView;

import com.queen.rxjavaretrofitdemo.R;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity { 
      @Bind(R.id.click_me_BN) 
      Button clickMeBN; 
      @Bind(R.id.result_TV) 
      TextView resultTV; 

      @Override 
      protected void onCreate(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState); 
            setContentView(R.layout.activity_main); 
            ButterKnife.bind(this); 
      } 

      @OnClick(R.id.click_me_BN) 
      public void onClick() { 
            getMovie(); 
      } 

      //進行網(wǎng)絡(luò)請求 
      private void getMovie(){
      }
}

注意不要忘記加網(wǎng)絡(luò)權(quán)限

<uses-permission android:name="android.permission.INTERNET"/>

1.2 只用Retrofit
我們準備在getMovie方法中進行網(wǎng)絡(luò)請求镐侯,我們先來看看只使用Retrofit是如何進行的侦讨。
我們使用豆瓣電影的Top250做測試連接,目標地址為

https://api.douban.com/v2/movie/top250?start=0&count=10

至于返回的數(shù)據(jù)格式苟翻,大家自己訪問下鏈接就看到了韵卤,太長就不放進來了。
首先我們要根據(jù)返回的結(jié)果封裝一個Entity崇猫,暫命名為MovieEntity沈条,代碼就不貼了。
接下來我們要創(chuàng)建一個接口取名為MovieService诅炉,代碼如下:

public interface MovieService { 
      @GET("top250") 
      Call<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}

回到MainActivity之中蜡歹,我們來寫getMovie方法的代碼

//進行網(wǎng)絡(luò)請求
private void getMovie(){ 
    String baseUrl = "https://api.douban.com/v2/movie/"; 

    Retrofit retrofit = new Retrofit.Builder() 
        .baseUrl(baseUrl) 
        .addConverterFactory(GsonConverterFactory.create()) 
        .build(); 

    MovieService movieService = retrofit.create(MovieService.class); 
    Call<MovieEntity> call = movieService.getTopMovie(0, 10); 
        call.enqueue(new Callback<MovieEntity>() { 
            @Override 
            public void onResponse(Call<MovieEntity> call, Response<MovieEntity> response){
                resultTV.setText(response.body().toString());
            } 

           @Override 
           public void onFailure(Call<MovieEntity> call, Throwable t) { 
               resultTV.setText(t.getMessage()); 
           } 
    });
}

以上為沒有經(jīng)過封裝的、原生態(tài)的Retrofit寫網(wǎng)絡(luò)請求的代碼涕烧。 我們可以封裝創(chuàng)建Retrofit和service部分的代碼月而,然后Activity用創(chuàng)建一個Callback作為參數(shù)給Call,這樣Activity中只關(guān)注請求的結(jié)果议纯,而且Call有cancel方法可以取消一個請求父款,好像沒Rxjava什么事了,我覺得可以寫到這就下班了~
接下來我們要面對的問題是這樣的 如果我的Http返回數(shù)據(jù)是一個統(tǒng)一的格式痹扇,例如

{ 
  "resultCode": 0, 
  "resultMessage": "成功",
  "data": {}
}

我們?nèi)绾螌Ψ祷亟Y(jié)果進行一個統(tǒng)一的處理呢铛漓?
另外,我的ProgressDialog的show方法應(yīng)該在哪調(diào)用呢鲫构?看樣子只能在getMovie()這個方法里面調(diào)用了浓恶,換個地方發(fā)出請求就要在對應(yīng)的Listener里面寫一遍show()的代碼,其實挺鬧心结笨。
而且錯誤請求我也想集中處理掉不要貼重復的代碼包晰。
我們先來看結(jié)合了Rxjava之后湿镀,事情有沒有變化的可能。當然即便是不用Rxjava伐憾,依舊能夠做很多的封裝勉痴,只是比較麻煩。
如需查看項目代碼 --> 代碼地址:

https://github.com/tough1985/RxjavaRetrofitDemo

選擇Tag -> step1
1.3 添加Rxjava
Retrofit本身對Rxjava提供了支持树肃。
添加Retrofit對Rxjava的支持需要在Gradle文件中添加
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'

當然我們已經(jīng)添加過了蒸矛。
然后在創(chuàng)建Retrofit的過程中添加如下代碼:

Retrofit retrofit = new Retrofit.Builder() 
        .baseUrl(baseUrl) 
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 
        .build();

這樣一來我們定義的service返回值就不在是一個Call了,而是一個Observable
重新定義MovieService

public interface MovieService { 
    @GET("top250") Observable<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}

getMovie方法改為:

//進行網(wǎng)絡(luò)請求
private void getMovie(){ 
    String baseUrl = "https://api.douban.com/v2/movie/";
 
    Retrofit retrofit= new Retrofit.Builder() 
        .baseUrl(baseUrl)
        .addConverterFactory(GsonConverterFactory.create()) 
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 
        .build(); 

    MovieService movieService = retrofit.create(MovieService.class); 

    movieService.getTopMovie(0, 10) 
        .subscribeOn(Schedulers.io()) 
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Subscriber<MovieEntity>() { 
            @Override 
            public void onCompleted() { 
                Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
            } 

            @Override 
            public void onError(Throwable e) { 
                resultTV.setText(e.getMessage()); 
            } 

            @Override 
            public void onNext(MovieEntity movieEntity) { 
                resultTV.setText(movieEntity.toString()); 
            } 
     });
}

這樣基本上就完成了Retrofit和Rxjava的結(jié)合胸嘴,但是我知道你們當然不會滿意的雏掠。
接下來我們把創(chuàng)建Retrofit的過程封裝一下,然后希望Activity創(chuàng)建Subscriber對象傳進來劣像。
如需查看項目代碼 --> 代碼地址:

https://github.com/tough1985/RxjavaRetrofitDemo

選擇Tag -> step2
1.4 將請求過程進行封裝
創(chuàng)建一個對象HttpMethods

public class HttpMethods {

    public static final String BASE_URL = "https://api.douban.com/v2/movie/";

    private static final int DEFAULT_TIMEOUT = 5;

    private Retrofit retrofit;
    private MovieService movieService;

    //構(gòu)造方法私有
    private HttpMethods() {
        //手動創(chuàng)建一個OkHttpClient并設(shè)置超時時間
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
        httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);

        retrofit = new Retrofit.Builder()
                .client(httpClientBuilder.build())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(BASE_URL)
                .build();

        movieService = retrofit.create(MovieService.class);
    }

    //在訪問HttpMethods時創(chuàng)建單例
    private static class SingletonHolder{
        private static final HttpMethods INSTANCE = new HttpMethods();
    }

    //獲取單例
    public static HttpMethods getInstance(){
        return SingletonHolder.INSTANCE;
    }

    /**
     * 用于獲取豆瓣電影Top250的數(shù)據(jù)
     * @param subscriber 由調(diào)用者傳過來的觀察者對象
     * @param start 起始位置
     * @param count 獲取長度
     */
    public void getTopMovie(Subscriber<MovieEntity> subscriber, int start, int count){
        movieService.getTopMovie(start, count)
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }
}

用一個單例來封裝該對象乡话,在構(gòu)造方法中創(chuàng)建Retrofit和對應(yīng)的Service。 如果需要訪問不同的基地址耳奕,那么你可能需要創(chuàng)建多個Retrofit對象绑青,或者干脆根據(jù)不同的基地址封裝不同的HttpMethod類。
我們回頭再來看MainActivity中的getMovie方法:

private void getMovie(){
     subscriber = new Subscriber<MovieEntity>() {
         @Override
         public void onCompleted() {
             Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
         }

         @Override
         public void onError(Throwable e) {
             resultTV.setText(e.getMessage());
         }

         @Override
         public void onNext(MovieEntity movieEntity) {
             resultTV.setText(movieEntity.toString());
         }
     };
     HttpMethods.getInstance().getTopMovie(subscriber, 0, 10);
}        

其中subscriber是MainActivity的成員變量屋群。
如需查看項目代碼 --> 代碼地址:

https://github.com/tough1985/RxjavaRetrofitDemo

選擇Tag -> step3

2.相同格式的Http請求數(shù)據(jù)該如何封裝

第二部分和第三部分我參考了知乎上的一個問答: RxJava+Retrofit闸婴,在聯(lián)網(wǎng)返回后如何先進行統(tǒng)一的判斷? 不過沒有完整的示例谓晌,所以在這寫一個完整的示例出來掠拳。
這個段落我們來聊一下有些Http服務(wù)返回一個固定格式的數(shù)據(jù)的問題。 例如:

{
 "resultCode": 0,
 "resultMessage": "成功",
 "data": {}
}

大部分的Http服務(wù)可能都是這樣設(shè)置纸肉,resultCode和resultMessage的內(nèi)容相對比較穩(wěn)定溺欧,而data的內(nèi)容變化多端,72變都不一定夠變的柏肪,有可能是個User對象姐刁,也有可能是個訂單對象,還有可能是個訂單列表烦味。 按照我們之前的用法聂使,使用Gson轉(zhuǎn)型需要我們在創(chuàng)建subscriber對象是指定返回值類型,如果我們對不同的返回值進行封裝的話谬俄,那可能就要有上百個Entity了柏靶,看著明明是很清晰的結(jié)構(gòu),卻因為data的不確定性無奈了起來溃论。
少年屎蜓,不必煩惱,來來來~ 老衲賜你寶典葵花钥勋,老衲就是練了這個才出家炬转。辆苔。。
我們可以創(chuàng)建一個HttpResult類

public class HttpResult<T>{
    private int resultCode;
    private String resultMessage;

    private T data;
}

如果data是一個User對象的話扼劈。那么在定義Service方法的返回值就可以寫為

Observable<HttpResult<User>>

這樣一來HttpResult就相當于一個包裝類驻啤,將結(jié)果包裝了起來,但是在使用的時候要給出一個明確的類型荐吵。
在上面的示例中骑冗,我也創(chuàng)建了一個HttpResult類治泥,用來模仿這個形式分别,將其中的Subject單獨封裝了起來。

public class HttpResult<T>{

    //用來模仿resultCode和resultMessage
    private int count;
    private int start;
    private int total;
    private String title;

    //用來模仿Data
    private T subjects;
}

這樣泛型的時候就要寫為:

Observable<HttpResult<List<Subject>>>

如需查看項目代碼 --> 代碼地址:

https://github.com/tough1985/RxjavaRetrofitDemo

選擇Tag -> step4

3.相同格式的Http請求數(shù)據(jù)統(tǒng)一進行預處理

既然我們有了相同的返回格式噪伊,那么我們可能就需要在獲得數(shù)據(jù)之后進行一個統(tǒng)一的預處理榨婆。
當接收到了一個Http請求結(jié)果之后,由于返回的結(jié)構(gòu)統(tǒng)一為

{
 "resultCode": 0,
 "resultMessage": "成功",
 "data": {}
}

我們想要對resultCoderesultMessage先做一個判斷褒侧,因為如果resultCode == 0代表success良风,那么resultCode != 0data一般都是null。Activity或Fragment對resultCoderesultMessage基本沒有興趣闷供,他們只對請求狀態(tài)data數(shù)據(jù)感興趣烟央。
基于這種考慮,我們在resultCode != 0的時候歪脏,拋出個自定義的ApiException疑俭。這樣就會進入到subscriber的onError中,我們可以在onError中處理錯誤信息婿失。
另外钞艇,請求成功時,需要將data數(shù)據(jù)轉(zhuǎn)換為目標數(shù)據(jù)類型傳遞給subscriber豪硅,因為哩照,Activity和Fragment只想拿到和他們真正相關(guān)的數(shù)據(jù)。
使用Observable的map方法可以完成這一功能懒浮。
HttpMethods中創(chuàng)建一個內(nèi)部類HttpResultFunc飘弧,代碼如下:

/**
 * 用來統(tǒng)一處理Http的resultCode,并將HttpResult的Data部分剝離出來返回給subscriber
 *
 * @param <T> Subscriber真正需要的數(shù)據(jù)類型,也就是Data部分的數(shù)據(jù)類型
 */
private class HttpResultFunc<T> implements Func1<HttpResult<T>, T>{

    @Override
    public T call(HttpResult<T> httpResult) {
        if (httpResult.getResultCode() != 0) {
            throw new ApiException(httpResult.getResultCode());
        }
        return httpResult.getData();
    }
}

然后我們的getTopMovie方法改為:

public void getTopMovie(Subscriber<List<Subject>> subscriber, int start, int count){

    movieService.getTopMovie(start, count)
            .map(new HttpResultFunc<List<Subject>>())
            .subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(subscriber);
}

由于HttpResult中的泛型T就是我們希望傳遞給subscriber的數(shù)據(jù)類型砚著,而數(shù)據(jù)可以通過httpResult的getData方法獲得次伶,這樣我們就處理了泛型問題,錯誤處理問題稽穆,還有將請求數(shù)據(jù)部分剝離出來給subscriber
這樣我們只需要關(guān)注Data數(shù)據(jù)的類型冠王,而不必在關(guān)心整個過程了。
需要注意一點秧骑,就是在定義Service的時候版确,泛型是

HttpResult<User>
//or
HttpResult<List<Subject>>```
而在定義Subscriber的時候泛型是
` java User //or List<Subject>`

不然你會得到一個轉(zhuǎn)型錯誤扣囊。
如需查看項目代碼 --> 代碼地址:
https://github.com/tough1985/RxjavaRetrofitDemo

選擇Tag -> step5
代碼中我是用豆瓣數(shù)據(jù)模擬了HttpResult中的resultCode和resultMessage,與文檔中的代碼略有出入绒疗。
###4.如何取消一個Http請求 -- 觀察者之間的對決侵歇,Observer VS Subscriber
4.1 取消一個Http請求
這一部分我們來聊一下關(guān)于取消Http請求的事情,已經(jīng)Oberver和Subscriber這兩個體位我們哪個更容易給我們G點吓蘑。
如果沒有使用Rxjava惕虑,那么Service返回的是一個Call,而這個Call對象有一個cancel方法可以用來取消Http請求磨镶。那么用了Rxjava之后溃蔫,如何來取消一個請求呢?因為返回值是一個Observable琳猫。我們能做的似乎只有解除對Observable對象的訂閱伟叛,其他的什么也做不了。
好在Retrofit已經(jīng)幫我們考慮到了這一點脐嫂。 答案在RxJavaCallAdapterFactory這個類的源碼中可以找到

    static final class CallOnSubscribe<T> implements Observable.OnSubscribe<Response<T>> {
      private final Call<T> originalCall;

      CallOnSubscribe(Call<T> originalCall) {
        this.originalCall = originalCall;
      }

      @Override public void call(final Subscriber<? super Response<T>> subscriber) {
        // Since Call is a one-shot type, clone it for each new subscriber.
        final Call<T> call = originalCall.clone();

        // Attempt to cancel the call if it is still in-flight on unsubscription.
        subscriber.add(Subscriptions.create(new Action0() {
          @Override public void call() {
            call.cancel();
          }
        }));

        try {
          Response<T> response = call.execute();
          if (!subscriber.isUnsubscribed()) {
            subscriber.onNext(response);
          }
        } catch (Throwable t) {
          Exceptions.throwIfFatal(t);
          if (!subscriber.isUnsubscribed()) {
            subscriber.onError(t);
          }
          return;
        }

        if (!subscriber.isUnsubscribed()) {
          subscriber.onCompleted();
        }
      }
    }
   

我們看到call方法中统刮,給subscriber添加了一個Subscription對象,Subscription對象很簡單账千,主要就是取消訂閱用的侥蒙,如果你查看Subscriptions.create的源碼,發(fā)現(xiàn)是這樣的
public static Subscription create(final Action0 unsubscribe) { return BooleanSubscription.create(unsubscribe);}

利用了一個BooleanSubscription類來創(chuàng)建一個Subscription匀奏,如果你點進去看BooleanSubscription.create方法一切就清晰了鞭衩,當接觸綁定的時候,subscriber會調(diào)用Subscription的unsubscribe方法娃善,然后觸發(fā)創(chuàng)建Subscription時候的傳遞進來的Action0的call方法论衍。RxJavaCallAdapterFactory幫我們給subscriber添加的是call.cancel(),
總結(jié)起來就是說会放,我們在Activity或者Fragment中創(chuàng)建subscriber對象饲齐,想要取消請求的時候調(diào)用subscriber的unsubscribe方法就可以了。
對不起這一節(jié)有太多的**Subscriber**和**Subscription**以及**Observer**和**Observable**咧最,老衲當時看的時候也是不知道吐了多少次了捂人,習慣了就好了。
4.2 為什么會提到Oberver
提到Observer的過程是這樣的矢沿。由于Subscriber一旦調(diào)用了unsubscribe方法之后滥搭,就沒有用了。且當事件傳遞到onError或者onCompleted之后捣鲸,也會自動的解綁瑟匆。這樣出現(xiàn)的一個問題就是每次發(fā)送請求都要創(chuàng)建新的Subscriber對象。
這樣我們就把注意力放到了Observer栽惶,Observer本身是一個接口愁溜,他的特性是不管你怎么用疾嗅,都不會解綁,為什么呢冕象?因為他沒有解綁的方法代承。所以就達到了復用的效果,一開始我一直美滋滋的用Observer渐扮。事實上论悴,如果你用的是Observer,在調(diào)用Observable對象的subscribe方法的時候墓律,會自動的將Observer對象轉(zhuǎn)換成Subscriber對象膀估。
下面是源碼:

    public final Subscription subscribe(final Observer<? super T> observer) {
        if (observer instanceof Subscriber) {
            return subscribe((Subscriber<? super T>)observer);
        }
        return subscribe(new Subscriber<T>() {

            @Override
            public void onCompleted() {
                observer.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                observer.onError(e);
            }

            @Override
            public void onNext(T t) {
                observer.onNext(t);
            }

        });
    }
    

后來發(fā)現(xiàn)了問題,
問題1 無法取消耻讽,因為Observer沒有unsubscribe方法 問題2 沒有onStart方法 這個一會聊

這兩個問題是很痛苦的察纯。所以,為了后面更好的高潮针肥,我們還是選擇用Subscriber捐寥。
###5.一個需要ProgressDialog的Subscriber該有的樣子
我們希望有一個Subscriber在我們每次發(fā)送請求的時候能夠彈出一個ProgressDialog,然后在請求接受的時候讓這個ProgressDialog消失祖驱,同時在我們?nèi)∠@個ProgressDialog的同時能夠取消當前的請求,而我們只需要處理里面的數(shù)據(jù)就可以了瞒窒。
我們先來創(chuàng)建一個類捺僻,就叫**ProgressSubscriber**,讓他繼承**Subscriber**崇裁。
Subscriber給我們提供了onStart匕坯、onNext、onError拔稳、onCompleted四個方法葛峻。
其中只有onNext方法返回了數(shù)據(jù),那我們自然希望能夠在onNext里面處理數(shù)據(jù)相關(guān)的邏輯巴比。
**onStart**方法我們用來啟動一個ProgressDialog术奖。 **onError**方法我們集中處理錯誤,同時也停止ProgressDialog **onComplated**方法里面停止ProgressDialog
其中我們需要解決兩個問題
問題1 onNext的處理 問題2 cancel掉一個ProgressDialog的時候取消請求

我們先來解決問題1
5.1處理onNext
我們希望這里能夠讓Activity或者Fragment自己處理onNext之后的邏輯轻绞,很自然的我們想到了用接口采记。問題還是泛型的問題,這里面我們必須指定明確的類型政勃。所以接口還是需要泛型唧龄。
我們先來定義一個接口,命名**SubscriberOnNextListener**
public interface SubscriberOnNextListener<T> { void onNext(T t);}

代碼很簡單奸远。再來看一下ProgressSubscriber現(xiàn)在的代碼

    public class ProgressSubscriber<T> extends Subscriber<T> {

        private SubscriberOnNextListener mSubscriberOnNextListener;
        private Context context;

        public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
            this.mSubscriberOnNextListener = mSubscriberOnNextListener;
            this.context = context;
        }

        @Override
        public void onStart() {
        }

        @Override
        public void onCompleted() {
            Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onError(Throwable e) {
            Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onNext(T t) {
            mSubscriberOnNextListener.onNext(t);
        }
    }
    

我知道傳Context不好既棺,不過為了演示而已讽挟,大家可以自己封裝一下Toast。
MainActivity使用是這樣的:
先來定義一個SubscriberOnNextListener對象丸冕,可以在onCreate里面創(chuàng)建這個對象

    private SubscriberOnNextListener getTopMovieOnNext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        getTopMovieOnNext = new SubscriberOnNextListener<List<Subject>>() {
            @Override
            public void onNext(List<Subject> subjects) {
                resultTV.setText(subjects.toString());
            }
        };
    }
    

**getMovie**方法這么寫:

    private void getMovie(){
     HttpMethods.getInstance().getTopMovie(
      new ProgressSubscriber(getTopMovieOnNext, MainActivity.this), 
      0, 10);
    }
    

這樣Activity或Fragment就只需要關(guān)注拿到結(jié)果之后的邏輯了耽梅,其他的完全不用操心。
如需查看項目代碼 --> 代碼地址:
>[https://github.com/tough1985/RxjavaRetrofitDemo](https://github.com/tough1985/RxjavaRetrofitDemo)

選擇Tag -> step6
5.2處理ProgressDialog
我們希望當cancel掉ProgressDialog的時候晨仑,能夠取消訂閱褐墅,也就取消了當前的Http請求。 所以我們先來創(chuàng)建個接口來處理這件事情洪己。

public interface ProgressCancelListener { void onCancelProgress();}

然后我們用ProgressSubscriber來實現(xiàn)這個接口妥凳,這樣ProgressSubscriber就有了一個onCancelProgress方法,在這里面取消訂閱答捕。

@Overridepublic void onCancelProgress() { if (!this.isUnsubscribed()) { this.unsubscribe(); }}

然后我用了一個Handler來封裝了ProgressDialog逝钥。

    public class ProgressDialogHandler extends Handler {

        public static final int SHOW_PROGRESS_DIALOG = 1;
        public static final int DISMISS_PROGRESS_DIALOG = 2;

        private ProgressDialog pd;

        private Context context;
        private boolean cancelable;
        private ProgressCancelListener mProgressCancelListener;

        public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener,
                                     boolean cancelable) {
            super();
            this.context = context;
            this.mProgressCancelListener = mProgressCancelListener;
            this.cancelable = cancelable;
        }

        private void initProgressDialog(){
            if (pd == null) {
                pd = new ProgressDialog(context);

                pd.setCancelable(cancelable);

                if (cancelable) {
                    pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialogInterface) {
                            mProgressCancelListener.onCancelProgress();
                        }
                    });
                }

                if (!pd.isShowing()) {
                    pd.show();
                }
            }
        }

        private void dismissProgressDialog(){
            if (pd != null) {
                pd.dismiss();
                pd = null;
            }
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW_PROGRESS_DIALOG:
                    initProgressDialog();
                    break;
                case DISMISS_PROGRESS_DIALOG:
                    dismissProgressDialog();
                    break;
            }
        }
    }
    

Handler接收兩個消息來控制顯示Dialog還是關(guān)閉Dialog。 創(chuàng)建Handler的時候我們需要傳入ProgressCancelListener的對象實例拱镐。
最后貼出ProgressSubscriber的完整代碼:

    public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{

        private SubscriberOnNextListener mSubscriberOnNextListener;
        private ProgressDialogHandler mProgressDialogHandler;

        private Context context;

        public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
            this.mSubscriberOnNextListener = mSubscriberOnNextListener;
            this.context = context;
            mProgressDialogHandler = new ProgressDialogHandler(context, this, true);
        }

        private void showProgressDialog(){
            if (mProgressDialogHandler != null) {
                mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
            }
        }

        private void dismissProgressDialog(){
            if (mProgressDialogHandler != null) {
                mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
                mProgressDialogHandler = null;
            }
        }

        @Override
        public void onStart() {
            showProgressDialog();
        }

        @Override
        public void onCompleted() {
            dismissProgressDialog();
            Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onError(Throwable e) {
            dismissProgressDialog();
            Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onNext(T t) {
            mSubscriberOnNextListener.onNext(t);
        }

        @Override
        public void onCancelProgress() {
            if (!this.isUnsubscribed()) {
                this.unsubscribe();
            }
        }
    }
    

目前為止艘款,就封裝完畢了。以上是我在用Rxjava和Retrofit過程中踩過的一些坑沃琅,最后整合出來的哗咆,由于沒有在實際的項目中跑過,有問題的話希望能夠提出來大家討論一下益眉,拍磚也歡迎晌柬。
現(xiàn)在我們再寫一個新的網(wǎng)絡(luò)請求,步驟是這樣的: 1. 在**Service**中定義一個新的方法郭脂。 2. 在**HttpMethods**封裝對應(yīng)的請求(代碼基本可以copy) 3. 創(chuàng)建一個**SubscriberOnNextListener**處理請求數(shù)據(jù)并刷新UI年碘。
最后
如果你覺得寫更改線程的代碼覺得也很煩的話,可以把訂閱這部分也封裝起來:

    public void getTopMovie(Subscriber<<List<Subject>> subscriber, int start, int count){
      //原來的樣子
    // movieService.getTopMovie(start, count)
    // .map(new HttpResultFunc<List<Subject>>())
    // .subscribeOn(Schedulers.io())
    // .unsubscribeOn(Schedulers.io())
    // .observeOn(AndroidSchedulers.mainThread())
    // .subscribe(subscriber);

     //修改之后的樣子
        Observable observable = movieService.getTopMovie(start, count)
                .map(new HttpResultFunc<List<Subject>>());

        toSubscribe(observable, subscriber);
    }

    //添加線程管理并訂閱
    private void toSubscribe(Observable o, Subscriber s){
         o.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(s);
    }
    

讓你每次寫一個請求的時候展鸡,寫的代碼盡量少屿衅,更多的精力放在業(yè)務(wù)邏輯本身。
最后的最后
如果你的httpResult格式本身沒有問題莹弊,但是data中的內(nèi)容是這樣的:

{
"resultCode": 0,
"resultMessage": "成功", "data": {"user": {}, "orderArray": []}
}

這樣的情況還能不能繼續(xù)使用這樣的框架呢涤久? 我的解決方法是封裝一個類,把user和orderArray作為類的屬性忍弛。 但是如果你的服務(wù)器一會data本身是一個完整的user數(shù)據(jù)拴竹,一會又是這樣: "data": {"user": {}, "orderArray": []}
 那我覺得你有必要跟你的服務(wù)端好好聊聊了,請他吃頓飯和頓酒剧罩,大不了獻出菊花就是了栓拜。
但是如果服務(wù)已經(jīng)上線了!!幕与!
對不起挑势,騷年......
老衲會在你墳前念300遍**Thinking in java**替你超度的~
希望你用Retrofit和Rxjava的新體位能夠享受到新的高潮。

作者:[tough1985](https://github.com/tough1985)
發(fā)布時間:2016-03-15 21:20
更新時間:2016-03-16 16:36
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啦鸣,一起剝皮案震驚了整個濱河市潮饱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诫给,老刑警劉巖香拉,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異中狂,居然都是意外死亡凫碌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門胃榕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盛险,“玉大人,你說我怎么就攤上這事勋又】嗑颍” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵楔壤,是天一觀的道長鹤啡。 經(jīng)常有香客問我,道長蹲嚣,這世上最難降的妖魔是什么揉忘? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮端铛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疲眷。我一直安慰自己禾蚕,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布狂丝。 她就那樣靜靜地躺著换淆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪几颜。 梳的紋絲不亂的頭發(fā)上倍试,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音蛋哭,去河邊找鬼县习。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的躁愿。 我是一名探鬼主播叛本,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彤钟!你這毒婦竟也來了来候?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤逸雹,失蹤者是張志新(化名)和其女友劉穎营搅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梆砸,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡转质,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辫樱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峭拘。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖狮暑,靈堂內(nèi)的尸體忽然破棺而出鸡挠,到底是詐尸還是另有隱情,我是刑警寧澤搬男,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布拣展,位于F島的核電站,受9級特大地震影響缔逛,放射性物質(zhì)發(fā)生泄漏备埃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一褐奴、第九天 我趴在偏房一處隱蔽的房頂上張望按脚。 院中可真熱鬧,春花似錦敦冬、人聲如沸辅搬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堪遂。三九已至,卻和暖如春萌庆,著一層夾襖步出監(jiān)牢的瞬間溶褪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工践险, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猿妈,地道東北人吹菱。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像于游,于是被迫代替她去往敵國和親毁葱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

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