使用Retrofit+RxJava實現(xiàn)網絡請求

安卓基礎開發(fā)庫痪寻,讓開發(fā)簡單點。
DevRing & Demo地址https://github.com/LJYcoder/DevRing

學習/參考地址:
Retrofit:
整體教程 http://blog.csdn.net/jdsjlzx/article/details/52015347
文件上傳 http://blog.csdn.net/jdsjlzx/article/details/52246114
文件下載 http://www.reibang.com/p/060d55fc1c82
Https請求 http://blog.csdn.net/dd864140130/article/details/52625666
異常處理 http://blog.csdn.net/mq2553299/article/details/70244529
失敗重試 http://blog.csdn.net/johnny901114/article/details/51539708
生命周期 http://android.jobbole.com/83847 | http://mp.weixin.qq.com/s/eedFDMIQe30rQmryLeif_Q

RxJava:
整體教程(RxJava1) https://gank.io/post/560e15be2dca930e00da1083
整體教程(RxJava2) https://mp.weixin.qq.com/s/UAEgdC2EtqSpEqvog0aoZQ
操作符 https://zhuanlan.zhihu.com/p/21926591
使用場景 http://blog.csdn.net/theone10211024/article/details/50435325
1.x與2.x區(qū)別 http://blog.csdn.net/qq_35064774/article/details/53045298

前言

Retrofit是目前主流的網絡請求框架,功能強大廉侧,操作便捷遭垛。
RxJava是實現(xiàn)異步操作的庫冰悠⌒栈螅可在線程間快速切換褐奴,同時提供許多操作符,使一些復雜的操作代碼變得清晰有條理挺益。
兩者結合使用后歉糜,使得網絡請求更加簡潔乘寒,尤其在嵌套請求等特殊場景大有作為望众。

本文側重于介紹Retrofit網絡請求,以及它是如何結合RxJava使用的伞辛。還沒了解過RxJava的建議先到上面貼出的參考地址學習烂翰,以便更好明白兩者結合的過程。

文章篇幅較長蚤氏,因為希望盡可能涵蓋常用甘耿、實用的模塊。

demo以及文章中的RxJava部分竿滨,已從1.x更新到2.x佳恬。


介紹

下面通過配置,請求于游,異常處理毁葱,生命周期管理,失敗重試贰剥,監(jiān)聽進度倾剿,封裝,混淆這幾個部分來介紹蚌成。

1. 配置

1.1 添加依賴

//Rxjava
compile 'io.reactivex.rxjava2:rxjava:2.1.6'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
//Retrofit
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'

1.2 開啟Log日志

OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
//啟用Log日志
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpClientBuilder.addInterceptor(loggingInterceptor);

開啟后前痘,則可以在Log日志中看到網絡請求相關的信息了,如請求地址担忧,請求狀態(tài)碼芹缔,返回的結果等。

Log日志截圖

1.3 開啟Gson轉換

Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
//配置轉化庫瓶盛,采用Gson
retrofitBuilder.addConverterFactory(GsonConverterFactory.create());

開啟后最欠,會自動把請求返回的結果(json字符串)自動轉化成與其結構相符的實體坡锡。

1.4 采用Rxjava

Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
//配置回調庫窒所,采用RxJava
retrofitBuilder.addCallAdapterFactory(RxJava2CallAdapterFactory.create());

1.5 設置基礎請求路徑BaseUrl

Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
//服務器地址禽额,基礎請求路徑捺氢,最好以"/"結尾
retrofitBuilder.baseUrl("https://api.douban.com/");

1.6 設置請求超時

OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
//設置請求超時時長為15秒
okHttpClientBuilder.connectTimeout(15, TimeUnit.SECONDS);

1.7 設置緩存

Interceptor cacheIntercepter=new Interceptor() {
     @Override
     public Response intercept(Chain chain) throws IOException {
         //對request的設置用來指定有網/無網下所走的方式
         //對response的設置用來指定有網/無網下的緩存時長

         Request request = chain.request();
         if (!NetworkUtil.isNetWorkAvailable(mContext)) {
             //無網絡下強制使用緩存摄乒,無論緩存是否過期,此時該請求實際上不會被發(fā)送出去悠反。
             //有網絡時則根據(jù)緩存時長來決定是否發(fā)出請求
             request = request.newBuilder()
             .cacheControl(CacheControl.FORCE_CACHE).build();
         }

         Response response = chain.proceed(request);
         if (NetworkUtil.isNetWorkAvailable(mContext)) {
             //有網絡情況下,超過1分鐘馍佑,則重新請求斋否,否則直接使用緩存數(shù)據(jù)
             int maxAge = 60; //緩存一分鐘
             String cacheControl = "public,max-age=" + maxAge;
             //當然如果你想在有網絡的情況下都直接走網絡,那么只需要
             //將其超時時間maxAge設為0即可
              return response.newBuilder()
              .header("Cache-Control",cacheControl)
              .removeHeader("Pragma").build();
         } else {
             //無網絡時直接取緩存數(shù)據(jù)拭荤,該緩存數(shù)據(jù)保存1周
             int maxStale = 60 * 60 * 24 * 7 * 1;  //1周
             return response.newBuilder()
             .header("Cache-Control", "public,only-if-cached,max-stale=" + maxStale)
             .removeHeader("Pragma").build();
         }

     }
 };
 
File cacheFile = new File(mContext.getExternalCacheDir(), "HttpCache");//緩存地址
Cache cache = new Cache(cacheFile, 1024 * 1024 * 50); //大小50Mb

//設置緩存方式茵臭、時長、地址
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder.addNetworkInterceptor(cacheIntercepter);
okHttpClientBuilder.addInterceptor(cacheIntercepter);
okHttpClientBuilder.cache(cache);

1.8 設置header

可統(tǒng)一設置

Interceptor headerInterceptor = new Interceptor() {
     @Override
     public Response intercept(Chain chain) throws IOException {
         Request originalRequest = chain.request();
         Request.Builder builder = originalRequest.newBuilder();
         //設置具體的header內容
         builder.header("timestamp", System.currentTimeMillis() + "");

         Request.Builder requestBuilder = 
         builder.method(originalRequest.method(), originalRequest.body());
         Request request = requestBuilder.build();
         return chain.proceed(request);
     }
 };
