淺談RxJava與2.0的新特性

yangxi_001發表於2017-06-13

簡介

說起 RxJava ,相信諸多 Android 開發者都不會陌生。作為一個知名的響應式程式設計庫,從前年開始逐漸變得火熱,從小眾到被眾多 Android 開發者們廣泛引入與流傳,其在 GitHub 的倉庫截止筆者寫這篇文章時,已經有16400+個 star 。甚至有一些大牛專門為 Android 寫了 RxJava 的適配庫,如

為什麼 RxJava 如此受到 Android 開發者們的歡迎。我想不外乎兩個原因。 1. 非同步 2. 鏈式操作

非同步

對 Android 執行緒有所瞭解的朋友都知道, Android的 UI 繪製 與 事件響應是在主執行緒的,為了保證介面的流暢性,很多耗時操作如讀寫資料庫、讀寫檔案、請求網路,我們都會挪到非同步執行緒去完成,再回撥到主執行緒。當然在4.0以後主執行緒直接就不允許請求網路了。

在過去沒有 RxJava 的時候,開發者一般都是通過 AsyncTask , Thread ,更好些的就是通過執行緒池來完成這些任務。而有了 RxJava 以後,簡簡單單的一句話就可以隨意的切換執行緒,簡直不用太舒服。

最典型的 RxJava 中的Observable類,提供了2個函式, 分別是subscribeOnobserveOn。前者可以切換被觀察時的執行緒(如果說資料發射的執行緒不夠嚴謹,資料並非一定在觀察時發射的,尤其是開發者自定義OnSubscribe時),後者可以切換資料被消費時的執行緒。

舉一個切換執行緒的例子:

