RxJava 與 Retrofit 結合的最佳實踐

轉載自:http://gank.io/post/56e80c2c677659311bed9841

前言

RxJava和Retrofit也火了一段時間了,不過最近一直在學習ReactNative和Node相關的姿勢,一直沒有時間研究這些新東西,最近有個項目準備寫,打算先用Android寫一個Demo出來,卻發現Android的世界發生了天翻地覆的變化,EventBus和OKHttp啥的都不見了,RxJava和Retrofit是什麼鬼?

好吧,到Github上耐着性子看過了RxJava和Retrofit的介紹和幾個Demo,原來Android的大神Jake Wharton爲Retrofit這個項目貢獻了這麼多的代碼,沒有道理不用了。

如果你對RxJava不熟悉請先看給 Android 開發者的 RxJava 詳解這篇文章。

如果你對Retrofit不熟悉就先看Retrofit官網

當然也有很多RxJava與Retrofit的文章,但是我覺得很多大家都很糾結的功能都沒有被總結出來,所以纔有了此篇文章。

歡迎大家拍磚。

接下來進入正文,我是從下面幾個角度去思考RxJava與Retrofit結合的。

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

1.RxJava如何與Retrofit結合

1.1 基本頁面

先扔出build.gradle文件的內容

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 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的代碼如下:

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(){

    }
}

注意不要忘記加網絡權限

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

1.2 只用Retrofit

我們準備在getMovie方法中進行網絡請求,我們先來看看只使用Retrofit是如何進行的。

我們使用豆瓣電影的Top250做測試連接,目標地址爲

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

至於返回的數據格式,大家自己訪問下鏈接就看到了,太長就不放進來了。

首先我們要根據返回的結果封裝一個Entity,暫命名爲MovieEntity,代碼就不貼了。

接下來我們要創建一個接口取名爲MovieService,代碼如下:

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

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

//進行網絡請求
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,依舊能夠做很多的封裝,只是比較麻煩。

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

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'

當然我們已經添加過了。

然後在創建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方法改爲:

//進行網絡請求
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對象傳進來。

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

https://github.com/tough1985/RxjavaRetrofitDemo

選擇Tag -> step2

1.4 將請求過程進行封裝

創建一個對象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;

    //構造方法私有
    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方法:

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請求數據該如何封裝

第二部分和第三部分我參考了知乎上的一個問答: RxJava+Retrofit,在聯網返回後如何先進行統一的判斷? 不過沒有完整的示例,所以在這寫一個完整的示例出來。

這個段落我們來聊一下有些Http服務返回一個固定格式的數據的問題。 例如:

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

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

少年,不必煩惱,來來來~ 老衲賜你寶典葵花,老衲就是練了這個纔出家。。。

我們可以創建一個HttpResult類

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

    private T data;
}

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

Observable<HttpResult<User>>

這樣一來HttpResult就相當於一個包裝類,將結果包裝了起來,但是在使用的時候要給出一個明確的類型。

在上面的示例中,我也創建了一個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請求數據統一進行預處理

既然我們有了相同的返回格式,那麼我們可能就需要在獲得數據之後進行一個統一的預處理。

當接收到了一個Http請求結果之後,由於返回的結構統一爲

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

我們想要對resultCoderesultMessage先做一個判斷,因爲如果resultCode == 0代表success,那麼resultCode != 0data一般都是null

Activity或Fragment對resultCoderesultMessage基本沒有興趣,他們只對請求狀態data數據感興趣。

基於這種考慮,我們在resultCode != 0的時候,拋出個自定義的ApiException。這樣就會進入到subscriber的onError中,我們可以在onError中處理錯誤信息。

另外,請求成功時,需要將data數據轉換爲目標數據類型傳遞給subscriber,因爲,Activity和Fragment只想拿到和他們真正相關的數據。

使用Observable的map方法可以完成這一功能。

HttpMethods中創建一個內部類HttpResultFunc,代碼如下:

/**
 * 用來統一處理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方法改爲:

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的時候,泛型是

HttpResult<User>
//or
HttpResult<List<Subject>>

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

不然你會得到一個轉型錯誤。

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

https://github.com/tough1985/RxjavaRetrofitDemo

選擇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這個類的源碼中可以找到

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的源碼,發現是這樣的

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對象。

下面是源碼:

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

public interface SubscriberOnNextListener<T> {
    void onNext(T t);
}

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

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裏面創建這個對象

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就只需要關注拿到結果之後的邏輯了,其他的完全不用操心。

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

https://github.com/tough1985/RxjavaRetrofitDemo

選擇Tag -> step6

5.2處理ProgressDialog

我們希望當cancel掉ProgressDialog的時候,能夠取消訂閱,也就取消了當前的Http請求。 所以我們先來創建個接口來處理這件事情。

public interface ProgressCancelListener {
    void onCancelProgress();
}

然後我們用ProgressSubscriber來實現這個接口,這樣ProgressSubscriber就有了一個onCancelProgress方法,在這裏面取消訂閱。

@Override
public 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還是關閉Dialog。 創建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過程中踩過的一些坑,最後整合出來的,由於沒有在實際的項目中跑過,有問題的話希望能夠提出來大家討論一下,拍磚也歡迎。

現在我們再寫一個新的網絡請求,步驟是這樣的: 1. 在Service中定義一個新的方法。 2. 在HttpMethods封裝對應的請求(代碼基本可以copy) 3. 創建一個SubscriberOnNextListener處理請求數據並刷新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);
}

讓你每次寫一個請求的時候,寫的代碼儘量少,更多的精力放在業務邏輯本身。

最後的最後

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

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

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

但是如果服務已經上線了!!!

對不起,騷年......

老衲會在你墳前念300遍Thinking in java替你超度的~

希望你用Retrofit和Rxjava的新體位能夠享受到新的高潮。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章