//設置統(tǒng)一的header
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder.addInterceptor(getHeaderInterceptor());

也可在請求方法中單獨設置

@Headers("Cache-Control: max-age=120")
@GET("請求地址")
Observable<HttpResult> getInfo();

或者

@GET("請求地址")
Observable<HttpResult> getInfo(@Header("token") String token);

1.9 設置https訪問

現(xiàn)在不少服務器接口采用了https的形式舅世,所以有時就需要設置https訪問旦委。
下面列舉“客戶端內置證書”時的配置方法,其他方式請參考 http://blog.csdn.net/dd864140130/article/details/52625666

//設置https訪問(驗證證書雏亚,請把服務器給的證書文件放在R.raw文件夾下)
okHttpClientBuilder.sslSocketFactory(getSSLSocketFactory(mContext, new int[]{R.raw.tomcat}));
okHttpClientBuilder.hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

getSSLSocketFactory()方法如下:

//設置https證書
protected static SSLSocketFactory getSSLSocketFactory(Context context, int[] certificates) {

    if (context == null) {
        throw new NullPointerException("context == null");
    }

    //CertificateFactory用來證書生成
    CertificateFactory certificateFactory;
    try {
        certificateFactory = CertificateFactory.getInstance("X.509");
        //Create a KeyStore containing our trusted CAs
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);

        for (int i = 0; i < certificates.length; i++) {
            //讀取本地證書
            InputStream is = context.getResources().openRawResource(certificates[i]);
            keyStore.setCertificateEntry(String.valueOf(i), certificateFactory
            .generateCertificate(is));

            if (is != null) {
                is.close();
            }
        }
        
        //Create a TrustManager that trusts the CAs in our keyStore
        TrustManagerFactory trustManagerFactory = TrustManagerFactory
        .getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        //Create an SSLContext that uses our TrustManager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
        return sslContext.getSocketFactory();

    } catch (Exception e) {

    }
    return null;
}

1.10 綜合前面的配置

OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
//設置請求超時時長
okHttpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
//啟用Log日志
okHttpClientBuilder.addInterceptor(getHttpLoggingInterceptor());
//設置緩存方式缨硝、時長、地址
okHttpClientBuilder.addNetworkInterceptor(getCacheInterceptor());
okHttpClientBuilder.addInterceptor(getCacheInterceptor());
okHttpClientBuilder.cache(getCache());
//設置https訪問(驗證證書)
okHttpClientBuilder.sslSocketFactory(getSSLSocketFactory(mContext, new int[]{R.raw.tomcat}));
okHttpClientBuilder.hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
//設置統(tǒng)一的header
okHttpClientBuilder.addInterceptor(getHeaderInterceptor());

Retrofit retrofit = new Retrofit.Builder()
           //服務器地址
           .baseUrl(UrlConstants.HOST_SITE_HTTPS)
           //配置轉化庫评凝,采用Gson
           .addConverterFactory(GsonConverterFactory.create())
           //配置回調庫追葡,采用RxJava
           .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
           //設置OKHttpClient為網絡客戶端
           .client(okHttpClientBuilder.build()).build();

配置后得到的retrofit變量用于后面發(fā)起請求。

2. 請求

2.1 創(chuàng)建API接口

定義一個接口奕短,在其中添加具體的網絡請求方法宜肉。
請求方法的格式大致如下:

@其他聲明
@請求方式("請求地址")
Observable<請求返回的實體> 請求方法名(請求參數(shù));

或者

@其他聲明
@請求方式
Observable<請求返回的實體> 請求方法名(@Url String 請求地址翎碑,請求參數(shù))谬返;

第一種格式中的請求地址,填寫基礎請求路徑baseUrl后續(xù)的部分即可日杈,當然填寫完整地址也是可以的遣铝。
第二種格式中的請求地址佑刷,需填寫完整的地址。



下面列舉Get請求Post請求文件上傳锭弊、文件下載的接口定義。
其中HttpResult是自定義的麦萤、與后臺返回的json數(shù)據(jù)結構相符的實體。

  • Get請求

請求參數(shù)逐個傳入

@GET("v2/movie/in_theaters")
Observable<HttpResult> getPlayingMovie(@Query("start") int start, @Query("count") int count);

請求參數(shù)一次性傳入(通過Map來存放key-value)

@GET("v2/movie/in_theaters")
Observable<HttpResult> getPlayingMovie(@QueryMap Map<String, String> map);

以上兩種方式扁眯,請求參數(shù)是以“?key=vale%key=value...”方式拼接到地址后面的壮莹,假如你需要的是以"/value"的方式拼接到地址后面(restful模式?)姻檀,那么可以通過@Path注解來實現(xiàn):

@GET("v2/movie/in_theaters/{start}/{count}")
Observable<HttpResult> getPlayingMovie(@Path("start") int start, @Path("count") int count);
  • Post請求

請求參數(shù)逐個傳入

@FormUrlEncoded
@POST("請求地址")
Observable<HttpResult> getInfo(@Field("token") String token, @Field("id") int id);

請求參數(shù)一次性傳入(通過Map來存放參數(shù)名和參數(shù)值)

@FormUrlEncoded
@POST("請求地址")
Observable<HttpResult> getInfo(@FieldMap Map<String, String> map);
  • 上傳文本+文件

1)上傳單個文本和單個文件

@Multipart
@POST("請求地址")
Observable<HttpResult> upLoadTextAndFile(@Part("textKey") RequestBody textBody, 
                @Part("fileKey\"; filename=\"test.png") RequestBody fileBody);

第一個參數(shù)用于傳文本命满,

--- @Part("textKey")中的"textKey"為文本參數(shù)的參數(shù)名。

--- RequestBody textBody為文本參數(shù)的參數(shù)值绣版,生成方式如下:
RequestBody textBody = RequestBody.create(MediaType.parse("text/plain"), text);

第二個參數(shù)用于傳文件胶台,

--- @Part("fileKey"; filename="test.png")
其中的"fileKey"為文件參數(shù)的參數(shù)名(由服務器后臺提供)
其中的"test.png"一般是指你希望保存在服務器的文件名字,傳入File.getName()即可

