RxJava的Single、Completable以及Maybe

Tony沈哲發表於2017-08-01

Maybe tomorrow.jpeg
Maybe tomorrow.jpeg

通常情況下,如果我們想要使用 RxJava 首先會想到的是使用Observable,如果要考慮到Backpressure的情況,在 RxJava2.x 時代我們會使用Flowable。除了Observable和Flowable之外,在 RxJava2.x 中還有三種型別的Observables:Single、Completable、Maybe。

型別 描述
Observable 能夠發射0或n個資料,並以成功或錯誤事件終止。
Flowable 能夠發射0或n個資料,並以成功或錯誤事件終止。 支援Backpressure,可以控制資料來源發射的速度。
Single 只發射單個資料或錯誤事件。
Completable 它從來不發射資料,只處理 onComplete 和 onError 事件。可以看成是Rx的Runnable。
Maybe 能夠發射0或者1個資料,要麼成功,要麼失敗。有點類似於Optional

從上面的表格可以看出,這五種被觀察者型別中只有Flowable能支援Backpressure,如果有需要Backpressure的情況,還是必須要使用Flowable。

Single

從SingleEmitter的原始碼可以看出,Single 只有 onSuccess 和 onError 事件。

/**
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex;

import io.reactivex.annotations.*;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Cancellable;

/**
 * Abstraction over an RxJava {@link SingleObserver} that allows associating
 * a resource with it.
 * <p>
 * All methods are safe to call from multiple threads.
 * <p>
 * Calling onSuccess or onError multiple times has no effect.
 *
 * @param <T> the value type to emit
 */
public interface SingleEmitter<T> {

    /**
     * Signal a success value.
     * @param t the value, not null
     */
    void onSuccess(@NonNull T t);

    /**
     * Signal an exception.
     * @param t the exception, not null
     */
    void onError(@NonNull Throwable t);

    /**
     * Sets a Disposable on this emitter; any previous Disposable
     * or Cancellation will be unsubscribed/cancelled.
     * @param s the disposable, null is allowed
     */
    void setDisposable(@Nullable Disposable s);

    /**
     * Sets a Cancellable on this emitter; any previous Disposable
     * or Cancellation will be unsubscribed/cancelled.
     * @param c the cancellable resource, null is allowed
     */
    void setCancellable(@Nullable Cancellable c);

    /**
     * Returns true if the downstream cancelled the sequence.
     * @return true if the downstream cancelled the sequence
     */
    boolean isDisposed();
}複製程式碼

其中,onSuccess()用於發射資料(在Observable/Flowable中使用onNext()來發射資料)。而且只能發射一個資料,後面即使再發射資料也不會做任何處理。

Single的SingleObserver中只有onSuccess、onError,並沒有onComplete。這是 Single 跟其他四種被觀察者最大的區別。

Single.create(new SingleOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull SingleEmitter<String> e) throws Exception {

                e.onSuccess("test");
            }
        }).subscribe(new Consumer<String>() {
            @Override
            public void accept(@NonNull String s) throws Exception {
                System.out.println(s);
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(@NonNull Throwable throwable) throws Exception {
                throwable.printStackTrace();
            }
        });複製程式碼

上面的程式碼,由於Observer中有兩個Consumer,還可以進一步簡化成

Single.create(new SingleOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull SingleEmitter<String> e) throws Exception {

                e.onSuccess("test");
            }
        }).subscribe(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) throws Exception {
                System.out.println(s);
            }
        });複製程式碼

Single 可以通過toXXX方法轉換成Observable、Flowable、Completable以及Maybe。

Completable

Completable在建立後,不會發射任何資料。從CompletableEmitter的原始碼可以看到

/**
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex;

import io.reactivex.annotations.*;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Cancellable;

/**
 * Abstraction over an RxJava {@link CompletableObserver} that allows associating
 * a resource with it.
 * <p>
 * All methods are safe to call from multiple threads.
 * <p>
 * Calling onComplete or onError multiple times has no effect.
 */
public interface CompletableEmitter {

    /**
     * Signal the completion.
     */
    void onComplete();

    /**
     * Signal an exception.
     * @param t the exception, not null
     */
    void onError(@NonNull Throwable t);

    /**
     * Sets a Disposable on this emitter; any previous Disposable
     * or Cancellation will be disposed/cancelled.
     * @param d the disposable, null is allowed
     */
    void setDisposable(@Nullable Disposable d);

    /**
     * Sets a Cancellable on this emitter; any previous Disposable
     * or Cancellation will be disposed/cancelled.
     * @param c the cancellable resource, null is allowed
     */
    void setCancellable(@Nullable Cancellable c);

    /**
     * Returns true if the downstream disposed the sequence.
     * @return true if the downstream disposed the sequence
     */
    boolean isDisposed();
}複製程式碼

