RxJava 與 Retrofit 完美組合

RxJava與Retrofit完美組合
  • RxJava如何與Retrofit結合
  • 相同格式的Http請求數據該如何封裝
  • 相同格式的Http請求數據統一進行預處理
  • 如何取消一個Http請求 -- 觀察者之間的對決,Oberver VS Subscriber
  • 一個需要ProgressDialog的Subscriber該有的樣子

1.RxJava如何與Retrofit結合1.1 基本頁面

先扔出build.gradle文件的內容

[XML] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
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中的線程問題。

下面先搭建一個基本的頁面,頁面很簡單,先來看文件目錄結構


activity_main.xml的代碼如下:

[XML] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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的代碼如下:

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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();
    }
 
    //進行網絡請求
    private void getMovie(){
 
    }
}

注意不要忘記加網絡權限

[XML] 查看源文件 複製代碼
1
<uses-permission android:name="android.permission.INTERNET"/>


1.2 只用Retrofit

我們準備在getMovie方法中進行網絡請求,我們先來看看只使用Retrofit是如何進行的。
我們使用豆瓣電影的Top250做測試連接,目標地址爲:


至於返回的數據格式,大家自己訪問下鏈接就看到了,太長就不放進來了。
首先我們要根據返回的結果封裝一個Entity,暫命名爲MovieEntity,代碼就不貼了。
接下來我們要創建一個接口取名爲MovieService,代碼如下:

[Java] 查看源文件 複製代碼
1
2
3
4
public interface MovieService {
    @GET("top250")
    Call<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}

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

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//進行網絡請求
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());
        }
    });
}


以上爲沒有經過封裝的、原生態的Retrofit寫網絡請求的代碼。 我們可以封裝創建Retrofit和service部分的代碼,然後Activity用創建一個Callback作爲參數給Call,這樣Activity中只關注請求的結果,而且Call有cancel方法可以取消一個請求,好像沒Rxjava什麼事了,我覺得可以寫到這就下班了~

接下來我們要面對的問題是這樣的 如果我的Http返回數據是一個統一的格式,例如

{ "resultCode": 0, "resultMessage": "成功", "data": {}}
我們如何對返回結果進行一個統一的處理呢?

另外,我的ProgressDialog的show方法應該在哪調用呢?看樣子只能在getMovie()這個方法裏面調用了,換個地方發出請求就要在對應的Listener裏面寫一遍show()的代碼,其實挺鬧心。
而且錯誤請求我也想集中處理掉不要貼重複的代碼。

我們先來看結合了Rxjava之後,事情有沒有變化的可能。當然即便是不用Rxjava,依舊能夠做很多的封裝,只是比較麻煩。
如需查看項目代碼 --> 代碼地址:


選擇Tag -> step1

1.3 添加Rxjava
Retrofit本身對Rxjava提供了支持。
添加Retrofit對Rxjava的支持需要在Gradle文件中添加

[XML] 查看源文件 複製代碼
1
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'

當然我們已經添加過了。
然後在創建Retrofit的過程中添加如下代碼:

[Java] 查看源文件 複製代碼
1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .build();

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

重新定義MovieService

[Java] 查看源文件 複製代碼
1
2
3
4
public interface MovieService {
    @GET("top250")
    Observable<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}

getMovie方法改爲:

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//進行網絡請求
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的結合,但是我知道你們當然不會滿意的。
接下來我們把創建Retrofit的過程封裝一下,然後希望Activity創建Subscriber對象傳進來。
如需查看項目代碼 --> 代碼地址:
選擇Tag -> step2