--- RequestBody fileBody為文件參數(shù)的參數(shù)值僵娃,生成方法如下:
RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);


(這里文件類型以png圖片為例概作,所以MediaType為"image/png"腋妙,
不同文件類型對應不同的type默怨,具體請參考http://tool.oschina.net/commons

2)上傳多個文本和多個文件(通過Map來傳入)

@Multipart
@POST("")
Observable<HttpResult> upLoadTextAndFiles(@PartMap Map<String, RequestBody> textBodyMap, @PartMap Map<String, RequestBody> fileBodyMap);

第一個參數(shù)用于傳文本,

Map的key為String骤素,內容請參考上方“上傳文本和單個文件”中@Part()里的值匙睹。
Map的value值為RequestBody,內容請參考上方“上傳文本和單個文件”中RequestBody的生成济竹。

第二個參數(shù)用于傳文件痕檬,

Map的key為String,內容請參考上方“上傳文本和單個文件”中@Part()里的值送浊。
Map的value值為RequestBody梦谜,內容請參考上方“上傳文本和單個文件”中RequestBody的生成。

3)另外補充多一種上傳方式(2018/07/16)袭景,以上傳多個文本和多個文件為例

@POST("")
Observable<HttpResult> upLoadTextAndFiles(@Body MultipartBody multipartBody);

MultipartBody 的生成方式如下:

MultipartBody.Builder builder = new MultipartBody.Builder();
//文本部分
builder.addFormDataPart("fromType", "1");
builder.addFormDataPart("content", "意見反饋內容");
builder.addFormDataPart("phone", "17700000066");

//文件部分
RequestBody requestBody = RequestBody.create(MediaType.parse("image/jpg"), file);
builder.addFormDataPart("image", file.getName(), requestBody); // “image”為文件參數(shù)的參數(shù)名(由服務器后臺提供)

builder.setType(MultipartBody.FORM);
MultipartBody multipartBody = builder.build();
  • 下載文件
//下載大文件時唁桩,請加上@Streaming,否則容易出現(xiàn)IO異常
@Streaming
@GET("請求地址")
Observable<ResponseBody> downloadFile();
//ResponseBody是Retrofit提供的返回實體耸棒,要下載的文件數(shù)據(jù)將包含在其中

(目前使用@Streaming進行下載的話荒澡,需添加Log攔截器(且LEVEL為BODY)才不會報錯,但是網上又說添加Log攔截器后進行下載容易OOM与殃,
所以這一塊還很懵单山,具體原因也不清楚碍现,有知道的朋友可以告訴下我)

2.2 發(fā)起請求

完成前面說的的配置和請求接口的定義后,就可以發(fā)起請求了米奸。

//構建Retrofit類
Retrofit retrofit = new Retrofit.Builder()
            //服務器地址
             .baseUrl("https://api.douban.com/")
             //配置轉化庫昼接,采用Gson
             .addConverterFactory(GsonConverterFactory.create())
             //配置回調庫,采用RxJava
             .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
             //設置OKHttpClient為網絡客戶端
             .client(okHttpClientBuilder.build()).build();

//獲取API接口
mApiService = retrofit.create(ApiService.class);
//調用之前定義好的請求方法悴晰,得到Observable
Observable observable = mApiService.xxx();

普通請求辩棒、上傳請求:

//通過Observable發(fā)起請求
observable
.subscribeOn(Schedulers.io())//指定網絡請求在io后臺線程中進行
.observeOn(AndroidSchedulers.mainThread())//指定observer回調在UI主線程中進行
.subscribe(observer);//發(fā)起請求,請求的結果會回調到訂閱者observer中

下載請求:

//通過Observable發(fā)起請求
observable
.subscribeOn(Schedulers.io()) //指定網絡請求在io后臺線程中進行
.observeOn(Schedulers.io()) //指定doOnNext的操作在io后臺線程進行
.doOnNext(new Consumer<ResponseBody>() {
           //doOnNext里的方法執(zhí)行完畢膨疏,observer里的onNext一睁、onError等方法才會執(zhí)行。
           @Override
           public void accept(ResponseBody body) throws Exception {
                 //下載文件佃却,保存到本地
                 //通過body.byteStream()可以得到輸入流者吁,然后就是常規(guī)的IO讀寫保存了。
                 ...
           }
})
.observeOn(AndroidSchedulers.mainThread()) //指定observer回調在UI主線程中進行
.subscribe(observer); //發(fā)起請求饲帅,請求的結果先回調到doOnNext進行處理复凳,再回調到observer中

3. 異常處理

使用Retrofit+RxJava發(fā)起請求后,如果請求失敗灶泵,會回調observer中的onError方法育八,該方法的參數(shù)為Throwable,并沒能反饋更直接清楚的異常信息給我們赦邻,所以有必要對Throwable異常進行處理轉換髓棋。

//observer封裝類中的代碼
@Override
public void onError(Throwable throwable) {
      if (throwable instanceof Exception) {
         onError(ThrowableHandler.handleThrowable(throwable));
      } else {
        onError(new HttpThrowable(HttpThrowable.UNKNOWN,"未知錯誤",throwable));
      }
}

//應用中具體實現(xiàn)的是下面這個onError方法
public abstract void onError(HttpThrowable httpThrowable);
public class ThrowableHandler {
    ....

    public static HttpThrowable handleThrowable(Throwable throwable) {
        if (throwable instanceof HttpException) {
            return new HttpThrowable(HttpThrowable.HTTP_ERROR, "網絡(協(xié)議)異常", throwable);
        } else if (throwable instanceof JsonParseException || throwable instanceof JSONException || throwable instanceof ParseException) {
            return new HttpThrowable(HttpThrowable.PARSE_ERROR, "數(shù)據(jù)解析異常", throwable);
        } else if (throwable instanceof UnknownHostException) {
            return new HttpThrowable(HttpThrowable.NO_NET_ERROR, "網絡連接失敗,請稍后重試", throwable);
        } else if (throwable instanceof SocketTimeoutException) {
            return new HttpThrowable(HttpThrowable.TIME_OUT_ERROR, "連接超時", throwable);
        } else if (throwable instanceof ConnectException) {
            return new HttpThrowable(HttpThrowable.CONNECT_ERROR, "連接異常", throwable);
        } else if (throwable instanceof javax.net.ssl.SSLHandshakeException) {
            return new HttpThrowable(HttpThrowable.SSL_ERROR, "證書驗證失敗", throwable);
        } else {
            return new HttpThrowable(HttpThrowable.UNKNOWN, throwable.getMessage(), throwable);
        }
    }
}
public class HttpThrowable extends Exception {
    public int errorType;
    public String message;
    public Throwable throwable;

