觀察者模式的運用
傳統的Java觀察者模式可以參考此篇博客:Java觀察者模式案例簡析
RxJava 是基于Java的觀察者模式開展的。構建被觀察者(Observable/Flowable)、觀察者(Observer/Subscriber),并通過建立兩者的訂閱關系實現觀察,在事件的傳遞過程中可以對事件進行各種處理。
在rxjava 1.x、rxjava 2.x里,Observable是被觀察者,Observer是觀察者,正常邏輯是觀察者通過subscribe訂閱Observable的事件處理,當Observable發射事件時Observer接收數據。但為了保持流式API風格,觀察者訂閱被觀察者的代碼順序設計有一些調整。
如:
Observable.subscribe(Observer);
RxJava 1.x 和RxJava 2.x的主要區別
在RxJava 2.x中的觀察者模式有兩種。而Flowable作為被觀察者是專門支持背壓的。這也是RxJava 1.x 和RxJava 2.x的主要區別。當然還有一些區別是操作符、接口的不兼容更新。
- Observable ( 被觀察者 ) / Observer ( 觀察者 )
-
Flowable (被觀察者)/ Subscriber (觀察者)
image.png
RxJava1.x 平滑升級到RxJava2.x
由于RxJava2.0變化較大無法直接升級,幸運的是,官方提供了RxJava2Interop這個庫,可以方便地將RxJava1.x升級到RxJava2.x,或者將RxJava2.x轉回RxJava1.x。
RxJava2Interop
經典流式API調用風格
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> e) throws Exception {
e.onNext(1);
e.onNext(2);
e.onNext(3);
}
})
// ...省略很多在發射過程中的流式處理代碼
.subscribe(new Observer<Integer>() {
private Disposable mDisposable;
@Override
public void onSubscribe(Disposable d) {
mDisposable = d;
}
@Override
public void onNext(Integer integer) {
Log.d("onNext", "" + integer);
//新增的Disposable可以做到切斷的操作,讓Observer觀察者不再接收上游事件。
if (integer == 3) {
mDisposable.dispose();
Log.d("onNext", "已停止接收事件");
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
打印結果:
11-28 13:00:42.195 29930-29930/? D/onNext: 1
11-28 13:00:42.195 29930-29930/? D/onNext: 2
11-28 13:00:42.195 29930-29930/? D/onNext: 3
11-28 13:00:42.195 29930-29930/? D/onNext: 已停止接收事件
Rxjava 線程調度
subscribeOn() 指定的就是發射事件的線程,observerOn 指定的就是訂閱者接收事件的線程。
多次指定發射事件的線程只有第一次指定的有效,也就是說多次調用 subscribeOn() 只有第一次的有效,其余的會被忽略。
但多次指定訂閱者接收線程是可以的,也就是說每調用一次 observerOn(),下游的線程就會切換一次。
Rxjava 2.x常用操作符
1,Function<T, R> ——將輸入的value類型T轉換成輸出的value類型R。通常結合Map操作符。
/**
* A functional interface that takes a value and returns another value, possibly with a
* different type and allows throwing a checked exception.
*
* @param <T> the input value type
* @param <R> the output value type
*/
public interface Function<T, R> {
/**
* Apply some calculation to the input value and return some other value.
* @param t the input value
* @return the output value
* @throws Exception on error
*/
@NonNull
R apply(@NonNull T t) throws Exception;
}
2,Map——將一個Observable被觀察者通過特定函數的執行,轉換成另一種Observable被觀察者。
在 2.x 中和 1.x 中作用幾乎一致,不同點在于:2.x 將 1.x 中的 Func1 和 Func2 改為了 Function 和 BiFunction。
/**
* Returns an Observable that applies a specified function to each item emitted by the source ObservableSource and emits the results of these function applications.
*
@SchedulerSupport(SchedulerSupport.NONE)
public final <R> Observable<R> map(Function<? super T, ? extends R> mapper) {
ObjectHelper.requireNonNull(mapper, "mapper is null");
return RxJavaPlugins.onAssembly(new ObservableMap<T, R>(this, mapper));
}
3,Consumer——接收一個單獨的數據,類似于一個簡化版的觀察者observer。
/**
* A functional interface (callback) that accepts a single value.
* @param <T> the value type
*/
public interface Consumer<T> {
/**
* Consume the given value.
* @param t the value
* @throws Exception on error
*/
void accept(@NonNull T t) throws Exception;
}
4,distinct——去重操作符。即先有的數字保留,重復的數字去除并保留原先順序的方式輸出。
Observable.just(2, 1, 2, 3, 4, 2, 3)
.distinct()
.subscribe(new Consumer<Integer>() {
@Override
public void accept(@NonNull Integer integer) throws Exception {
Log.d("accept", "" + integer);
}
});
輸出
11-28 11:59:17.511 7052-7052/com.zjrb.sjzsw D/accept: 2
11-28 11:59:17.511 7052-7052/com.zjrb.sjzsw D/accept: 1
11-28 11:59:17.511 7052-7052/com.zjrb.sjzsw D/accept: 3
11-28 11:59:17.511 7052-7052/com.zjrb.sjzsw D/accept: 4
5,concat—— 可以做到不交錯的發射兩個甚至多個 Observable 的發射事件,并且只有前一個 Observable 終止(onComplete) 后才會訂閱下一個 Observable。比如可以采用 concat 操作符先讀取緩存再通過網絡請求獲取數據。
案例說明:
Observable observable = Observable.just(1, 2, 3, 4, 5, 6)
.map(new Function<Integer, Integer>() {
@Override
public Integer apply(@NonNull Integer integer) throws Exception {
return integer + 1;
}
});
Observable.concat(Observable.just(-1, -2, -3, -4, -5, -6), observable)
.subscribe(new Consumer<Integer>() {
@Override
public void accept(@NonNull Integer integer) throws Exception {
Log.d("accept", "" + integer);
}
});
打印輸出:看吧,兩個Observable是按照順序依次無交錯執行的。
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -1
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -2
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -3
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -4
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -5
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: -6
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: 2
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: 3
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: 4
11-29 01:48:50.430 31564-31564/com.zjrb.sjzsw D/accept: 5
11-29 01:48:50.431 31564-31564/com.zjrb.sjzsw D/accept: 6
11-29 01:48:50.431 31564-31564/com.zjrb.sjzsw D/accept: 7
注:熟悉操作符的目的在于,不同場景中都能隨時想到有對應的工具可用。
背壓
背壓:指在異步場景中,被觀察者發送事件速度遠快于觀察者的處理速度的情況下,一種告訴上游的被觀察者降低發送速度的策略。
因為事件產生的速度遠遠快于事件消費的速度,最終導致數據積累越來越多,從而導致OOM等異常。這就是背壓產生的必要性。
RxJava2.0中,Flowable是能夠支持Backpressure的Observable,是對Observable的補充(而不是替代)。所以Observable被觀察者支持的API,Flowable也都支持,并且Flowable的API里也都強制支持背壓。
背壓經典代碼
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
if (e.requested() != 0) {
for (int i = 0; i < 10; i++) {
e.onNext(i + 1);
Log.d(TAG, "已發送" + (i + 1) + "個——剩下" + e.requested());
}
e.onComplete();
}
}
}, BackpressureStrategy.LATEST)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
subscription = s;
}
@Override
public void onNext(Integer s) {
Log.d(TAG, "接收 ——" + s);
if (s == 9){
subscription.cancel();
}
}
@Override
public void onError(Throwable t) {
Log.d(TAG, "接收錯誤——" + t);
}
@Override
public void onComplete() {
}
});
外部調用subscription請求配合配額11個。
if (subscription != null) {
subscription.request(11);
}
在BackpressureStrategy.LATEST背壓策略下,上游發射10個事件,下游由外部調用請求發布配額指令,當下游接收到第9個事件時暫停上游發布(此操作會清空上游事件源)。
背壓策略
- BackpressureStrategy.MISSING
此策略下,上游發射的數據不做緩存也不丟棄,下游處理溢出的問題。簡單說就是沒有背壓。 - BackpressureStrategy.ERROR
此策略下,在上游發射速度過快并超出下游接收速度時,拋出MissingBackpressureException異常。 - BackpressureStrategy.BUFFER
此策略下,把上游發射過來的所有數據全部緩存在緩存區,不做丟棄,待下游接收。 - BackpressureStrategy.DROP
此策略下,相當于一種令牌機制(或者配額機制),下游通過request請求產生令牌(配額)給上游,上游接到多少令牌,就給下游發送多少數據。當令牌數消耗到0的時候,上游開始丟棄數據。BackpressureStrategy.LATEST的策略和此類似。 - BackpressureStrategy.LATEST
此策略和BackpressureStrategy.DROP的策略類似,但在令牌數為0的時候有一點微妙的區別:onBackpressureDrop直接丟棄數據,不緩存任何數據;而onBackpressureLatest則緩存最新的一條數據,這樣當上游接到新令牌的時候,它就先把緩存的上一條“最新”數據發送給下游。
/**
* Represents the options for applying backpressure to a source sequence.
*/
public enum BackpressureStrategy {
/**
* OnNext events are written without any buffering or dropping.
* Downstream has to deal with any overflow.
* <p>Useful when one applies one of the custom-parameter onBackpressureXXX operators.
*/
MISSING,
/**
* Signals a MissingBackpressureException in case the downstream can't keep up.
*/
ERROR,
/**
* Buffers <em>all</em> onNext values until the downstream consumes it.
*/
BUFFER,
/**
* Drops the most recent onNext value if the downstream can't keep up.
*/
DROP,
/**
* Keeps only the latest onNext value, overwriting any previous value if the
* downstream can't keep up.
*/
LATEST
}
Flowable案例代碼
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
e.onNext(1);
e.onNext(2);
e.onNext(3);
e.onComplete();
e.onNext(4);
}
}, BackpressureStrategy.ERROR)
//下面兩行代碼執行線程切換,達到異步效果
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
subscription = s;
// subscription.request(1);
}
@Override
public void onNext(Integer s) {
// subscription.request(1);
Log.d(TAG, "接收——" + s);
// subscription.cancel();
}
@Override
public void onError(Throwable t) {
Log.d(TAG, "接收錯誤——" + t);
}
@Override
public void onComplete() {
}
});
這里指定背壓策略是BackpressureStrategy.ERROR,這種策略下執行此段代碼會報如下錯誤。
D/rxjava2: 接收錯誤——io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests
因為,上下游是同步的。上游發射了事件但是下游沒有接收,就會造成阻塞(即便上游的事件隊列長度只有3個 < 128)。為了避免ANR,就要提示MissingBackpressureException異常。
如果恢復第12、13行處理線程切換的代碼,表示上下游位于不同線程,是異步狀態。此種情形下,上游發射數據后就不會報MissingBackpressureException異常,但雖然上游能正常發射數據,下游同樣接收不到數據。
這里涉及到一個知識點:
Flowable默認事件隊列大小為128。BackpressureStrategy.BUFFER策略下事件隊列無限大,和沒有采取背壓的Observable ( 被觀察者 ) / Observer ( 觀察者 )類似了。
注:在處理同一組數據時,Observable ( 被觀察者 ) / Observer ( 觀察者 )比BackpressureStrategy.BUFFER策略下的Flowable (被觀察者)/ Subscriber (觀察者)性能更優,內存消耗更少。
在上下游異步的情況下,上游會先把事件發送到長度為128的事件隊列中,待下游發送請求數據指令后從事件隊列中拉取數據。這種“響應式拉取”的思想用于解決上下游流速不均衡的情況。
上述代碼中,第19、24行代碼是表示下游接收前、接收后發送請求配額指令給上游。也可以通過subscription.request(n);在外圍調用發送n個請求配額給上游以獲取數據。
API自帶的被觀察者的背壓策略
API內的其他被觀察者,API也為我們提供了背壓策略方法:
- onBackpressureBuffer()
- onBackpressureDrop()
- onBackpressureLatest()
示例代碼如下:
Flowable.interval(1, TimeUnit.MICROSECONDS)
.onBackpressureDrop() //加上背壓策略
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Long>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
subscription = s;
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Long aLong) {
Log.d(TAG, "onNext: " + aLong);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
如果不加背壓策略,則會報錯:
D/rxjava2: onSubscribe
W/rxjava2: onError:
io.reactivex.exceptions.MissingBackpressureException: Can't deliver value 128 due to lack of requests
at io.reactivex.internal.operators.flowable.FlowableInterval$IntervalSubscriber.run(FlowableInterval.java:87)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:278)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:273)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
響應式編程
當上下游在同一個線程中的時候,在下游調用request(n)就會直接改變上游中的requested的值,多次調用便會疊加這個值,而上游每發送一個事件之后便會去減少這個值,當這個值減少至0的時候,上游若繼續發送事件便會拋異常了。
案例情景一:同步環境下,在下游發出10個請求配額情況下,上游發射130個事件。
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
for (int i = 0; i < 130; i++) {
e.onNext(i + 1);
Log.d(TAG, "requested—left—" + e.requested());
}
// e.onComplete();
}
}, BackpressureStrategy.ERROR)
//下面兩行代碼執行線程切換,達到異步效果
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
subscription = s;
subscription.request(10);
}
@Override
public void onNext(Integer s) {
Log.d(TAG, "接收 ——" + s);
}
@Override
public void onError(Throwable t) {
Log.d(TAG, "接收錯誤——" + t);
}
@Override
public void onComplete() {
}
});
打印LOG日志:
D/rxjava2: 接收 ——1
D/rxjava2: requested—left—9
D/rxjava2: 接收 ——2
D/rxjava2: requested—left—8
D/rxjava2: 接收 ——3
D/rxjava2: requested—left—7
D/rxjava2: 接收 ——4
D/rxjava2: requested—left—6
D/rxjava2: 接收 ——5
D/rxjava2: requested—left—5
D/rxjava2: 接收 ——6
D/rxjava2: requested—left—4
D/rxjava2: 接收 ——7
D/rxjava2: requested—left—3
D/rxjava2: 接收 ——8
D/rxjava2: requested—left—2
D/rxjava2: 接收 ——9
D/rxjava2: requested—left—1
D/rxjava2: 接收 ——10
D/rxjava2: requested—left—0
D/rxjava2: 接收錯誤——io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests
D/rxjava2: requested—left—0
下游不再發送請求配額時,上游的配額令牌就為0。此時上游還有事件強行發個的話,就會出現異常。這里可以驗證上面說的結論。
案例情景二:異步環境,在下游發出10個請求配額情況下,上游發射128個事件。
只是多了切換線程的代碼:
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
打印LOG日志:
D/rxjava2: requested—left—127
D/rxjava2: requested—left—126
D/rxjava2: requested—left—125
...
D/rxjava2: requested—left—2
D/rxjava2: requested—left—1
D/rxjava2: requested—left—0
D/rxjava2: 接收 ——1
D/rxjava2: 接收 ——2
D/rxjava2: 接收 ——3
D/rxjava2: 接收 ——4
D/rxjava2: 接收 ——5
D/rxjava2: 接收 ——6
D/rxjava2: 接收 ——7
D/rxjava2: 接收 ——8
D/rxjava2: 接收 ——9
D/rxjava2: 接收 ——10
異步的情況下,上游先把事件序列內的事件發射完畢,下游才開始接收。如果上游的時間序列超過默認的128個,則上游事件發射到第129個就會報MissingBackpressureException異常,下游就接收不到事件了。這就涉及到下一個知識點了。
當上下游工作在不同的線程里時,每一個線程里都有一個requested,而我們下游調用request(1000)時,實際上改變的是下游線程中的requested,而上游中的requested的值是由RxJava內部調用request(n)去設置的,這個調用會在合適的時候自動觸發。
何時自動觸發呢?我們一起看下:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
Log.d(TAG, "e.requested() == "+e.requested());
for (int i = 0; ; i++) {
boolean flag = false;
//這里做了一個循環,使發射循環處于保活狀態,并在適當時機繼續發射事件
while (e.requested() == 0){
if (!flag){
Log.d(TAG, "e.requested() == "+e.requested());
flag = true;
}
}
e.onNext(i + 1);
Log.d(TAG, "已發送" + (i + 1) + "個——剩下" + e.requested());
}
}
}, BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
subscription = s;
}
@Override
public void onNext(Integer s) {
Log.d(TAG, "接收 ——" + s);
}
@Override
public void onError(Throwable t) {
Log.d(TAG, "接收錯誤——" + t);
}
@Override
public void onComplete() {
}
});
外部手動調用request向上游請求發射配額
if (subscription != null) {
subscription.request(96);
Log.d(TAG,"下游請求了96個");
}
打印Log日志:
D/rxjava2: e.requested() == 128
D/rxjava2: 已發送1個——剩下127
D/rxjava2: 已發送2個——剩下126
...
D/rxjava2: 已發送127個——剩下1
D/rxjava2: 已發送128個——剩下0
D/rxjava2: e.requested() == 0
D/rxjava2: 下游請求了96個
D/rxjava2: 接收 ——1
D/rxjava2: 接收 ——2
...
D/rxjava2: 接收 ——95
D/rxjava2: 接收 ——96
D/rxjava2: 已發送129個——剩下95
...
D/rxjava2: 已發送223個——剩下1
D/rxjava2: 已發送224個——剩下0
D/rxjava2: e.requested() == 0
1,2,3,4行表示啟動之后,上游自動想上游的事件序列緩存區發射128個事件。
8,9...13行表示下游手動請求96個發射配額時接收的事件。此時會自動觸發上游繼續發送事件,如14,15...17,18行,上游會自動再次發射96個事件(95-0+1=96)。
如果你下游請求95個發射配額的話,上游不會自動觸發事件發射的(這個應該是底層設置的觸發閥吧)。
因此得出結論:當下游每消費96個事件便會自動觸發內部的request()去設置上游的requested的值。
在某一些場景下,可以在發送事件前先判斷當前的requested的值是否大于0,若等于0則說明下游處理不過來了,則需要等待,
注意:是在onNext事件里,onComplete和onError事件不會消耗requested。
本章完~