1.4 將請求過程進行封裝
創建一個對象HttpMethods

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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;
 
    //構造方法私有
    private HttpMethods() {
        //手動創建一個OkHttpClient並設置超時時間
        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時創建單例
    private static class SingletonHolder{
        private static final HttpMethods INSTANCE = new HttpMethods();
    }
 
    //獲取單例
    public static HttpMethods getInstance(){
        return SingletonHolder.INSTANCE;
    }
 
    /**
     * 用於獲取豆瓣電影Top250的數據
     * @param subscriber 由調用者傳過來的觀察者對象
     * @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);
    }
}


用一個單例來封裝該對象,在構造方法中創建Retrofit和對應的Service。 如果需要訪問不同的基地址,那麼你可能需要創建多個Retrofit對象,或者乾脆根據不同的基地址封裝不同的HttpMethod類。
我們回頭再來看MainActivity中的getMovie方法:

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
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的成員變量。
如需查看項目代碼 --> 代碼地址:

選擇Tag -> step3

2.相同格式的Http請求數據該如何封裝

第二部分和第三部分我參考了知乎上的一個問答: RxJava+Retrofit,在聯網返回後如何先進行統一的判斷? 不過沒有完整的示例,所以在這寫一個完整的示例出來。
這個段落我們來聊一下有些Http服務返回一個固定格式的數據的問題。 例如:

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

大部分的Http服務可能都是這樣設置,resultCode和resultMessage的內容相對比較穩定,而data的內容變化多端,72變都不一定夠變的,有可能是個User對象,也有可能是個訂單對象,還有可能是個訂單列表。 按照我們之前的用法,使用Gson轉型需要我們在創建subscriber對象是指定返回值類型,如果我們對不同的返回值進行封裝的話,那可能就要有上百個Entity了,看着明明是很清晰的結構,卻因爲data的不確定性無奈了起來。
少年,不必煩惱,來來來~ 老衲賜你寶典葵花,老衲就是練了這個纔出家。。。

我們可以創建一個HttpResult類

[Java] 查看源文件 複製代碼
1
2
3
4
5
6
public class HttpResult<T> {
    private int resultCode;
    private String resultMessage;
 
    private T data;
}

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

Observable<HttpResult<User>>
這樣一來HttpResult就相當於一個包裝類,將結果包裝了起來,但是在使用的時候要給出一個明確的類型。
在上面的示例中,我也創建了一個HttpResult類,用來模仿這個形式,將其中的Subject單獨封裝了起來。

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
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>>>

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


選擇Tag -> step4

3.相同格式的Http請求數據統一進行預處理
既然我們有了相同的返回格式,那麼我們可能就需要在獲得數據之後進行一個統一的預處理。
當接收到了一個Http請求結果之後,由於返回的結構統一爲
{ "resultCode": 0, "resultMessage": "成功", "data": {}}

我們想要對resultCoderesultMessage先做一個判斷,因爲如果resultCode == 0代表success,那麼resultCode != 0時data一般都是null
Activity或Fragment對resultCoderesultMessage基本沒有興趣,他們只對請求狀態data數據感興趣。

基於這種考慮,我們在resultCode != 0的時候,拋出個自定義的ApiException。這樣就會進入到subscriber的onError中,我們可以在onError中處理錯誤信息。
另外,請求成功時,需要將data數據轉換爲目標數據類型傳遞給subscriber,因爲,Activity和Fragment只想拿到和他們真正相關的數據。
使用Observable的map方法可以完成這一功能

HttpMethods中創建一個內部類HttpResultFunc,代碼如下:
[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
/**
 * 用來統一處理Http的resultCode,並將HttpResult的Data部分剝離出來返回給subscriber
 *
 * @param <T> Subscriber真正需要的數據類型,也就是Data部分的數據類型
 */
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方法改爲:
[Java] 查看源文件 複製代碼
1
2
3
4
5
6
7
8
9
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的數據類型,而數據可以通過httpResult的getData方法獲得,這樣我們就處理了泛型問題,錯誤處理問題,還有將請求數據部分剝離出來給subscriber
這樣我們只需要關注Data數據的類型,而不必在關心整個過程了。
需要注意一點,就是在定義Service的時候,泛型是
[Java] 查看源文件 複製代碼
1
2
3
HttpResult<User>
//or
HttpResult<List<Subject>>
而在定義Subscriber的時候泛型是 java User //or List<Subject>
不然你會得到一個轉型錯誤。
如需查看項目代碼 --> 代碼地址:
選擇Tag -> step5
代碼中我是用豆瓣數據模擬了HttpResult中的resultCode和resultMessage,與文檔中的代碼略有出入。

4.如何取消一個Http請求 -- 觀察者之間的對決,Observer VS Subscriber


4.1 取消一個Http請求
這一部分我們來聊一下關於取消Http請求的事情,已經Oberver和Subscriber這兩個體位我們哪個更容易給我們G點。
如果沒有使用Rxjava,那麼Service返回的是一個Call,而這個Call對象有一個cancel方法可以用來取消Http請求。那麼用了Rxjava之後,如何來取消一個請求呢?因爲返回值是一個Observable。我們能做的似乎只有解除對Observable對象的訂閱,其他的什麼也做不了。
好在Retrofit已經幫我們考慮到了這一點。 答案在RxJavaCallAdapterFactory這個類的源碼中可以找到
[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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的源碼,發現是這樣的
[Java] 查看源文件 複製代碼
1
2
3
public static Subscription create(final Action0 unsubscribe) {
  return BooleanSubscription.create(unsubscribe);
}
利用了一個BooleanSubscription類來創建一個Subscription,如果你點進去看BooleanSubscription.create方法一切就清晰了,當接觸綁定的時候,subscriber會調用Subscription的unsubscribe方法,然後觸發創建Subscription時候的傳遞進來的Action0的call方法。RxJavaCallAdapterFactory幫我們給subscriber添加的是call.cancel(),
總結起來就是說,我們在Activity或者Fragment中創建subscriber對象,想要取消請求的時候調用subscriber的unsubscribe方法就可以了。
對不起這一節有太多的SubscriberSubscription以及ObserverObservable,老衲當時看的時候也是不知道吐了多少次了,習慣了就好了。

4.2 爲什麼會提到Oberver
提到Observer的過程是這樣的。由於Subscriber一旦調用了unsubscribe方法之後,就沒有用了。且當事件傳遞到onError或者onCompleted之後,也會自動的解綁。這樣出現的一個問題就是每次發送請求都要創建新的Subscriber對象。
這樣我們就把注意力放到了Observer,Observer本身是一個接口,他的特性是不管你怎麼用,都不會解綁,爲什麼呢?因爲他沒有解綁的方法。所以就達到了複用的效果,一開始我一直美滋滋的用Observer。事實上,如果你用的是Observer,在調用Observable對象的subscribe方法的時候,會自動的將Observer對象轉換成Subscriber對象。
下面是源碼:
[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
        }
 
    });
}
後來發現了問題,
問題1 無法取消,因爲Observer沒有unsubscribe方法 問題2 沒有onStart方法 這個一會聊
這兩個問題是很痛苦的。所以,爲了後面更好的高潮,我們還是選擇用Subscriber。