    /**
     * 未知錯誤
     */
    public static final int UNKNOWN = 1000;
    /**
     * 解析錯誤
     */
    public static final int PARSE_ERROR = 1001;
    /**
     * 連接錯誤
     */
    public static final int CONNECT_ERROR = 1002;
    /**
     * DNS解析失敾讨蕖(無網絡)
     */
    public static final int NO_NET_ERROR = 1003;
    /**
     * 連接超時錯誤
     */
    public static final int TIME_OUT_ERROR = 1004;
    /**
     * 網絡(協(xié)議)錯誤
     */
    public static final int HTTP_ERROR = 1005;
    /**
     * 證書錯誤
     */
    public static final int SSL_ERROR = 1006;

    public HttpThrowable(int errorType, String message, Throwable throwable) {
        super(throwable);
        this.errorType = errorType;
        this.message = message;
        this.throwable = throwable;
    }
}

處理后得到ResponeThrowable按声,里面包含了異常碼code異常描述信息message,這樣就可以方便地知道請求失敗的原因了恬吕。

4. 生命周期管理

4.1 意義

如果頁面發(fā)起了網絡請求并且在請求結果返回前就已經銷毀了签则,那么我們應該在它銷毀時把相關的請求終止。一方面是為了停止無意義的請求铐料,另一方面是為了避免可能帶來的內存泄漏渐裂。

強大的RxJava可以幫助我們實現(xiàn)這一需求。下面通過 takeUntil钠惩、PublishSubject柒凉、綜合兩者進行控制 三個部分來講解如何實現(xiàn)。

4.2 takeUntil

RxJava中提供了許多操作符妻柒,這里我們需要使用takeUntil操作符扛拨。

ObservableA.takeUntil(ObservableB) 的作用是:
監(jiān)視ObservableB,當它發(fā)射內容時举塔,則停止ObservableA的發(fā)射并將其終止绑警。

下面通過示意圖和示例代碼來加深了解求泰,參考自https://zhuanlan.zhihu.com/p/21966621

示意圖:


takeUntil

示例代碼:

//下面的Observable.interval( x, TimeUnit.MILLISECONDS) 表示每隔x毫秒發(fā)射一個long類型數(shù)字,數(shù)字從0開始计盒,每次遞增1
Observable<Long> observableA = Observable.interval(300, TimeUnit.MILLISECONDS);
Observable<Long> observableB = Observable.interval(800, TimeUnit.MILLISECONDS);

observableA.takeUntil(observableB)
        .subscribe(new Observer<Long>() {
            
            //...onComplete...
            //...onError...
            
            @Override
            public void onNext(Long aLong) {
                System.out.println(aLong);
            }
        });
輸出結果為
0
1
  • 示例代碼大意:
    ObservableA每隔300ms發(fā)射一個數(shù)字(并打印出發(fā)射的數(shù)字)渴频,ObservableB每隔800ms發(fā)射一個數(shù)字。
    由于ObservableB在800ms時發(fā)射了內容北启,終止了ObservableA的發(fā)射卜朗,所以ObservableA最后只能發(fā)射0,1兩個數(shù)字。

因此咕村,我們可以利用takeUntil這一特性场钉,讓ObservableA負責網絡請求,讓ObservableB負責在頁面銷毀時發(fā)射事件懈涛,從而終止ObservableA(網絡請求)逛万。

4.3 PublishSubject

上面提到了需要一個ObservableB來負責在頁面銷毀時發(fā)射事件,PublishSubject就能充當這一角色批钠。

閱讀PublishSubject的源碼可以發(fā)現(xiàn)宇植,它既可充當Observable,擁有subscribe()等方法埋心;也可充當Observer(Subscriber)指郁,擁有onNext(),onError等方法。

它的特點是進行subscribe()訂閱后拷呆,并不立即發(fā)射事件闲坎,而是允許我們在認為合適的時機通過調用onNext(),onError()洋腮,onCompleted()來發(fā)射事件箫柳。

所以,我們需在Activity或Fragment的生命周期onDestroy()中通過PublishSubject來發(fā)射事件

//一般以下代碼寫在Activity或Fragment的基類中啥供。

PublishSubject<LifeCycleEvent> lifecycleSubject = PublishSubject.create();

//用于提供lifecycleSubject到RetrofitUtil中。
public PublishSubject<LifeCycleEvent> getLifeSubject() {
    return lifecycleSubject;
}

//一般是在onDestroy()時發(fā)射事件終止請求库糠,當然你也可以根據(jù)需求在生命周期的其他狀態(tài)中發(fā)射伙狐。
@Override
protected void onDestroy() {
    //publishSubject發(fā)射事件
    lifecycleSubject.onNext(LifeCycleEvent.DESTROY);
    super.onDestroy();
 }

4.4 進行控制

了解 takeUntil 和 PublishSubject 后,就可以綜合兩者來實現(xiàn)生命周期的控制了瞬欧。

//省略Retrofit和ApiService的構造過程
...
...
//得到負責網絡請求的Observable
Observable observableNet= mApiService.getCommingMovie(count);

//得到負責在頁面銷毀時發(fā)射事件的Observable
Observable<LifeCycleEvent> observableLife = 
     lifecycleSubject.filter(new Predicate<LifeCycleEvent>() {
             @Override
             public boolean test(LifeCycleEvent lifeCycleEvent) throws Exception {
             //當生命周期為DESTROY狀態(tài)時贷屎,發(fā)射事件
             return lifeCycleEvent.equals(LifeCycleEvent.DESTROY);
           }
     }).take(1);

