RXJava的使用<二>

前言
RxJava和Retrofit也火了一段時間了受楼,不過最近一直在學習ReactNative和Node相關的姿勢欢顷,一直沒有時間研究這些新東西凡简,最近有個項目準備寫贬墩,打算先用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官網钳踊。
當然也有很多RxJava與Retrofit的文章衷敌,但是我覺得很多大家都很糾結的功能都沒有被總結出來,所以才有了此篇文章拓瞪。
歡迎大家拍磚缴罗。
接下來進入正文,我是從下面幾個角度去思考RxJava與Retrofit結合的吴藻。
RxJava如何與Retrofit結合
相同格式的Http請求數(shù)據該如何封裝
相同格式的Http請求數(shù)據統(tǒng)一進行預處理
如何取消一個Http請求 -- 觀察者之間的對決瞒爬,Oberver VS Subscriber
一個需要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

至于返回的數(shù)據格式粥血,大家自己訪問下鏈接就看到了柏锄,太長就不放進來了酿箭。
首先我們要根據返回的結果封裝一個Entity,暫命名為MovieEntity趾娃,代碼就不貼了缭嫡。
接下來我們要創(chuàng)建一個接口取名為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()); } });}

以上為沒有經過封裝的妇蛀、原生態(tài)的Retrofit寫網絡請求的代碼。 我們可以封裝創(chuàng)建Retrofit和service部分的代碼笤成,然后Activity用創(chuàng)建一個Callback作為參數(shù)給Call评架,這樣Activity中只關注請求的結果,而且Call有cancel方法可以取消一個請求炕泳,好像沒Rxjava什么事了纵诞,我覺得可以寫到這就下班了~
接下來我們要面對的問題是這樣的 如果我的Http返回數(shù)據是一個統(tǒng)一的格式,例如
{ "resultCode": 0, "resultMessage": "成功", "data": {}}

我們如何對返回結果進行一個統(tǒng)一的處理呢培遵?
另外浙芙,我的ProgressDialog的show方法應該在哪調用呢?看樣子只能在getMovie()這個方法里面調用了籽腕,換個地方發(fā)出請求就要在對應的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'