5.一個需要ProgressDialog的Subscriber該有的樣子
我們希望有一個Subscriber在我們每次發送請求的時候能夠彈出一個ProgressDialog,然後在請求接受的時候讓這個ProgressDialog消失,同時在我們取消這個ProgressDialog的同時能夠取消當前的請求,而我們只需要處理裏面的數據就可以了。
我們先來創建一個類,就叫ProgressSubscriber,讓他繼承Subscriber
Subscriber給我們提供了onStart、onNext、onError、onCompleted四個方法。
其中只有onNext方法返回了數據,那我們自然希望能夠在onNext裏面處理數據相關的邏輯。
onStart方法我們用來啓動一個ProgressDialog。 onError方法我們集中處理錯誤,同時也停止ProgressDialog onComplated方法裏面停止ProgressDialog
其中我們需要解決兩個問題
問題1 onNext的處理 問題2 cancel掉一個ProgressDialog的時候取消請求
我們先來解決問題1

5.1處理onNext
我們希望這裏能夠讓Activity或者Fragment自己處理onNext之後的邏輯,很自然的我們想到了用接口。問題還是泛型的問題,這裏面我們必須指定明確的類型。所以接口還是需要泛型。
我們先來定義一個接口,命名SubscriberOnNextListener
[Java] 查看源文件 複製代碼
1
2
3
4
5
public interface SubscriberOnNextListener<T> {
 
    void onNext(T t);
 
}

代碼很簡單。再來看一下ProgressSubscriber現在的代碼

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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裏面創建這個對象
[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
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方法這麼寫:

[Java] 查看源文件 複製代碼
1
2
3
4
5
private void getMovie(){
 HttpMethods.getInstance().getTopMovie(
  new ProgressSubscriber(getTopMovieOnNext, MainActivity.this),
  0, 10);
}
這樣Activity或Fragment就只需要關注拿到結果之後的邏輯了,其他的完全不用操心。
如需查看項目代碼 --> 代碼地址:
選擇Tag -> step6
5.2處理ProgressDialog
我們希望當cancel掉ProgressDialog的時候,能夠取消訂閱,也就取消了當前的Http請求。 所以我們先來創建個接口來處理這件事情。
[Java] 查看源文件 複製代碼
1
2
3
public interface ProgressCancelListener {
    void onCancelProgress();
}
然後我們用ProgressSubscriber來實現這個接口,這樣ProgressSubscriber就有了一個onCancelProgress方法,在這裏面取消訂閱。
[Java] 查看源文件 複製代碼
1
2
3
4
5
6
@Override
public void onCancelProgress() {
    if (!this.isUnsubscribed()) {
        this.unsubscribe();
    }
}

然後我用了一個Handler來封裝了ProgressDialog。

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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還是關閉Dialog。 創建Handler的時候我們需要傳入ProgressCancelListener的對象實例。
最後貼出ProgressSubscriber的完整代碼:

[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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過程中踩過的一些坑,最後整合出來的,由於沒有在實際的項目中跑過,有問題的話希望能夠提出來大家討論一下,拍磚也歡迎。
現在我們再寫一個新的網絡請求,步驟是這樣的: 1. 在Service中定義一個新的方法。 2. 在HttpMethods封裝對應的請求(代碼基本可以copy) 3. 創建一個SubscriberOnNextListener處理請求數據並刷新UI。

最後:
如果你覺得寫更改線程的代碼覺得也很煩的話,可以把訂閱這部分也封裝起來:
[Java] 查看源文件 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
讓你每次寫一個請求的時候,寫的代碼儘量少,更多的精力放在業務邏輯本身。

最後的最後
如果你的httpResult格式本身沒有問題,但是data中的內容是這樣的:

[XML] 查看源文件 複製代碼
1
2
3
4
5
{
 "resultCode": 0,
 "resultMessage": "成功",
 "data": {"user": {}, "orderArray": []}
}

這樣的情況還能不能繼續使用這樣的框架呢? 我的解決方法是封裝一個類,把user和orderArray作爲類的屬性。 但是如果你的服務器一會data本身是一個完整的user數據,一會又是這樣: "data": {"user": {}, "orderArray": []} 那我覺得你有必要跟你的服務端好好聊聊了,請他吃頓飯和頓酒,大不了獻出菊花就是了

發佈了33 篇原創文章 · 獲贊 9 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章