//通過takeUntil將兩個Observable聯(lián)系在一起,實現(xiàn)生命周期的控制
observableNet.takeUntil(observableLife)
             .subscribeOn(Schedulers.io())//設置網絡請求在io后臺線程中進行
             .observeOn(AndroidSchedulers.mainThread())//設置請求后的回調在UI主線程中進行
             .subscribe(observer);//發(fā)起請求艘虎,請求的回調結果會傳到訂閱者observer中

還有其他方式可以實現(xiàn)生命周期的控制唉侄,具體實現(xiàn)可到以下地址查看:
http://www.reibang.com/p/d62962243c33
http://mp.weixin.qq.com/s/eedFDMIQe30rQmryLeif_Q

5.失敗重試機制

有時候用戶的網絡比較不穩(wěn)定,出現(xiàn)了請求失敗的情況野建。這時我們不一定就要直接反饋用戶請求失敗属划,而可以在失敗后嘗試重新請求恬叹,說不定這時網絡恢復穩(wěn)定請求成功了呢?同眯! 這樣或許可以提高用戶體驗绽昼。
下面介紹如何設置某個請求在失敗后自動進行重試,以及設置重試的次數(shù)须蜗、延遲重試的時間硅确。

先上代碼:

Observable observableNet= mApiService.getCommingMovie(count);

observableNet.retryWhen(new RetryFunction(3,3))//加入失敗重試機制(失敗后延遲3秒開始重試,重試3次)

.takeUntil(observableLife)//生命周期控制
.subscribeOn(Schedulers.io())//設置網絡請求在io后臺線程中進行
.observeOn(AndroidSchedulers.mainThread())//設置請求后的回調在UI主線程中進行
.subscribe(observer);//發(fā)起請求
//請求失敗重試機制
public static class RetryFunction implements Function<Observable<Throwable>, ObservableSource<?>> {

        private int retryDelaySeconds;//延遲重試的時間
        private int retryCount;//記錄當前重試次數(shù)
        private int retryCountMax;//最大重試次數(shù)

        public RetryFunction(int retryDelaySeconds, int retryCountMax) {
            this.retryDelaySeconds = retryDelaySeconds;
            this.retryCountMax = retryCountMax;
        }

        @Override
        public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {

            //方案一:使用全局變量來控制重試次數(shù)明肮,重試3次后不再重試菱农,通過代碼顯式回調onError結束請求
            return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                @Override
                public ObservableSource<?> apply(Throwable throwable) throws Exception {
                    //如果失敗的原因是UnknownHostException(DNS解析失敗,當前無網絡)柿估,則沒必要重試大莫,直接回調error結束請求即可
                    if (throwable instanceof UnknownHostException) {
                        return Observable.error(throwable);
                    }

                    //沒超過最大重試次數(shù)的話則進行重試
                    if (++retryCount <= retryCountMax) {
                        //延遲retryDelaySeconds后開始重試
                        return Observable.timer(retryDelaySeconds, TimeUnit.SECONDS);
                    }

                    return Observable.error(throwable);
                }
            });


            //方案二:使用zip控制重試次數(shù),重試3次后不再重試(會隱式回調onComplete結束請求官份,但我需要的是回調onError只厘,所以沒采用方案一)
//            return Observable.zip(throwableObservable,Observable.range(1, retryCountMax),new BiFunction<Throwable, Integer, Throwable>() {
//                @Override
//                public Throwable apply(Throwable throwable, Integer integer) throws Exception {
//                    LogUtil.e("ljy",""+integer);
//                    return throwable;
//                }
//            }).flatMap(new Function<Throwable, ObservableSource<?>>() {
//                @Override
//                public ObservableSource<?> apply(Throwable throwable) throws Exception {
//                    if (throwable instanceof UnknownHostException) {
//                        return Observable.error(throwable);
//                    }
//                    return Observable.timer(retryDelaySeconds, TimeUnit.SECONDS);
//                }
//            });

        }
}

分析:

  1. 通過observableNet.retryWhen(new RetryFunction(3,3))加入失敗重試機制,其參數(shù)RetryFunction中的apply方法會返回一個Observable舅巷,后面就稱它為ObservableRetry吧羔味。
    加入后,當網絡請求失敗時钠右,并不會直接回調observer中的onError赋元,而是會先將失敗異常throwable作為ObservableRetry的事件源。如果ObservableRetry通過onNext發(fā)射了事件飒房,則觸發(fā)重新請求搁凸,而如果ObservableRetry發(fā)射了onError/onComplete通知,則該請求正式結束狠毯。因此可以我們對apply方法中的throwableObservable進行改造护糖,然后返回一個合適的ObservableRetry來實現(xiàn)自己想要的重試效果。
  2. 代碼中對throwableObservable進行了flatMap操作嚼松,目的是對其事件throwable的類型進行判斷嫡良。如果為UnknownHostException類型,則表示無網絡DNS解析失敗献酗,這時就沒必要進行重試(都沒網絡還重試啥呀)寝受,直接通過Observable.error(throwable)結束該次請求。
  3. 然后通過全局變量 retryCount 和 retryCountMax 來控制重試的次數(shù)罕偎。重試retryCountMax次之后如果還是失敗很澄,那就通過Observable.error(throwable)放棄重試并結束請求。
  4. 代碼中還有個方案二,與方案一的區(qū)別在于使用zip操作符來控制重試的次數(shù)甩苛。
    了解過zip的應該知道其產生的ObservableZip發(fā)射的事件總量蹂楣,與組合成員中事件量少的一致。所以我們通過Observable.range(start, count)發(fā)射有限的事件浪藻,如range(1, 3)只發(fā)射"1","2","3"三個事件捐迫,從而限制了ObservableZip最終發(fā)射的事件總量不大于3,即重試的次數(shù)不超過3次爱葵。當超過3次的時候施戴,它會隱式地調用onComplete來結束該次請求(方案一是通過顯式地調用onError來結束請求,而我需要在observer的onError中反饋給用戶請求失敗萌丈,所以選擇了方案一)

6.監(jiān)聽進度

這里只講下實現(xiàn)步驟思路赞哗,代碼太多就不放上來了,大家可以直接看DevRing/Demo里的代碼辆雾,基本參考自JessYan的ProgressManager庫

