淺談RxJava與2.0的新特性
簡介
說起 RxJava ,相信諸多 Android 開發者都不會陌生。作為一個知名的響應式程式設計庫,從前年開始逐漸變得火熱,從小眾到被眾多 Android 開發者們廣泛引入與流傳,其在 GitHub 的倉庫截止筆者寫這篇文章時,已經有16400+個 star 。甚至有一些大牛專門為 Android 寫了 RxJava 的適配庫,如
為什麼 RxJava 如此受到 Android 開發者們的歡迎。我想不外乎兩個原因。 1. 非同步 2. 鏈式操作
非同步
對 Android 執行緒有所瞭解的朋友都知道, Android的 UI 繪製 與 事件響應是在主執行緒的,為了保證介面的流暢性,很多耗時操作如讀寫資料庫、讀寫檔案、請求網路,我們都會挪到非同步執行緒去完成,再回撥到主執行緒。當然在4.0以後主執行緒直接就不允許請求網路了。
在過去沒有 RxJava 的時候,開發者一般都是通過 AsyncTask , Thread ,更好些的就是通過執行緒池來完成這些任務。而有了 RxJava 以後,簡簡單單的一句話就可以隨意的切換執行緒,簡直不用太舒服。
最典型的 RxJava 中的Observable
類,提供了2個函式, 分別是subscribeOn
與observeOn
。前者可以切換被觀察時的執行緒(如果說資料發射的執行緒不夠嚴謹,資料並非一定在觀察時發射的,尤其是開發者自定義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 提供了很多便捷的操作符供我們使用,如map
、filter
、flatMap
、merge
、concat
等。可見當熟練使用後對我們的程式設計效率確實有很大幫助。尤其是
MVP 模式, RxJava 與之結合可謂是”天作之合”。
鏈式操作
上面筆者演示的程式碼其實就是 RxJava 的典型使用方式:
- 發射資料來源
- 中間操作
- 處理結果
其中中間操作包含諸多用法, 如果切換執行緒,變換資料等。
為什麼我說鏈式操作很好。第一,鏈式邏輯替代深度回撥邏輯,容易編寫,不易出 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
更像是綜合了舊的Producer
與Subscription
的綜合體。他既可以向上遊請求資料,又可以打斷並釋放資源。而舊的Subscription
在這裡因為名字被佔,而被重新命名成了Disposable
Disposable
public interface Disposable {
void dispose();
boolean isDisposed();
}
這裡最大的不同就是這個onSubscribe
,根據 Specification, 這個函式一定是第一個被呼叫的, 然後就會傳給呼叫方一個Subscription
,通過這種方式組織新的背壓關係。當我們消費資料時,可以通過Subscription
物件,自己決定請求資料。
這裡就可以解釋上面的非阻塞的背壓。舊的阻塞式的背壓,就是根據下游的消費速度,中游可以選擇阻塞住等待下游的消費,隨後向上遊請求資料。而新的非阻塞就不在有中間阻塞的過程,由下游自己決定取多少,還有背壓策略,如拋棄最新、拋棄最舊、快取、拋異常等。
而新的介面帶來的新的呼叫方式與舊的也不太一樣, subscribe
後不再會有 Subscription 也就是如今的 Disposable,為了保持向後的相容,
Flowable 提供了 subscribeWith方法
返回當前的Subscriber
物件,
並且同時提供了DefaultSubscriber
, ResourceSubscriber
,DisposableSubscriber
,讓他們提供了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可以丟擲異常
新的ActionX
、FunctionX
的方法宣告都增加了一個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中
如ConnectableObservable
、BlockObservable
等,這樣可以直接在Flowable
中寫出這樣的程式碼:
List<Integer> list = Flowable.range(1, 100).toList().blockingFirst();
其他修改
還有一些普通開發者不太在意的修改:
- hook方式變化,現在可以通過提供介面在 runtime hook
- 部分在 1.x 中 被標記
@Beta
、@Experimental
的操作符現在合併到正式版裡了 - 由於類結構的變動,一些類名的變化
等其他變動。
結語
RxJava 作為開源的經典之作,筆者一直都有所關注。後續筆者會繼續為大家帶來 RxJava 的原始碼解析與進階使用系列等。感謝大家的閱讀,如有不知之處,歡迎討論交流。
相關文章
- 〔譯〕TypeScript 2.0 的新特性TypeScript
- C++ 2.0新特性C++
- mysql淺談--事務ACID特性MySql
- 新特性:postgresql的vacuum漫談SQL
- 關於 RxJava 最友好的文章—— RxJava 2.0 全新來襲RxJava
- PHP 7.4 新特性 —— 箭頭函式 2.0PHP函式
- Spring Boot 2.0 新特性(二):新增事件ApplicationStartedEventSpring Boot事件APPdev
- 淺談ActiveMQ與使用MQ
- VirtualDOM與DomDiff淺談
- C++2.0——語言新特性之Variadic TemplatesC++
- 淺談src與href的區別
- 淺談:HTTP 2.0 的二進位制幀、流、多路複用HTTP
- 淺談px,em與remREM
- 淺談JVM與垃圾回收JVM
- 談談深拷貝與淺拷貝
- 給初學者的RxJava2.0教程(三)RxJava
- 給初學者的RxJava2.0教程(七)RxJava
- 給初學者的 RxJava2.0 教程 (四)RxJava
- vue2.0的基本特性Vue
- 淺談js的this指向與解決思路JS
- 淺談JavaScript的防抖與節流JavaScript
- 淺談 Dart 類與類的基本方法Dart
- RxJava2.0——變換操作符RxJava
- WWDC 2018:Safari與WebKit的新特性WebKit
- 官方解讀:TensorFlow 2.0中即將到來的所有新特性
- 在Vue3.0之前,回顧Vue2.0新特性的使用Vue
- 淺談前端MVC與MVVC模式前端MVC模式
- 淺談貪心與動歸
- 淺談js的記憶體與閉包JS記憶體
- Flink ML的新特性解析與應用
- 淺談 Slack Channel 支援的一些提高工作效率的特性
- 從玩法、敘事、主題三個角度淺要談談《死亡擱淺》的好與壞
- Oracle 21c新特性預覽與日常管理相關的幾個新特性Oracle
- 淺談DDos攻擊與防禦
- 淺談VueUse設計與實現Vue
- 淺淺談ReduxRedux
- Oracle merge 與 PG新特性 UPSERTOracle
- 淺談 JavaScript 中的防抖與節流(一)JavaScript
- 淺談MySQL的B樹索引與索引優化MySql索引優化