Log.i("debug", Thread.currentThread().getName());  
Observable.empty()  
        .doOnCompleted(new Action0() {
            @Override
            public void call() {
                Log.i("debug", Thread.currentThread().getName());
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .doOnCompleted(new Action0() {
            @Override
            public void call() {
                Log.i("debug", Thread.currentThread().getName());
            }
        })
        .subscribe();   

這裡我們沒有任何資料,就僅僅發射了一個onComplete,但是在切換執行緒的程式碼中,我們增加了onComplte時要額外執行的程式碼,輸出結果如下:

08-27 10:47:41.173 6741-6741/com.dieyidezui.rxjavademo I/debug: main  
08-27 10:47:41.201 6741-6762/com.dieyidezui.rxjavademo I/debug: RxIoScheduler-2  
08-27 10:47:41.217 6741-6741/com.dieyidezui.rxjavademo I/debug: main  

這僅僅是簡單的例子, RxJava 提供了很多便捷的操作符供我們使用,如mapfilterflatMapmergeconcat等。可見當熟練使用後對我們的程式設計效率確實有很大幫助。尤其是 MVP 模式, RxJava 與之結合可謂是”天作之合”。

鏈式操作

上面筆者演示的程式碼其實就是 RxJava 的典型使用方式:

  1. 發射資料來源
  2. 中間操作
  3. 處理結果

其中中間操作包含諸多用法, 如果切換執行緒,變換資料等。

為什麼我說鏈式操作很好。第一,鏈式邏輯替代深度回撥邏輯,容易編寫,不易出 BUG 。第二,RxJava 提供諸多了整體處理資料的操作符,非常實用。第三,配合 Java8 的 lambda 表示式,使程式碼簡短優雅。

好了,對 RxJava 的介紹就此為止了。進階用法、原理剖析以後會有專門的文章。對 RxJava 不熟悉的同學,建議先去看一下官方的 wiki 。連結: https://github.com/ReactiveX/RxJava/wiki

RxJava2.0

前天, RxJava終於釋出了2.0 RC1 版本,一直關注於此的筆者立刻就進去嚐鮮了。結合官方的介紹,筆者總結並翻譯了一些與 1.x 的異同與大家分享。

包名與MAVEN依賴

首先要說的就是 RxJava 2和1是互相獨立的。因此包名與 maven 的依賴也是不一樣的,就類似於 OkHttp 3與2一樣。 RxJava 2.x的依賴是全新的io.reactivex.rxjava2:rxjava:2.x.y,並且類處於該io.reactivex包名下,而不再是rx

介面變化

RxJava2 是遵循 Reactive Streams Specification 的規範完成的,新的特性依賴其提供的4個基礎介面。分別是

  • Publisher
  • Subscriber
  • Subscription
  • Processor

Flowable與Observable

新的實現叫做Flowable, 同時舊的Observable也保留了。因為在 RxJava1.x 中,有很多事件不被能正確的背壓,從而丟擲MissingBackpressureException

舉個簡單的例子,在 RxJava1.x 中的 observeOn, 因為是切換了消費者的執行緒,因此內部實現用佇列儲存事件。在 Android 中預設的 buffersize 大小是16,因此當消費比生產慢時, 佇列中的數目積累到超過16個,就會丟擲MissingBackpressureException, 初學者很難明白為什麼會這樣,使得學習曲線異常得陡峭。

而在 2.0 中,Observable 不再支援背壓,而Flowable 支援非阻塞式的背壓。並且規範要求,所有的操作符強制支援背壓。幸運的是, Flowable 中的操作符大多與舊有的 Observable 類似。

Single、Completable

Single 與 Completable 都基於新的 Reactive Streams 的思想重新設計了介面,主要是消費者的介面, 現在他們是這樣的:

interface SingleObserver<T> {  
    void onSubscribe(Disposable d);
    void onSuccess(T value);
    void onError(Throwable error);
}

interface CompletableObserver<T> {  
    void onSubscribe(Disposable d);
    void onComplete();
    void onError(Throwable error);
}

Subscriber

對比一下 Subscriber :

public interface Subscriber<T> {  
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}

我們會發現和以前不一樣的是多了一個onSubscribe的方法,Subscription如下:

Subscription

public interface Subscription {  
    public void request(long n);
    public void cancel();
}

熟悉 RxJava 1.x 的朋友能發現, 新的Subscription更像是綜合了舊的ProducerSubscription的綜合體。他既可以向上遊請求資料,又可以打斷並釋放資源。而舊的Subscription在這裡因為名字被佔,而被重新命名成了Disposable

Disposable

public interface Disposable {  
    void dispose();
    boolean isDisposed();
}

這裡最大的不同就是這個onSubscribe,根據 Specification, 這個函式一定是第一個被呼叫的, 然後就會傳給呼叫方一個Subscription,通過這種方式組織新的背壓關係。當我們消費資料時,可以通過Subscription物件,自己決定請求資料。

這裡就可以解釋上面的非阻塞的背壓。舊的阻塞式的背壓,就是根據下游的消費速度,中游可以選擇阻塞住等待下游的消費,隨後向上遊請求資料。而新的非阻塞就不在有中間阻塞的過程,由下游自己決定取多少,還有背壓策略,如拋棄最新、拋棄最舊、快取、拋異常等。

而新的介面帶來的新的呼叫方式與舊的也不太一樣, subscribe後不再會有 Subscription 也就是如今的 Disposable,為了保持向後的相容, Flowable 提供了 subscribeWith方法 返回當前的Subscriber物件, 並且同時提供了DefaultSubscriberResourceSubscriberDisposableSubscriber,讓他們提供了Disposable介面, 可以完成和以前類似的程式碼:

ResourceSubscriber<Integer> subscriber = new ResourceSubscriber<Integer>() {  
    @Override
    public void onStart() {
        request(Long.MAX_VALUE);
    }

    @Override
    public void onNext(Integer t) {
        System.out.println(t);
    }

    @Override
    public void onError(Throwable t) {
        t.printStackTrace();
    }

    @Override
    public void onComplete() {
        System.out.println("Done");
    }
};

Flowable.range(1, 10).delay(1, TimeUnit.SECONDS).subscribe(subscriber);

subscriber.dispose();  

收回 create 方法許可權

在RxJava 1.x 最明顯的問題就是由於 create 的太過開放,導致其被開發者濫用,而不是學習使用提供的操作符。

並且使用者對 RxJava 不夠了解,導致各種各樣的問題,如背壓、異常處理等。

由於規範要求所有的操作符強制支援背壓,因此新的 create 採用了保守的設計,讓使用者實現FlowableOnSubscribe介面,並選取背壓策略,然後在內部實現封裝支援背壓,簡單的例子如下:

Flowable.create((FlowableEmitter<Integer> emitter) -> {  
    emitter.onNext(1);
    emitter.onNext(2);
    emitter.onComplete();
}, BackpressureStrategy.BUFFER);

Functions可以丟擲異常

新的ActionXFunctionX的方法宣告都增加了一個throws Exception,這帶來了顯而易見的好處,現在我們可以這樣寫:

Flowable.just("file.txt")  
.map(name -> Files.readLines(name))
.subscribe(lines -> System.out.println(lines.size()), Throwable::printStackTrace);

而在以前是不行的, 因為Files.readLines(name)會顯式的丟擲一個IOException。這樣對 lambda 更加友好,而不必再去 try catch 。

Scheduler可以直接schedule

在以前是必須要先createWorker,用 Worker 物件去 shedule, 現在可以直接在Scheduler用這些方法:

public abstract class Scheduler {

    public Disposable scheduleDirect(Runnable task) { ... }

    public Disposable scheduleDirect(Runnable task, long delay, TimeUnit unit) { ... }

    public Disposable scheduleDirectPeriodically(Runnable task, long initialDelay, 
        long period, TimeUnit unit) { ... }

    public long now(TimeUnit unit) { ... }

    // ... rest is the same: lifecycle methods, worker creation
}

這算是一個小優化,方便開發者使用。

Observable的一些繼承併入了Flowable中

ConnectableObservableBlockObservable等,這樣可以直接在Flowable中寫出這樣的程式碼:

List<Integer> list = Flowable.range(1, 100).toList().blockingFirst();  

其他修改

還有一些普通開發者不太在意的修改:

  • hook方式變化,現在可以通過提供介面在 runtime hook
  • 部分在 1.x 中 被標記@Beta@Experimental的操作符現在合併到正式版裡了
  • 由於類結構的變動,一些類名的變化

等其他變動。

結語

RxJava 作為開源的經典之作,筆者一直都有所關注。後續筆者會繼續為大家帶來 RxJava 的原始碼解析與進階使用系列等。感謝大家的閱讀,如有不知之處,歡迎討論交流。

相關文章