6.1 上傳進度

  1. 自定義請求實體肪笋,繼承RequestBody重寫其幾個必要的方法。
    其中監(jiān)聽上傳進度主要是重寫其writeTo(BufferedSink sink)方法度迂,從該方法中獲取數(shù)據(jù)總量以及已寫入請求實體的數(shù)據(jù)量藤乙,在這里通過回調傳遞相關進度。
  2. 自定義攔截器惭墓,實現(xiàn)Interceptor的intercept(Chain chain)方法坛梁。
    通過該方法將第1步定義的請求實體應用到請求中。
  3. 添加攔截器到OkHttpClient中腊凶。
    builder.addNetworkInterceptor(progressInterceptor);

6.2 下載進度

思路和上傳進度差不多

  1. 自定義響應實體划咐,繼承ResponseBody重寫其幾個必要的方法。
    其中監(jiān)聽下載進度主要是重寫其source(Source source)方法钧萍,從該方法中獲取數(shù)據(jù)總量以及已寫入響應實體的數(shù)據(jù)量褐缠,在這里通過回調傳遞相關進度。
  2. 自定義攔截器风瘦,實現(xiàn)Interceptor的intercept(Chain chain)方法队魏。
    通過該方法將第1步定義的響應實體應用到請求中。
  3. 添加攔截器到OkHttpClient中弛秋。
    builder.addNetworkInterceptor(progressInterceptor);

7. 封裝

(2018.3.27:Demo已對封裝這一塊做了新的調整器躏,詳情請看demo,但封裝的思路還是和下文差不多的)

封裝分為 初始化配置蟹略、統(tǒng)一轉換、請求結果封裝遏佣、請求回調(Observer)封裝 四個部分進行挖炬。

7.1 初始化配置


public Retrofit initRetrofit() {
   
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
        //設置請求超時時長
        okHttpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
        //啟用Log日志
        okHttpClientBuilder.addInterceptor(getHttpLoggingInterceptor());
        //設置緩存方式、時長状婶、地址
        okHttpClientBuilder.addNetworkInterceptor(getCacheInterceptor());
        okHttpClientBuilder.addInterceptor(getCacheInterceptor());
        okHttpClientBuilder.cache(getCache());
        //設置https訪問(驗證證書)
        okHttpClientBuilder.hostnameVerifier
        (org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        //設置統(tǒng)一的header
        okHttpClientBuilder.addInterceptor(getHeaderInterceptor());

        Retrofit retrofit = new Retrofit.Builder()
                //服務器地址
                .baseUrl(UrlConstants.HOST_SITE_HTTPS)
                //配置轉化庫意敛,采用Gson
                .addConverterFactory(GsonConverterFactory.create())
                //配置回調庫馅巷,采用RxJava
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                //設置OKHttpClient為網絡客戶端
                .client(okHttpClientBuilder.build()).build();    
   
        return retrofit ;
}

7.2 統(tǒng)一轉換

由于每次請求都要進行線程切換以及生命周期的控制,頻繁地調用以下代碼

observable.takeUntil(lifecycleObservable)
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread());

因此可以使用compose方法對Observable進行統(tǒng)一的轉換

//RetrofitUtil中的方法

/**
* 對observable進行統(tǒng)一轉換草姻,并發(fā)起請求
*
* @param observable         被訂閱者
* @param observer           訂閱者
* @param event              生命周期中的某一個狀態(tài)钓猬,比如傳入DESTROY,則表示在進入destroy狀態(tài)時  
*                           lifecycleSubject會發(fā)射一個事件從而終止請求
* @param lifecycleSubject   生命周期事件發(fā)射者
*/
public static void composeToSubscribe(Observable observable, Observer observer, LifeCycleEvent event, PublishSubject<LifeCycleEvent> lifecycleSubject) {

    observable.compose(getTransformer(event, lifecycleSubject)).subscribe(observer);
}


/**
 * 獲取統(tǒng)一轉換用的Transformer
 *
 * @param event               生命周期中的某一個狀態(tài)撩独,比如傳入DESTROY敞曹,則表示在進入destroy狀態(tài)時
 *                            lifecycleSubject會發(fā)射一個事件從而終止請求
 * @param lifecycleSubject    生命周期事件發(fā)射者
 */
public static <T> ObservableTransformer<T, T> getTransformer(final LifeCycleEvent event, final PublishSubject<LifeCycleEvent> lifecycleSubject) {
    return new ObservableTransformer() {
        @Override
        public ObservableSource apply(Observable upstream) {

            //當lifecycleObservable發(fā)射事件時,終止操作综膀。
            //統(tǒng)一在請求時切入io線程澳迫,回調后進入ui線程
            //加入失敗重試機制(延遲3秒開始重試,重試3次)
            return upstream
                .takeUntil(getLifeCycleObservable(event, lifecycleSubject))
                .retryWhen(new RetryFunction(3,3))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
        }
    };
}


7.3 封裝請求結果

服務器返回的請求結果剧劝,一般分為三個部分:請求結果的狀態(tài)值橄登,請求結果的描述,返回的數(shù)據(jù)內容讥此。

{
   "status" : 1,
   "message" : "success",
   "data":{
         "name":"小明",
          "sex": 0,
          "age": 10
   }
}

其中status和message的類型是固定的拢锹,而data的類型不確定,所以data可以采用泛型表示

豆瓣接口返回的結構比較特殊萄喳,并不是上面所說的那三部分卒稳。實際結構根據(jù)服務器后臺給的來定

//與請求結果結構相符的實體類
public class HttpResult<T> {

    private int count;//請求的數(shù)量
    private int start;//請求的起始頁碼
    private int total;//得到的數(shù)據(jù)總數(shù)
    private String title;//請求結果的描述
    private T subjects;//返回的數(shù)據(jù)內容,類型不確定取胎,使用泛型T表示

    //getter&setter
    ...
}

7.4 封裝請求回調(Observer)

(DevRing中提供了三種封裝好的Observer展哭,分別用于普通請求,上傳請求(可監(jiān)聽進度)闻蛀,下載請求(可監(jiān)聽進度))

