Rxjava 過程分析三之 subscribeOn
說明
- 只分析 Rxjava 線程切換的大致過程和思想。
- 以弄明白流程爲主, 線程切換就是切換到其他線程中去運行, 我們知道 Rxjava 提供了 newThread, io密集型的, cpu密集型的等方式. 我們就拿看名字最得勁的分析下。 那就是算 newThead。
- 這篇只介紹 subscribeOn, 至於 observeOn 我們再下一篇再次介紹。
基本使用
Flowable.create(new FlowableOnSubscribe<String>() {
@Override
public void subscribe(FlowableEmitter<String> emitter) throws Exception {
// emitter.onNext("");
// emitter.onError();
// emitter.onComplete();
}
}, BackpressureStrategy.LATEST)
.subscribeOn(Schedulers.newThread())
.subscribe(new FlowableSubscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
}
@Override
public void onNext(String s) {
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
我們可以看到需要切換線程只需要加上一行代碼, 一般對應的就是異步操作耗時操作。 使用十分的簡單, 那麼它是怎麼做到的呢, 我們往下分析。
引發的思考
- 我們多寫幾遍 subscribeOn 去切換線程可以嗎? 有必要嗎?
- 它是再什麼時候去切換線程?
- 它切換出來的線程是那些代碼段再運行? eg: 上游再新線程還是下游在呢? 爲啥了?
- 我們再不使用 Rxjava 時, 如果其他線程需要運行到當前一般都時需要一個接口回調出來呢。 Rxjava 可以跟同步一樣去拿到結果, 它是怎麼解決這個問題的呢?
源碼分析
前一堆和後一堆我們就不分析了, 如果不懂的可以看我之前的文章。 我們就直接拿 subscribeOn 開涮。
public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler) {
return subscribeOn(scheduler, !(this instanceof FlowableCreate));
}
調用了 subscribeOn 的兩個參數的方法。
public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler, boolean requestOn) {
return new FlowableSubscribeOn<T>(this, scheduler, requestOn);
}
好簡單對吧, 跟以前一樣的套路, 新建的一個處理 SubscribeOn 的 Flowable 而已, 並對成員變量賦值了。 我們從以前的幾篇 Rxjava 的講解中也得知要給套路, 一旦訂閱了, 就會執行相應 Flowable 中的 subscribeActual, 所以我們就看看 FlowableSubscribeOn 中的做了啥事呢。
public void subscribeActual(final Subscriber<? super T> s) {
Scheduler.Worker w = scheduler.createWorker();
final SubscribeOnSubscriber<T> sos = new SubscribeOnSubscriber<T>(s, w, source, nonScheduledRequests);
s.onSubscribe(sos);
w.schedule(sos);
}
第一行
來吧, 我們一行一行的分析。 Worker 是啥? 我們跟進去看看 createWorker 代碼。
public abstract Worker createWorker();
怎麼破。 還記得這個接口是那個去實現的嗎? 對, 是 Scheduler.newThead。 那麼我定位到 NewThreadScheduler 裏。 其實再 Android Studio 中行數旁邊點擊向下的箭頭就可以看看實現了 Scheduler 的類了。 我們會很方便的切換到想看的具體實現的類中。 ok, 我們看看 NewThreadScheduler 做了啥。
public Worker createWorker() {
return new NewThreadWorker(threadFactory);
}
不說了,快點點進去 NewThreadWorker 是啥子呢。
public NewThreadWorker(ThreadFactory threadFactory) {
executor = SchedulerPoolFactory.create(threadFactory);
}
好嘛, 看到了嗎! 是一個封裝要給線程池嘛。 好了到這裏我們知道了 createWorker 是做啥了把, 不同的 worker 工作者內部實現可能不一樣, 也就是選擇不同工作者, 其中處理肯定不一樣呀, 就比如現在的 newThead 和 io 等肯定是不一樣的呀。 不過把抽象後, 是不是寫法很好。 是不是又學到了一個編程思想呢。 還不拿筆記下快點!
第二行
好了停下來不扯了, 看看第二行做了什麼吧。 僅僅是創建了一個對象, 相應的成員變量賦值。
static final class SubscribeOnSubscriber<T> extends AtomicReference<Thread>
implements FlowableSubscriber<T>, Subscription, Runnable {}
我們需要關注下它是實現了 Runnable 接口哈, 還有還有實現了 FlowableSubscriber 接口, 你知道我想表達什麼嗎? 它是有 onNext 等等那些方法呢! 難道它是中間的要給代理接口回調? 也就是我們之前說的, 從分線程吧結果回調出來? 先猜着吧。
第三行
我們繼續往下看第三行, 一行不說了, 至少在現在我們簡易的裏面就是調用了下游的方法, 處理背壓等問題, 我們不分析哈。
第四行
我們繼續往下看最後一行吧。 嗯? 這個是啥? 距我們之上的分析 worker 在 newThead 裏是建了一個線程池, 傳入的類又是 Runnable, 我們猜測是不是直接把 Runnable 扔進線程池, 是不是直接去執行了呀? 我們去驗證下。 點擊去直接跟着點到 NewThreadWorker 裏吧。 最後調用到了如下
public ScheduledRunnable scheduleActual(final Runnable run, long delayTime, @NonNull TimeUnit unit, @Nullable DisposableContainer parent) {
Runnable decoratedRun = run;
ScheduledRunnable sr = new ScheduledRunnable(decoratedRun, parent);
Future<?> f;
try {
if (delayTime <= 0) {
f = executor.submit((Callable<Object>)sr);
} else {
f = executor.schedule((Callable<Object>)sr, delayTime, unit);
}
sr.setFuture(f);
} catch (RejectedExecutionException ex) {
}
return sr;
}
我們看到關鍵的一步是
if (delayTime <= 0) {
f = executor.submit((Callable<Object>)sr);
} else {
f = executor.schedule((Callable<Object>)sr, delayTime, unit);
}
好吧不管有沒有延遲吧, 是直接塞到了線程池裏執行了呢。 這裏可能發現了又 new 出來要給 Runnable, 又包了一層, 幹啥玩意呢? 包裝了下, 實現了一些特定的處理。 什麼? 啥處理, 我不分析了。 我們只關注主流程啦。
到這裏你打通一道路沒? 就是一旦發生訂閱, 就會在指定線程中運行 第二行 SubscribeOnSubscriber 中的 run 方法。 我們看看裏面實現了啥。
public void run() {
lazySet(Thread.currentThread());
Publisher<T> src = source;
source = null;
src.subscribe(this);
}
你一定要有一個意識就是, 在 run 裏的方法都是在指定線程中運行的哈! 比如分線程。 其中 source 就是上一層的 Flowable, 在 run 方法中發生了訂閱, 也就是在傳遞訂閱和上面的執行在這一級看來都是 run 裏去執行的。
上層調用了上游 onNext 等方法, 就會調用到該類 SubscribeOnSubscriber 中相應的方法。 注意不管怎麼調用都是在這個線程中執行的。 對吧, 你想想。 那麼讓我們看看 onNext 做了什麼。
public void onNext(T t) {
downstream.onNext(t);
}
沒想到吧, 就是這麼簡單, 把結果直接流給了下游。 那到這裏我想問問你在這裏的 onNext 及往下流是在哪個線程呢?
前面的疑惑問題
- 我們多寫幾遍 subscribeOn 去切換線程可以嗎? 有必要嗎?
這個嘛, 從我們上面的分析可以看出,如果調用了多次, 在每次向上訂閱的時候就會在新的指定的線程中。 至於有必要嗎? 看你怎麼看了, 我用眼看! 哈哈, 其實在實際開發中, 我們在寫上切換線程那語句上面的及訂閱後流下來的都是在當前線程中, 我們一般都在分線程去處理數據, 你切多次有啥用或者有用, 看自己了哈。
- 它是再什麼時候去切換線程?
在上面的分析可知, 在發生訂閱時, 運行了放到線程池中的 run, 又在 run 裏發生了訂閱。
- 它切換出來的線程是那些代碼段再運行? eg: 上游再新線程還是下游在呢? 爲啥了?
通過上面的分析及上面第一個問題中其實也又回答了, 在 run 裏發生訂閱, 訂閱後的事情都是在線程中運行的
- 我們再不使用 Rxjava 時, 如果其他線程需要運行到當前一般都時需要一個接口回調出來呢。 Rxjava 可以跟同步一樣去拿到結果, 它是怎麼解決這個問題的呢?
是的, 其實可以把 SubscribeOnSubscriber 當成一箇中間接口回調。 當然看個人理解了。