當然我們已經添加過了。
然后在創(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方法改為:
//進行網絡請求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的結合傅寡,但是我知道你們當然不會滿意的。
接下來我們把創(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; //構造方法私有 private HttpMethods() { //手動創(chuàng)建一個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時創(chuàng)建單例 private static class SingletonHolder{ private static final HttpMethods INSTANCE = new HttpMethods(); } //獲取單例 public static HttpMethods getInstance(){ return SingletonHolder.INSTANCE; } /** * 用于獲取豆瓣電影Top250的數(shù)據 * @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); }}

用一個單例來封裝該對象,在構造方法中創(chuàng)建Retrofit和對應的Service珍策。 如果需要訪問不同的基地址托启,那么你可能需要創(chuàng)建多個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請求數(shù)據該如何封裝
第二部分和第三部分我參考了知乎上的一個問答: RxJava+Retrofit拐迁,在聯(lián)網返回后如何先進行統(tǒng)一的判斷? 不過沒有完整的示例疗绣,所以在這寫一個完整的示例出來线召。
這個段落我們來聊一下有些Http服務返回一個固定格式的數(shù)據的問題。 例如:
{ "resultCode": 0, "resultMessage": "成功", "data": {}}

大部分的Http服務可能都是這樣設置多矮,resultCode和resultMessage的內容相對比較穩(wěn)定缓淹,而data的內容變化多端,72變都不一定夠變的工窍,有可能是個User對象割卖,也有可能是個訂單對象,還有可能是個訂單列表患雏。 按照我們之前的用法鹏溯,使用Gson轉型需要我們在創(chuàng)建subscriber對象是指定返回值類型,如果我們對不同的返回值進行封裝的話淹仑,那可能就要有上百個Entity了丙挽,看著明明是很清晰的結構,卻因為data的不確定性無奈了起來匀借。
少年颜阐,不必煩惱,來來來~ 老衲賜你寶典葵花吓肋,老衲就是練了這個才出家凳怨。。是鬼。
我們可以創(chuàng)建一個HttpResult類
public class HttpResult<T> { private int resultCode; private String resultMessage; private T data;}

如果data是一個User對象的話肤舞。那么在定義Service方法的返回值就可以寫為
Observable<HttpResult<User>>

這樣一來HttpResult就相當于一個包裝類,將結果包裝了起來均蜜,但是在使用的時候要給出一個明確的類型李剖。
在上面的示例中,我也創(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ù)據統(tǒng)一進行預處理
既然我們有了相同的返回格式充择,那么我們可能就需要在獲得數(shù)據之后進行一個統(tǒng)一的預處理德玫。
當接收到了一個Http請求結果之后,由于返回的結構統(tǒng)一為
{ "resultCode": 0, "resultMessage": "成功", "data": {}}

我們想要對resultCoderesultMessage先做一個判斷椎麦,因為如果resultCode == 0
代表success化焕,那么resultCode != 0
data一般都是null
Activity或Fragment對resultCoderesultMessage基本沒有興趣铃剔,他們只對請求狀態(tài)data數(shù)據感興趣撒桨。
基于這種考慮查刻,我們在resultCode != 0
的時候,拋出個自定義的ApiException凤类。這樣就會進入到subscriber的onError中穗泵,我們可以在onError中處理錯誤信息。
另外谜疤,請求成功時佃延,需要將data數(shù)據轉換為目標數(shù)據類型傳遞給subscriber,因為夷磕,Activity和Fragment只想拿到和他們真正相關的數(shù)據履肃。
使用Observable的map方法可以完成這一功能。
HttpMethods中創(chuàng)建一個內部類HttpResultFunc坐桩,代碼如下:
/** * 用來統(tǒng)一處理Http的resultCode,并將HttpResult的Data部分剝離出來返回給subscriber * * @param <T> Subscriber真正需要的數(shù)據類型尺棋,也就是Data部分的數(shù)據類型 */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ù)據類型,而數(shù)據可以通過httpResult的getData方法獲得绵跷,這樣我們就處理了泛型問題膘螟,錯誤處理問題,還有將請求數(shù)據部分剝離出來給subscriber
這樣我們只需要關注Data數(shù)據的類型碾局,而不必在關心整個過程了荆残。
需要注意一點,就是在定義Service的時候净当,泛型是
HttpResult<User>//orHttpResult<List<Subject>>

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

不然你會得到一個轉型錯誤内斯。
如需查看項目代碼 --> 代碼地址:
https://github.com/tough1985/RxjavaRetrofitDemo

選擇Tag -> step5
代碼中我是用豆瓣數(shù)據模擬了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的源碼,發(fā)現(xiàn)是這樣的
public static Subscription create(final Action0 unsubscribe) { return BooleanSubscription.create(unsubscribe);}

利用了一個BooleanSubscription類來創(chuàng)建一個Subscription放闺,如果你點進去看BooleanSubscription.create方法一切就清晰了祟昭,當接觸綁定的時候,subscriber會調用Subscription的unsubscribe方法怖侦,然后觸發(fā)創(chuàng)建Subscription時候的傳遞進來的Action0的call方法篡悟。RxJavaCallAdapterFactory幫我們給subscriber添加的是call.cancel(),
總結起來就是說匾寝,我們在Activity或者Fragment中創(chuàng)建subscriber對象搬葬,想要取消請求的時候調用subscriber的unsubscribe方法就可以了。
對不起這一節(jié)有太多的SubscriberSubscription以及ObserverObservable艳悔,老衲當時看的時候也是不知道吐了多少次了急凰,習慣了就好了。
4.2 為什么會提到Oberver
提到Observer的過程是這樣的很钓。由于Subscriber一旦調用了unsubscribe方法之后香府,就沒有用了。且當事件傳遞到onError或者onCompleted之后码倦,也會自動的解綁企孩。這樣出現(xiàn)的一個問題就是每次發(fā)送請求都要創(chuàng)建新的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); } });}

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