可對Observer封裝一層匪傍,作用:

  • 在onError中進行統(tǒng)一的異常處理,得到更直接詳細的異常信息
  • 在onNext中進行統(tǒng)一操作觉痛,如請求回來后役衡,先判斷token是否失效,如果失效則直接跳轉登錄頁面
  • 在onNext中對返回的結果進行處理薪棒,得到更直接的數(shù)據(jù)信息
  • 在onSubscribe中進行請求前的操作手蝎,注意,onSubscribe是執(zhí)行在 subscribe() 被調用時的線程俐芯,所以如果在onSubscribe里進行UI操作棵介,就要保證subscribe()也是調用在UI線程里。
public abstract class HttpObserver<T> implements Observer<HttpResult<T>> {

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onComplete() {

    }

    @Override
    public void onError(Throwable e) {
        if (e instanceof Exception) {
            //訪問獲得對應的Exception
            ExceptionHandler.ResponeThrowable responeThrowable = ExceptionHandler.handleException(e);
            onError(responeThrowable.code, responeThrowable.message);
        } else {
            //將Throwable 和 未知錯誤的status code返回
            ExceptionHandler.ResponeThrowable responeThrowable = new ExceptionHandler.ResponeThrowable(e, ExceptionHandler.ERROR.UNKNOWN);
            onError(responeThrowable.code, responeThrowable.message);
        }
    }

    @Override
    public void onNext(HttpResult<T> httpResult) {
        //做一些回調后需統(tǒng)一處理的事情
        //如請求回來后吧史,先判斷token是否失效
        //如果失效則直接跳轉登錄頁面
        //...

        //如果沒失效邮辽,則正常回調
        onNext(httpResult.getTitle(), httpResult.getSubjects());
    }

    //具體實現(xiàn)下面兩個方法,便可從中得到更直接詳細的信息
    public abstract void onNext(String title, T t);
    public abstract void onError(int errType, String errMessage);
}

到此吨述,封裝算是結束了岩睁,這樣使用起來就會便捷很多,整個的使用流程會在下面的“使用”中介紹揣云,也可以查看demo捕儒。

8. 混淆

在proguard-rules.pro文件中添加以下內容進行混淆配置

#Retrofit開始
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-dontwarn okio.**
#Retrofit結束


#Rxjava&RxAndroid開始
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
   long producerIndex;
   long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
#Rxjava&RxAndroid結束

使用

經過前面的配置和封裝后,下面演示一下在實際場景的使用邓夕。

1. 一般場景

請求正在上映的電影刘莹,然后在View層展示

@GET("v2/movie/in_theaters")
Observable<HttpResult<List<MovieRes>>> getPlayingMovie(@Query("count") int count);
//被訂閱者(用于發(fā)起網絡請求)
Observable observable = RetrofitUtil.getApiService().getPlayingMovie(count);

//訂閱者(網絡請求回調)
HttpObserver<List<MovieRes>> observer = new HttpObserver<List<MovieRes>>() {
            //請求成功回調
            @Override
            public void onNext(String title, List<MovieRes> list) {
                LogUtil.d(TAG,"獲取"+title+"成功");
                //通過IView接口將數(shù)據(jù)回調給View層展示
                if (mIView != null) {
                    mIView.getMovieSuccess(list);
                }
            }

            //請求失敗回調
            @Override
            public void onError(int errType, String errMessage) {
                //通過IView接口將數(shù)據(jù)回調給View層展示
                if (mIView != null) {
                    mIView.getMovieFail(errType, errMessage);
                }
            }
        };

//通過IView接口獲取View層的PublishSubject來進行生命周期的控制 
PublishSubject<LifeCycleEvent> lifecycleSubject = mIView.getLifeSubject();

//發(fā)起請求
RetrofitUtil.composeToSubscribe(observable, observer, lifecycleSubject);

2. 特殊場景

由于沒找到相符的接口,所以demo中沒有提供以下代碼翎迁。就當作提供個思路栋猖,請諒解。

2.1 嵌套請求(使用flatMap實現(xiàn))

場景:先請求token汪榔,再根據(jù)得到的token請求用戶信息蒲拉,最后在View層展示

@GET("...")
Observable<HttpResult<String>> getToken();

@GET("...")
Observable<HttpResult<UserInfo>> getUserInfo(@Query("token") String token);
//被訂閱者(用于發(fā)起網絡請求)
Observable observable = RetrofitUtil.getApiService().getToken()
    .flatMap(new Function<HttpResult<String>, ObservableSource<HttpResult<UserInfo>>{
    
        @Override
        public ObservableSource<HttpResult<UserInfo>> apply(HttpResult<String> httpResult) throws Exception {
        
          //從httpResult中得到請求來的token,然后再發(fā)起用戶信息的請求
          return RetrofitUtil.getApiService().getUserInfo(httpResult.getData());
        }
    });

//訂閱者(網絡請求回調)
HttpObserver<UserInfo> observer= new HttpObserver<UserInfo>() {
            //請求成功回調
            @Override
            public void onNext(UserInfo userInfo) {
                //通過IView接口將數(shù)據(jù)回調給View層展示
                if (mIView != null) {
                    mIView.getUserInfoSuccess(userInfo);
                }
            }

            //請求失敗回調
            @Override
            public void onError(int errType, String errMessage) {
                //通過IView接口將數(shù)據(jù)回調給View層展示
                if (mIView != null) {
                    mIView.getUserInfoFail(errType, errMessage);
                }
            }
        };

//通過IView接口獲取View層的PublishSubject來進行生命周期的控制 
PublishSubject<LifeCycleEvent> lifecycleSubject = mIView.getLifeSubject();

//發(fā)起請求
RetrofitUtil.composeToSubscribe(observable, observer, lifecycleSubject);

2.2 組合請求返回的結果(使用zip實現(xiàn))

場景:請求今日最佳男歌手痴腌,請求今日最佳女歌手雌团,將男歌手和女歌手進行組合,得到“最佳歌手組合”士聪,最后在View層展示

@GET("...")
Observable<HttpResult<Singer>> getBestSingerMale();

