在 Android 中使用 RxJava 經常會用到 observeOn
這個操作符來完成線程的切換,比如網絡請求之后切換到“主線程”,通常會這么寫:
Observable.observeOn(AndroidSchedulers.mainThread())
然而我遇到一個奇怪的問題,onNext
有時候會丟失,先描述一下場景。我用 Retrofit + RxJava 來進行網絡請求,我可以直接拿到一個 Observable
,就像下面這樣:
// 定義 Retrofit 接口
public interface FeedApi {
@GET("feeds")
Observable<List<Feed>> feeds();
}
// 省略部分代碼
RestApi restApi = retrofit.create(RestApi.class);
Observable<List<Feed>> observable = restApi.feeds()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
observable.subscribe(...);
目前來看一切正常,我為了更好的用戶體驗,加入了緩存,當網絡有問題的時候,會顯示緩存數據,用戶不會看到一個空白的頁面。緩存的方式多種多樣,比如 Realm
,同樣支持 RxJava,我依然可以拿到一個 Observable
,然后我使用 concat
把來自緩存的 Observable
和來自網絡的 Observable
合并為一個 Observable
對外提供,就像下面這樣:
Observable<List<Feed>> fromNetwork = ...
Observable<List<Feed>> fromCache = ...
Observable<List<Feed>> observable = Observable.concat(fromCache, fromNetwork)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
observable.subscribe(...);
合并之后的 Observable
繼續使用 subscribeOn
和 observeOn
來完成線程的切換。恩,看起來很完美,我想象中運行效果是這樣的:
假設已經存在緩存的情況下
網絡正常的時候,fromCache
從緩存拿到數據發送給Subscriber
,顯示到頁面上,然后fromNetwork
從服務器拿到最新的數據,繼續發送給Subscriber
,刷新頁面為最新的數據,Subscribe
會經歷onNext()
->onNext()
->onCompleted()
。
網絡異常的情況,fromCache
從緩存拿到數據發送給Subscriber
,顯示到頁面上,然后fromNetwork
發生異常,Subscriber
收到Error
,可以提示用戶網絡異常,但是頁面上會顯示緩存的數據,用戶不會看到空白頁面,Subscriber
會經歷onNext()
->onError()
。
我運行之后,在網絡正常的情況下,能顯示數據,當我把網絡關閉的時候,依然能顯示數據,數據是從緩存加載的,還不錯,和我想象的一樣。但是當我反復多次打開頁面的時候,發現一個奇怪的現象,頁面有時候會顯示空白,有時候會顯示緩數據,但是都會提示網絡異常的信息,從 Subscriber
的角度講,就是 Subscriber
有時候會 onNext()
-> onError()
,有時候只會 onError()
,緩存數據的那次 onNext()
丟失了!
為了搞清楚這個問題,我做了這樣的測試,在 subscribe
之前各個環節加上 doOnNext
,觀察會不會執行。
observable
.doOnNext(s -> log("1 doOnNext: " + s))
.subscribeOn(Schedulers.io())
.doOnNext(s -> log("2 doOnNext: " + s))
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(s -> log("3 doOnNext: " + s))
.subscribe(...);
// 輸出結果
1 doOnNext: test
2 doOnNext: test
第三個居然沒有打印,說明 observeOn
之后的 onNext()
沒有執行,確定了問題范圍之后,我嘗試從 observeOn
的源碼尋找線索。我發現 observeOn
有幾個重載的方法,其中有一個參數叫 delayError
,看名字是“延遲錯誤”。
public final Observable<T> observeOn(Scheduler scheduler, boolean delayError);
注釋對這個參數的描述是:
indicates if the onError notification may not cut ahead of onNext notification on the other side of the scheduling boundary. If true a sequence ending in onError will be replayed in the same order as was received from upstream
大概說 delayError
決定了 onError
會不會切斷 onNext
通知,貌似和我的問題有些關系,為了弄清楚具體情況,還是看源碼。
observeOn
基于 lift
操作符,lift
需要一個 Operator
,Operator
可以將 下游 Subscriber
進行“代理”,返回一個 “代理” Subscriber
對象,“代理”對象可以收到 onNext
、onError
、onCompleted
,然后可以進行一些處理,再選擇性的發送給 下游 Subscriber
。
public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
if (this instanceof ScalarSynchronousObservable) {
return ((ScalarSynchronousObservable<T>)this).scalarScheduleOn(scheduler);
}
// OperatorObserveOn 是關鍵
return lift(new OperatorObserveOn<T>(scheduler, delayError, bufferSize));
}
我們繼續看 OperatorObserveOn
。
public final class OperatorObserveOn<T> implements Operator<T, T> {
...
@Override
public Subscriber<? super T> call(Subscriber<? super T> child) {
// 創建一個 Subscriber “代理”
ObserveOnSubscriber<T> parent = new ObserveOnSubscriber<T>(scheduler, child, delayError, bufferSize);
parent.init();
return parent;
}
// Subscriber 的代理,它會代理 下游的 Subscriber 收到事件
// 然后在 Scheduler 的線程中再轉發給 下游的 Subscriber,就完成了線程的切換
static final class ObserveOnSubscriber<T> extends Subscriber<T> implements Action0 {
@Override
public void onNext(final T t) {
// 代理對象先收到 onNext
if (isUnsubscribed() || finished) {
return;
}
// 把收到的數據放到一個隊列中
if (!queue.offer(NotificationLite.next(t))) {
onError(new MissingBackpressureException());
return;
}
// 通知 Scheduler,Scheduler 會回調 call 方法
schedule();
}
@Override
public void onCompleted() {
if (isUnsubscribed() || finished) {
return;
}
// 標記 流 已經結束
finished = true;
// 通知 Scheduler,Scheduler 會回調 call 方法
schedule();
}
@Override
public void onError(final Throwable e) {
if (isUnsubscribed() || finished) {
RxJavaHooks.onError(e);
return;
}
// 保存 error
error = e;
// 標記 流 已經結束
finished = true;
// 通知 Scheduler,Scheduler 會回調 call 方法
schedule();
}
protected void schedule() {
if (counter.getAndIncrement() == 0) {
// 把 this 當做一個 callback,Scheduler 會調用 call 方法。
recursiveScheduler.schedule(this);
}
}
// only execute this from schedule()
@Override
public void call() {
// 從 Scheduler 中調用,此時已經在 Scheduler 的線程中,比如 Android 的主線程中
long missed = 1L;
long currentEmission = emitted;
final Queue<Object> q = this.queue; // 保存 next 數據的隊列
final Subscriber<? super T> localChild = this.child; // 下游的 Subscriber
for (;;) {
long requestAmount = requested.get();
while (requestAmount != currentEmission) {
boolean done = finished;
// 從隊列中取數據,就是 onNext 的時候放到隊列中的
Object v = q.poll();
// 判斷是不是 null
boolean empty = v == null;
// 這里是關鍵,檢查是否需要中斷 onNext
if (checkTerminated(done, empty, localChild, q)) {
// 中斷,最終的 Subscriber 不會收到 onNext
return;
}
if (empty) {
break;
}
// 轉發給 下游的 Subscriber
localChild.onNext(NotificationLite.<T>getValue(v));
...
}
}
}
// 檢查是否需要中斷 onNext
boolean checkTerminated(boolean done, boolean isEmpty, Subscriber<? super T> a, Queue<Object> q) {
if (a.isUnsubscribed()) {
q.clear();
return true;
}
if (done) {
// 如果 流 已經結束,執行了 onCompleted 或者 onError
if (delayError) {
// 如果是延遲錯誤,delayError 默認是 false
if (isEmpty) {
// 如果沒有 onNext 的數據了
Throwable e = error;
try {
if (e != null) {
// 如果存在 error,直接轉發給 下游的 Subscriber
a.onError(e);
} else {
// 如果是正常結束,也是直接轉發給 下游的 Subscriber
a.onCompleted();
}
} finally {
recursiveScheduler.unsubscribe();
}
}
} else {
// 如果沒有延遲錯誤,這也是默認的情況
Throwable e = error;
if (e != null) {
// **** 如果已經有 error 了,把隊列里的 onNext 情況,也就是 onNext 的數據丟失了!
q.clear();
try {
// **** 把 error 發送給 下游的 Subscriber
a.onError(e);
} finally {
recursiveScheduler.unsubscribe();
}
return true;
} else
if (isEmpty) {
// 如果沒有 error,并且沒有 onNext 的數據了,直接完成
try {
a.onCompleted();
} finally {
recursiveScheduler.unsubscribe();
}
return true;
}
}
}
return false;
}
}
}
通過上面對源碼的分析,可以確定這個問題出現的原因了,上面注釋中 ****
標記的地方就是問題所在:
if (e != null) {
// **** 如果已經有 error 了,把隊列里的 onNext 情況,也就是 onNext 的數據丟失了!
q.clear();
try {
// **** 把 error 發送給 下游的 Subscriber
a.onError(e);
} finally {
recursiveScheduler.unsubscribe();
}
return true;
}
默認 delayError
是 false
,就會走上面這段代碼,如果有 error
就會把隊列中還未下發給 下游 Subscriber
的 onNext
數據清空,從而導致了 Subscriber
沒有收到 onNext
,直接收到了 onError
。
為什么這個問題不是必現的呢,因為這有一個條件,就是出現 onError
的時候,onNext
的數據還沒有被消費完,隊列中還有數據的情況下才會丟失,也就是 Scheduler
的處理速度跟不上生產速度,這很好理解,因為我們用的是 AndroidSchedulers.mainThread()
,Android 的主線程也就是 Main Lopper
,是一個單線程模型,只能一個個的處理,不能并行,而且 Android 中的各種事件,包括 View 的繪制都要通過 Main Looper
來完成,如果 Looper
中積壓的消息太多,那么新消息就不會被及時的處理,這時候我們的 fromCache
把緩存數據發送給 Subscriber
,就會由于 Looper
中積壓的消息過多,Scheduler
不能立刻執行 ObserveOnSubscriber
的 call
方法,從而不能立刻被 下游的 Subscriber
接收到,與此同時 fromNetowrk
發生了 error
,當 Looper
處理到消息的時候,也就是 ObserveOnSubscriber
的 call
方法被調用的時候,這時候隊列中存在一個 fromCache
的數據,并且 error
也存在,就發生了上面源碼分析的情況,導致隊列被清空,onNext
沒有執行,直接執行了 onError
。
怎么解決這個問題?答案已經明確了,就是顯式的傳遞 delayError
為 true
。
Observable.observeOn(AndroidSchedulers.mainThread(), true)
這個問題之前有人在 GitHub 上提過 Issue,并且在這個 Pull 3544 中解決,也就是增加了 delayError
,在此之前是沒有這個參數的。
明白了原因之后,這個問題其實和 concat
沒有什么關系,不用 concat
依然可能出現 onNext
丟失,由于 Main Looper
我們不好去控制,可以通過一個簡單的 Java 程序來模擬這種情況。
public static void main(String[] args) {
// 用一個 單線程的線程池 來模擬 Android 的主線程
ExecutorService executor = Executors.newSingleThreadExecutor();
Scheduler androidMainThread = Schedulers.from(executor);
Observable<String> observable = Observable.create(subscriber -> {
// 發送數據
subscriber.onNext("text");
// 一秒后發送 error
sleep(1000);
subscriber.onError(new RuntimeException());
});
// 完成線程切換,然后訂閱
observable
.subscribeOn(Schedulers.io())
.observeOn(androidMainThread)
.subscribe(s -> {
// 收到 onNext 數據
System.out.println("onNext: " + s);
}, throwable -> {
// 收到 onError
System.out.println("onError: " + throwable);
});
}
static void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
輸出結果:
onNext: text
onError: java.lang.RuntimeException
輸出結果是正常的,既收到了 onNext
又收到了 onError
,因為 androidMainThread
中沒有其他的任務要處理,Observable
發送 onNext
之后會 sleep
一秒鐘之后發送 onError
,androidMainThread
在這一秒鐘之內完全可以把 onNext
的數據消費掉,下面模擬 androidMainThread
中有任務處理的情況。
// 主線程有其他任務處理,會阻塞 兩秒
executor.execute(() -> sleep(2000));
// 完成線程切換,然后訂閱
observable
.subscribeOn(Schedulers.io())
.observeOn(androidMainThread)
.subscribe(s -> {
// 收到 onNext 數據
System.out.println("onNext: " + s);
}, throwable -> {
// 收到 onError
System.out.println("onError: " + throwable);
});
輸出結果:
onError: java.lang.RuntimeException
會發現只有 onError
,onNext
丟失了,因為 Observable
只是 sleep
了一秒就發送了 onError
,而 androidMainThread
的 線程 正在執行一個需要耗時兩秒的任務,執行完之后,ObserveOnSubscriber
中的隊列存在一個 onNext
數據和一個 error
,這就會發生之前分析的情況,隊列被清空,直接發送了 onError
。我們再試試 顯式 傳遞 delayError
的情況。
// 主線程有其他任務處理,會阻塞 兩秒
executor.execute(() -> sleep(2000));
// 完成線程切換,然后訂閱
observable
.subscribeOn(Schedulers.io())
.observeOn(androidMainThread, true) // delayError 為 true
.subscribe(s -> {
// 收到 onNext 數據
System.out.println("onNext: " + s);
}, throwable -> {
// 收到 onError
System.out.println("onError: " + throwable);
});
輸出結果:
onNext: text
onError: java.lang.RuntimeException
會發現 onNext
收到了,這也證明了上面源碼分析的邏輯是正確的。