Completable 只有 onComplete 和 onError 事件,同時 Completable 並沒有map、flatMap等操作符,它的操作符比起 Observable/Flowable 要少得多。

我們可以通過fromXXX操作符來建立一個Completable。這是一個Completable版本的Hello World。

Completable.fromAction(new Action() {
            @Override
            public void run() throws Exception {

                System.out.println("Hello World");
            }
        }).subscribe();複製程式碼

Completable 經常會結合andThen操作符

Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(@NonNull CompletableEmitter emitter) throws Exception {

                try {
                    TimeUnit.SECONDS.sleep(1);
                    emitter.onComplete();
                } catch (InterruptedException e) {
                    emitter.onError(e);
                }
            }
        }).andThen(Observable.range(1, 10))
        .subscribe(new Consumer<Integer>() {
            @Override
            public void accept(@NonNull Integer integer) throws Exception {
                System.out.println(integer);
            }
        });複製程式碼

在這裡emitter.onComplete()執行完之後,表明Completable已經完全執行完畢,接下來是執行andThen裡的操作。

列印結果如下:

1
2
3
4
5
6
7
8
9
10複製程式碼

在Completable中,andThen有多個過載的方法,正好對應了五種被觀察者的型別。

Completable       andThen(CompletableSource next)
<T> Maybe<T>      andThen(MaybeSource<T> next)
<T> Observable<T> andThen(ObservableSource<T> next)
<T> Flowable<T>   andThen(Publisher<T> next)
<T> Single<T>     andThen(SingleSource<T> next)複製程式碼

Completable 也可以通過toXXX方法轉換成Observable、Flowable、Single以及Maybe。

在網路操作中,如果遇到更新的情況,也就是Restful架構中的PUT操作,一般要麼返回原先的物件要麼只提示更新成功。下面兩個介面使用了Retrofit,分別是用於獲取簡訊驗證碼和更新使用者資訊,其中更新使用者資訊如果用PUT會更符合Restful的API。

    /**
     * 獲取簡訊驗證碼
     * @param param
     * @return
     */
    @POST("v1/user-auth")
    Completable getVerificationCode(@Body VerificationCodeParam param);

    /**
     * 使用者資訊更新介面
     * @param param
     * @return
     */
    @POST("v1/user-update")
    Completable update(@Body UpdateParam param);複製程式碼

在model類中大致會這樣寫。

/**
 * Created by Tony Shen on 2017/7/24.
 */

public class VerificationCodeModel extends HttpResponse {

    /**
     * 獲取驗證碼
     * @param activity
     * @param param
     * @return
     */
    public Completable getVerificationCode(AppCompatActivity activity, VerificationCodeParam param) {

        return apiService
                .getVerificationCode(param)
                .compose(RxJavaUtils.<VerificationCodeModel>completableToMain())
                .compose(RxLifecycle.bind(activity).<VerificationCodeModel>toLifecycleTransformer());
    }
}複製程式碼

特別要注意的是getVerificationCode返回的是Completable而不是Completable

獲取驗證碼成功則給出相應地toast提示,失敗可以做出相應地處理。

VerificationCodeModel model = new VerificationCodeModel();
model.getVerificationCode(RegisterActivity.this,param)
           .subscribe(new Action() {
                      @Override
                      public void run() throws Exception {
                              showShort(RegisterActivity.this,"傳送驗證碼成功");
                      }
            },new RxException<Throwable>(){
                      @Override
                     public void accept(@NonNull Throwable throwable) throws Exception {
                              throwable.printStackTrace();
                              ......
                     }
            });複製程式碼

獲取手機驗證碼.jpeg
獲取手機驗證碼.jpeg

Maybe

Maybe 是 RxJava2.x 之後才有的新型別,可以看成是Single和Completable的結合。

Maybe建立之後,MaybeEmitter 和 SingleEmitter 一樣並沒有onNext()方法,同樣需要通過onSuccess()方法來發射資料。

Maybe.create(new MaybeOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull MaybeEmitter<String> e) throws Exception {
                e.onSuccess("testA");
            }
        }).subscribe(new Consumer<String>() {

            @Override
            public void accept(@NonNull String s) throws Exception {

                System.out.println("s="+s);
            }
        });複製程式碼

列印出來的結果是

s=testA複製程式碼

Maybe也只能發射0或者1個資料,即使發射多個資料,後面發射的資料也不會處理。

Maybe.create(new MaybeOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull MaybeEmitter<String> e) throws Exception {
                e.onSuccess("testA");
                e.onSuccess("testB");
            }
        }).subscribe(new Consumer<String>() {

            @Override
            public void accept(@NonNull String s) throws Exception {

                System.out.println("s="+s);
            }
        });複製程式碼

列印出來的結果仍然是

s=testA複製程式碼

跟第一次執行的結果是一致的。