這兩個問題是很痛苦的。所以裕菠,為了后面更好的高潮咬清,我們還是選擇用Subscriber。
5.一個需要ProgressDialog的Subscriber該有的樣子
我們希望有一個Subscriber在我們每次發(fā)送請求的時候能夠彈出一個ProgressDialog,然后在請求接受的時候讓這個ProgressDialog消失旧烧,同時在我們取消這個ProgressDialog的同時能夠取消當前的請求影钉,而我們只需要處理里面的數(shù)據就可以了。
我們先來創(chuàng)建一個類粪滤,就叫ProgressSubscriber斧拍,讓他繼承Subscriber
Subscriber給我們提供了onStart杖小、onNext肆汹、onError、onCompleted四個方法予权。
其中只有onNext方法返回了數(shù)據昂勉,那我們自然希望能夠在onNext里面處理數(shù)據相關的邏輯。
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;@Overrideprotected 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請求。 所以我們先來創(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還是關閉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)在我們再寫一個新的網絡請求,步驟是這樣的: 1. 在Service中定義一個新的方法耿戚。 2. 在HttpMethods封裝對應的請求(代碼基本可以copy) 3. 創(chuàng)建一個SubscriberOnNextListener處理請求數(shù)據并刷新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è)務邏輯本身。
最后的最后
如果你的httpResult格式本身沒有問題皂股,但是data中的內容是這樣的:
{ "resultCode": 0, "resultMessage": "成功", "data": {"user": {}, "orderArray": []}}

這樣的情況還能不能繼續(xù)使用這樣的框架呢墅茉? 我的解決方法是封裝一個類,把user和orderArray作為類的屬性呜呐。 但是如果你的服務器一會data本身是一個完整的user數(shù)據就斤,一會又是這樣: "data": {"user": {}, "orderArray": []}
那我覺得你有必要跟你的服務端好好聊聊了,請他吃頓飯和頓酒蘑辑,大不了獻出菊花就是了洋机。
但是如果服務已經上線了!R郧槐秧!
對不起,騷年......
老衲會在你墳前念300遍Thinking in java替你超度的~
希望你用Retrofit和Rxjava的新體位能夠享受到新的高潮忧设。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末刁标,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子址晕,更是在濱河造成了極大的恐慌膀懈,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谨垃,死亡現(xiàn)場離奇詭異启搂,居然都是意外死亡,警方通過查閱死者的電腦和手機刘陶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門胳赌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人匙隔,你說我怎么就攤上這事疑苫。” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵捍掺,是天一觀的道長撼短。 經常有香客問我,道長挺勿,這世上最難降的妖魔是什么曲横? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮不瓶,結果婚禮上禾嫉,老公的妹妹穿的比我還像新娘。我一直安慰自己湃番,他們只是感情好夭织,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吠撮,像睡著了一般尊惰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泥兰,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天弄屡,我揣著相機與錄音,去河邊找鬼鞋诗。 笑死膀捷,一個胖子當著我的面吹牛,可吹牛的內容都是我干的削彬。 我是一名探鬼主播全庸,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼融痛!你這毒婦竟也來了壶笼?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雁刷,失蹤者是張志新(化名)和其女友劉穎覆劈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沛励,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡责语,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了目派。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坤候。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖企蹭,靈堂內的尸體忽然破棺而出白筹,到底是詐尸還是另有隱情徘键,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布遍蟋,位于F島的核電站,受9級特大地震影響螟凭,放射性物質發(fā)生泄漏虚青。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一螺男、第九天 我趴在偏房一處隱蔽的房頂上張望棒厘。 院中可真熱鬧,春花似錦下隧、人聲如沸奢人。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽何乎。三九已至,卻和暖如春土辩,著一層夾襖步出監(jiān)牢的瞬間支救,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工拷淘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留各墨,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓启涯,卻偏偏與公主長得像贬堵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子结洼,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容