@GET("...")
Observable<HttpResult<Singer>> getBestSingerFemale();
//被訂閱者(用于發(fā)起網絡請求)
Observable observableMale = RetrofitUtil.getApiService().getBestSingerMale();
Observable observableFemale = RetrofitUtil.getApiService().getBestSingerFemale();
Observable observableGroup =
    Observable.zip(observableMale , observableFemale , 
         new BiFunction<HttpResult<Singer>, HttpResult<Singer>, HttpResult<SingerGroup>() {
             @Override
             public HttpResult<SingerGroup> apply(HttpResult<Singer> resultMale, 
                                                 HttpResult<Singer> resultFemale) {
            
             //組合男女歌手
             Singer singerMale = resultMale.getData();
             Singer singerFemale = resultFemale.getData();
             SingerGroup singerGroup = new SingerGroup(singerMale, singerFemale);
             
             HttpResult<SingerGroup> resultGroup = new HttpResult<SingerGroup>();
             resultGroup.setData(singerGroup); 
             
             return resultGroup;
            }
         });

//訂閱者(網絡請求回調)
HttpObserver<SingerGroup> observer= new HttpObserver<SingerGroup>() {
            //請求成功回調
            @Override
            public void onNext(SingerGroup singerGroup) {
                //通過IView接口將數(shù)據(jù)回調給View層展示
                if (mIView != null) {
                    mIView.getSingerGroupSuccess(singerGroup);
                }
            }

            //請求失敗回調
            @Override
            public void onError(int errType, String errMessage) {
                //通過IView接口將數(shù)據(jù)回調給View層展示
                if (mIView != null) {
                    mIView.getSingerGroupFail(errType, errMessage);
                }
            }
        };

//通過IView接口獲取View層的PublishSubject來進行生命周期的控制    
PublishSubject<LifeCycleEvent> lifecycleSubject = mIView.getLifeSubject();

//發(fā)起請求
RetrofitUtil.composeToSubscribe(observableGroup , observer, lifecycleSubject);

實際開發(fā)中肯定還有其他的特殊場景锦援,關鍵是運用好RxJava的操作符。操作符的學習地址已貼在文章頂部剥悟。


更新:

已將demo和文章中關于Rxjava的部分從1.x改為2.x
這里貼一下RxJava2與RxJava1的區(qū)別總結(隨筆記錄灵寺,僅供參考):

  • RxJava2 按是否可以背壓處理,分成Observable和Flowable区岗,Observable的訂閱者為Observer略板,F(xiàn)lowable的訂閱者為Subscriber。
    不了解背壓的可以看這個系列的5-9篇慈缔。

  • 背壓處理
    1)上游(Flowable)通過emitter.requested()查看事件容器的剩余空間叮称。下游(Subscriber)通過subscription.request(n)從事件容器中請求并消耗事件(消耗一個事件并不代表事件容器立刻多出一個位置)
    2)四種策略 BUFFER,ERROR藐鹤,DROP瓤檐,LATEST
    Buffer:事件容器的空間不限制,非Buffer策略時事件容器大小為128
    ERROR:當事件容器溢出時會報MissingBackpressureException娱节。該策略下挠蛉,當下游累計消耗完96個事件后,才會給事件容器騰出96個位置肄满。
    DROP: 事件容器裝入128個事件后碌秸,剩下的將不會裝入绍移,當下游累計消耗完128個事件后悄窃,才會給事件容器騰出128個位置讥电,這時再取當前時刻發(fā)送的事件裝入。
    LATEST: 與DROP類似轧抗,但它會保證取到最后發(fā)射的事件

  • Observable多了幾個小伙伴:Single恩敌、Completable、Maybe横媚。他們都繼承了ObservableSource纠炮。
    Single/SingleObserver:只發(fā)送/接收onNext和onError,且只發(fā)送一次
    Completable/Completable:只發(fā)送/接收onComplete和onError
    Maybe:Single與Completable的結合

  • Func1改為Function灯蝴,F(xiàn)unc2..n改為BiFunction恢口。其中的方法call改成了apply。另外對于filter()過濾穷躁,其參數(shù)為不為Function而是Predicate

  • Action1改為Consumer耕肩,Action2改為BiConsumer。其中的方法call改成了accept问潭。

  • Observer/Subscriber的抽象方法中多了一個onSubscribe(Disposable/Subscription)猿诸,類似1.x的onStart方法,它在subscribe()時調用狡忙。其中的參數(shù)Disposable/Subscription可以用來取消訂閱/查詢訂閱狀態(tài)梳虽,Subscription還可用于背壓中請求消耗事件。

  • 不再能發(fā)送null事件灾茁,Observable<Void> 不再發(fā)射任何值窜觉,而是正常結束或者拋出空指針。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末北专,一起剝皮案震驚了整個濱河市禀挫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌逗余,老刑警劉巖特咆,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異录粱,居然都是意外死亡腻格,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門啥繁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菜职,“玉大人,你說我怎么就攤上這事旗闽〕旰耍” “怎么了蜜另?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嫡意。 經常有香客問我举瑰,道長,這世上最難降的妖魔是什么蔬螟? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任此迅,我火速辦了婚禮,結果婚禮上旧巾,老公的妹妹穿的比我還像新娘耸序。我一直安慰自己,他們只是感情好鲁猩,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布坎怪。 她就那樣靜靜地躺著,像睡著了一般廓握。 火紅的嫁衣襯著肌膚如雪搅窿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天疾棵,我揣著相機與錄音戈钢,去河邊找鬼。 笑死是尔,一個胖子當著我的面吹牛殉了,可吹牛的內容都是我干的。 我是一名探鬼主播拟枚,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼薪铜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恩溅?” 一聲冷哼從身側響起隔箍,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脚乡,沒想到半個月后蜒滩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡奶稠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年俯艰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锌订。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡竹握,死狀恐怖,靈堂內的尸體忽然破棺而出辆飘,到底是詐尸還是另有隱情啦辐,我是刑警寧澤谓传,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站芹关,受9級特大地震影響续挟,放射性物質發(fā)生泄漏。R本人自食惡果不足惜充边,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一庸推、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浇冰,春花似錦、人聲如沸聋亡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坡倔。三九已至漂佩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罪塔,已是汗流浹背投蝉。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留征堪,地道東北人瘩缆。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像佃蚜,于是被迫代替她去往敵國和親庸娱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345