如果MaybeEmitter先呼叫了onComplete(),即使後面再呼叫了onSuccess()也不會發射任何資料。

Maybe.create(new MaybeOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull MaybeEmitter<String> e) throws Exception {
                e.onComplete();
                e.onSuccess("testA");
            }
        }).subscribe(new Consumer<String>() {

            @Override
            public void accept(@NonNull String s) throws Exception {

                System.out.println("s="+s);
            }
        });複製程式碼

這次就沒有列印任何資料了。

我們對上面的程式碼再做一下修改,在subscribe()中也加入onComplete(),看看列印出來的結果會是這樣的?因為SingleObserver中是沒有onComplete()方法。

Maybe.create(new MaybeOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull MaybeEmitter<String> e) throws Exception {
                e.onComplete();
                e.onSuccess("testA");
            }
        }).subscribe(new Consumer<String>() {

            @Override
            public void accept(@NonNull String s) throws Exception {

                System.out.println("s=" + s);
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(@NonNull Throwable throwable) throws Exception {

            }
        }, new Action() {
            @Override
            public void run() throws Exception {
                System.out.println("Maybe onComplete");
            }
        });複製程式碼

這次列印的結果是

Maybe onComplete複製程式碼

通過檢視Maybe相關的原始碼

    @CheckReturnValue
    @SchedulerSupport(SchedulerSupport.NONE)
    public final Disposable subscribe(Consumer<? super T> onSuccess, Consumer<? super Throwable> onError,
            Action onComplete) {
        ObjectHelper.requireNonNull(onSuccess, "onSuccess is null");
        ObjectHelper.requireNonNull(onError, "onError is null");
        ObjectHelper.requireNonNull(onComplete, "onComplete is null");
        return subscribeWith(new MaybeCallbackObserver<T>(onSuccess, onError, onComplete));
    }複製程式碼

我們可以得到,Maybe在沒有資料發射時候subscribe會呼叫MaybeObserver的onComplete()。如果Maybe有資料發射或者呼叫了onError(),是不會再執行MaybeObserver的onComplete()。

我們也可以將 Maybe 轉換成Observable、Flowable、Single,只需相應地呼叫toObservable()、toFlowable()、toSingle()。

接下來我們再來看看 Maybe 跟 Retrofit 是怎樣結合使用的?
下面的網路請求,最初返回的型別是Flowable,但是這個網路請求並不是一個連續事件流,我們只會發起一次 Post 請求返回資料並且只收到一個事件。因此,可以考慮將 onComplete() 可以跟 onNext() 合併。在這裡,嘗試我們將Flowable改成Maybe。

    @POST("v1/contents")
    Maybe<ContentModel> loadContent(@Body ContentParam param);複製程式碼

在model類中,我們大致會這樣寫。

public class ContentModel extends HttpResponse {

    public List<ContentItem> data;

    /**
     * 獲取內容
     * @param fragment
     * @param param
     * @param cacheKey
     * @return
     */
    public Maybe<ContentModel> getContent(Fragment fragment,ContentParam param,String cacheKey) {

        return apiService.loadContent(param)
                .compose(RxLifecycle.bind(fragment).<ContentModel>toLifecycleTransformer())
                .compose(RxJavaUtils.<ContentModel>maybeToMain())
                .compose(RxUtils.<ContentModel>toCacheTransformer(cacheKey,App.getInstance().cache));
    }

    ......
}複製程式碼

其中,maybeToMain()方法是用Kotlin編寫的工具方法,這些工具方法由Kotlin來編寫會顯得比較簡單和清晰,特別是lambda表示式更加直觀。

    @JvmStatic
    fun <T> maybeToMain(): MaybeTransformer<T, T> {

        return MaybeTransformer{
            upstream ->
            upstream.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
        }
    }複製程式碼

最後是真正地使用model類,如果網路請求成功則將資料展示到recyclerview上,如果失敗也會做出相應地處理。

        model.getContent(this,param,cacheKey)
                .subscribe(new Consumer<ContentModel>() {
                    @Override
                    public void accept(@io.reactivex.annotations.NonNull ContentModel model) throws Exception {

                        adapter = new NewsAdapter(mContext, model);
                        recyclerview.setAdapter(adapter);
                        spinKitView.setVisibility(View.GONE);
                    }
                }, new RxException<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        throwable.printStackTrace();
                        spinKitView.setVisibility(View.GONE);

                        ......
                    }
                });複製程式碼

獲取內容的request.jpeg
獲取內容的request.jpeg

獲取內容的response.jpeg
獲取內容的response.jpeg

總結

RxJava 有五種不同型別的被觀察者,合理地使用它們能夠寫出更簡潔優雅的程式碼。這些被觀察者在一定程度上也能夠作一些相互轉換。值得注意的是,只有Flowable是支援Backpressure的,其餘四種都不支援。

相關文章