在項目的下載庫部分使用了PublishProcessor來實現支持有背壓的Observable,向外提供數據變化狀態,同時還對外提供了Single從數據庫裏面去獲取數據。在下載界面我們會首先去subscribe PublishProcessor以監聽數據變化,然後去拉取數據,填充到我們的界面裏。流程大致如下:
TD.downloader.observeTaskChange()
.filter { it.change == TaskChange.TASK_ADD }
.observeOn(AndroidSchedulers.mainThread())
.`as`(RXUtils.autoDispose(lifecycleOwner))
.subscribe { events ->
...
}
...
TD.downloader.queryAll()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { tasks ->
...
}
在下載庫裏,我們的query是先與這個PublishProcessor.onNext()運行的,但是在實際運行中我發現在少部分低端手機上面總是先執行了observeTaskChange裏面的代碼,然後才執行queryAll()裏面的代碼。通過打印日誌,我能很肯定代碼運行的流程沒問題,而如果我單步去調試,則總是先執行queryAll(),然後再執行observeTaskChange()。一時懷疑甚至懷疑這個Android系統的消息隊列有問題,但是想想如果是消息隊列序列有問題,那整個系統就完了。回頭想想那麼問題出在哪裏呢??既然問題出在主線程分發,那如果能夠在rxandroid打印一下postMessage的線程,就可以確定消息隊列的序列了。爲了驗證這個問題,我把rxandroid的項目下載了下來,然後把項目中的rxandroid庫換爲了本地的,然後在HandlerScheduler類的schedule方法裏sendMessageDelayed前加上了日誌,打印了線程ID。打印Log後,我驚呆了,居然PublishProcessor.onNext()在有Bug的情況下根本就不會觸發給Android的主線程發消息。於是我們可以猜測,應該是Flowable的其他地方已經把線程起起來了,然後通過Queue去把數據發送到了下游。通過Debug,然後發現FlowableObserveOn裏面的trySchedule會在Subscription request的時候去調度線程,而這個request是在subscribe()的時候就會調用,這裏就會去給主線程sendMessage了,如果手機配置不高,主線程執行速度慢,這個消息可能會有一定延遲纔會收到。而如果這個時候我們調用了PublishProcessor.onNext(),就不會去給主線程發送新的消息,因爲FlowableObserveOn裏面正在處理,所以直接排隊即可。這也就解釋了爲什麼在同一個線程裏面先返回了queryAll,後去PublishProcessor.onNext(),會先執行PublishProcessor的消費者代碼,後執行queryAll的消費者代碼。我這裏用Java寫了一個簡單的Demo驗證這個結論:
以下代碼的single()線程可以認爲是我上面說的Android主線程
public static void main(String[] args) {
PublishProcessor<Integer> publishProcessor = PublishProcessor.create();
Observable<Integer> observable = Observable.fromCallable(() -> 1)
.observeOn(Schedulers.single());
Flowable flowable = publishProcessor.hide().observeOn(Schedulers.single());
Completable.fromCallable((Callable<Integer>) () -> {
Thread.sleep(1000);
return 1;
}).subscribeOn(Schedulers.single())
.subscribe();
flowable.subscribe(res -> System.out.println(res));
observable.subscribe(res -> System.out.println(res));
publishProcessor.onNext(2);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
輸出結果是:
2
1
證明了以上結論。
如何解決這個問題
這個問題的核心在於:
核心問題是熱Flowable在subscribe的時候就會觸發一次線程切換調度,不管有沒有發射數據。我們儘量是理清楚是否真的需要處理背壓,選擇合適的操作符,我這裏其實是不需要處理背壓的,所以直接加一句toObservable()即可完美解決這個問題。此外我們注意Flowable的subscribe和其他操作符的subscribe調用順序,也能完美解決這個問題。