概述
在 RxJava 中可以通過 subscribeOn/observeOn 很方便地完成上下游指定線程的切換,日常開發(fā)除了一些常用的Rx 操作符外,這兩個方法也是打交道最多的。最初學習 RxJava 的時候總是死記硬背:subscribeOn 用于指定上游線程,observeOn 用于指定下游線程,多次用 subscribeOn 指定上游線程只有第一次有效,多次用 observeOn 指定下次線程,每次都有效…很久不用之后,總是把這兩個方法搞混,那么這兩個方法內部是怎么實現(xiàn)的呢?本篇先分析subscribeOn 方法。
例子
先回顧上篇文章的流程,飯店(Observable)開張前提要有廚師(ObservableOnSubscribe),接著改名叫沙縣小吃(ObservableCreate),飯店接客(Observable.subscribe(observer)),創(chuàng)建服務員(CreateEmitter)把顧客和廚師關聯(lián)起來,之后廚師每做一道菜都通過服務員端給顧客,整個流程如下:
我們都知道 Andriod 有主線程,在未指定線程切換操作的情況下,上圖的流程是跑在主線程中,另外主線程中往往還存在其他任務需要執(zhí)行,所以結合線程來看應該是這樣的
上圖給人一種感覺,好像廚師的菜是「秒做」出來的,然而我們都知道現(xiàn)實生活中廚師做菜是需要時間的,在安卓中,主線程執(zhí)行耗時操作會阻塞后續(xù)的任務,還有可能引起 ANR,所以廚師做菜的操作不能放在主線程中 。下面讓上游睡5秒模擬耗時操作
上游:
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 744px;">
final Observable<String> source = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
Thread.sleep(5000);
Log.e(TAG, "服務員從廚師那取得 扁食" + " 線程名: "+Thread.currentThread().getName());
e.onNext("扁食");
Log.e(TAG, "服務員從廚師那取得 拌面" + " 線程名: " + Thread.currentThread().getName());
e.onNext("拌面");
Log.e(TAG, "服務員從廚師那取得 蒸餃" + " 線程名: " + Thread.currentThread().getName());
e.onNext("蒸餃");
Log.e(TAG, "廚師告知服務員菜上好了" + " 線程名: " + Thread.currentThread().getName());
e.onComplete();
}
});
</pre>
|
</figure>
下游:
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 737px;">
Observer<String> observer = new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "來個沙縣套餐!!!" + " 線程名: " + Thread.currentThread().getName());
}
@Override
public void onNext(String s) {
Log.d(TAG, "服務員端給顧客 " + s + " 線程名: " + Thread.currentThread().getName());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
Log.d(TAG, "服務員告訴顧客菜上好了" + " 線程名: " + Thread.currentThread().getName());
}
};
</pre>
|
</figure>
建立聯(lián)系,以及執(zhí)行其他任務(這里只是打了個 log )
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 213px;">
source.subscribe(observer);
Log.d(TAG, "其他任務執(zhí)行");
</pre>
|
</figure>
打印如下:
可以看到,由于上游耗時,導致主線程中「其他任務」被阻塞了,因此需要新建一個子線程來處理上游的耗時任務,使用 RxJava 的 subscribeOn 就能輕松實現(xiàn),修改代碼:
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 321px;">
source.subscribeOn(Schedulers.newThread())
.subscribe(observer);
Log.e(TAG, "其他任務執(zhí)行");
</pre>
|
</figure>
打印如下:
此時「其他任務」不會被阻塞。從上面的 log 可以看到,創(chuàng)建了 RxNewThreadScheduler-1 的子線程來執(zhí)行上游的耗時任務,并且此時下游除 onSubscribe 外,所有方法都執(zhí)行在子線程中,它是怎么做到的?(通常情況下游會調用 observeOn(AndroidSchedulers.mainThread()) 來更新UI,下篇分析)。
源碼分析
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 321px;">
source.subscribeOn(Schedulers.newThread())
.subscribe(observer);
</pre>
|
</figure>
上面的代碼簡短優(yōu)雅,其實做了很多事情。基于上篇的分析,在執(zhí)行完 Observable.create 和 new Observer 后此時主線程應該是下面的樣子
Schedulers.newThread()
Scheduler 翻譯為調度器,RxJava2 中 Scheduler 的一些常用子類如下:
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 478px;">
static final class SingleHolder {
static final Scheduler DEFAULT = new SingleScheduler();
}
static final class ComputationHolder {
static final Scheduler DEFAULT = new ComputationScheduler();
}
static final class IoHolder {
static final Scheduler DEFAULT = new IoScheduler();
}
static final class NewThreadHolder {
static final Scheduler DEFAULT = new NewThreadScheduler();
}
</pre>
|
</figure>
Schedulers.newThread() 會初始化 NewThreadScheduler ;
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 692px;">
public final class NewThreadScheduler extends Scheduler {
final ThreadFactory threadFactory;
//看著很眼熟,原來我們上游的線程名稱的一部分就是這么起的"RxNewThreadScheduler-1"
private static final String THREAD_NAME_PREFIX = "RxNewThreadScheduler";
//線程工廠
private static final RxThreadFactory THREAD_FACTORY;
/** The name of the system property for setting the thread priority for this Scheduler. */
//用來設置線程優(yōu)先級的key
private static final String KEY_NEWTHREAD_PRIORITY = "rx2.newthread-priority";
//靜態(tài)代碼塊
static {
//確定線程的優(yōu)先級,這里初始化為5 NORM_PRIORITY
int priority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY,
Integer.getInteger(KEY_NEWTHREAD_PRIORITY, Thread.NORM_PRIORITY)));
//初始化線程工廠,傳入線程名稱和優(yōu)先級
THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX, priority);
}
//賦值
public NewThreadScheduler() {
this(THREAD_FACTORY);
}
//賦值
public NewThreadScheduler(ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
@NonNull
@Override
//這個方法很重要,很重要,很重要!!!后面會用到
public Worker createWorker() {
return new NewThreadWorker(threadFactory);
}
}
</pre>
|
</figure>
上面的注釋已經解釋得很清楚了,在初始化 NewThreadScheduler 的時候會創(chuàng)建 RxThreadFactory,并指明了該線程工廠之后生產線程的名稱和默認優(yōu)先級;RxThreadFactory 是 ThreadFactory 的子類,也沒多少代碼
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 707px;">
public RxThreadFactory(String prefix, int priority) {
this(prefix, priority, false);
}
public RxThreadFactory(String prefix, int priority, boolean nonBlocking) {
this.prefix = prefix;
this.priority = priority;
this.nonBlocking = nonBlocking;
}
@Override
//生產新線程的方法!!!
public Thread newThread(Runnable r) {
StringBuilder nameBuilder = new StringBuilder(prefix).append('-').append(incrementAndGet());
String name = nameBuilder.toString();
Thread t = nonBlocking ? new RxCustomThread(r, name) : new Thread(r, name);
t.setPriority(priority);
t.setDaemon(true);
return t;
}
</pre>
|
</figure>
RxThreadFactory 中的 newThread 方法用來生產新線程。Schedulers.newThread() 到此就完成了它的工作,總結下來就是:
1.創(chuàng)建線程調度器 NewThreadScheduler;
2.創(chuàng)建線程工廠 RxThreadFactory ;
到目前為止這些操作都是在主線程中執(zhí)行的,子線程還未被創(chuàng)建。
subscribeOn(Scheduler scheduler)
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 614px;">
public final Observable<T> subscribeOn(Scheduler scheduler) {
ObjectHelper.requireNonNull(scheduler, "scheduler is null");
return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
}
</pre>
|
</figure>
該方法返回 Observable ,創(chuàng)建了 ObservableSubscribeOn ,名字起得又很容易讓人頭暈…這里就不畫關系圖了,只關心它的屬性即可,它是 Observable(飯店) 的子類,結合我們舉的例子,就給它起名黃燜雞飯店;this 就是上面?zhèn)鬟^來的沙縣小吃(ObservableCreate) ;初始化如下:
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 664px;">
public final class ObservableSubscribeOn<T> extends AbstractObservableWithUpstream<T, T> {
final Scheduler scheduler;
public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
super(source);
this.scheduler = scheduler;
}
//省略其他代碼
}
</pre>
|
</figure>
目前為止這些操作都是在主線程中執(zhí)行,子線程還未創(chuàng)建
subscribe(Observer observer)
通過上篇學習可知,subscribe(observer) 內部會調用 subscribeActual(observer) ,該方法是個抽象方法,具體實現(xiàn)在 Observable(飯店) 的子類,現(xiàn)在是 ObservableSubscribeOn(黃燜雞飯店)。
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 578px;">
public void subscribeActual(final Observer<? super T> s) {
//注釋1
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(s);
//注釋2
s.onSubscribe(parent);
//注釋3
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
</pre>
|
</figure>
注釋1 又冒出來一個 SubscribeOnObserver,同樣只關心它的屬性,SubscribeOnObserver 是AtomicReference的子類(保證原子性),同時實現(xiàn)了 Observer(也是個顧客) 和 Disposable(保證一次性操作) 接口;為了方便理解,假設之前傳的顧客叫小明,這里的顧客叫小紅,小紅會持有小明的引用(actual),之后一系列的方法實際上會調用到小明的方法。
注釋2 執(zhí)行顧客小明的 onSubscribe 方法,我們發(fā)現(xiàn)到目前為止還沒有創(chuàng)建過子線程,所以解釋了上面 log 下游 onSubscribe 打印線程名為 main。
注釋3 分為下面3步
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 549px;">
步驟③ 步驟② 步驟①
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
</pre>
|
</figure>
步驟① SubscribeTask 是 ObservableSubscribeOn(黃燜雞飯店) 的內部類,實現(xiàn)了 Runnable 接口
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 454px;">
final class SubscribeTask implements Runnable {
private final SubscribeOnObserver<T> parent;
SubscribeTask(SubscribeOnObserver<T> parent) {
this.parent = parent;
}
@Override
public void run() {
//這里的 source 就是之前傳的 ObservableCreate(沙縣小吃)
source.subscribe(parent);
}
}
</pre>
|
</figure>
如果 run 方法被觸發(fā),那么執(zhí)行順序是:
Observable.subscribe() —> Observable.subscribeActual() —> ObservableCreate.subscribeActual(),繞了一圈又回到上篇的那個流程。為了方便理解,SubscribeTask 就是黃燜雞飯店(ObservableSubscribeOn)的「任務」也就是沙縣小吃的「做菜」(ObservableCreate.subscribeActual)。所以現(xiàn)在萬事具備,只差子線程了。
步驟② Scheduler.scheduleDirect()
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 578px;">
/**
Schedules the given task on this scheduler non-delayed execution.
<p>
This method is safe to be called from multiple threads but there are no
ordering guarantees between tasks.
@param run the task to execute
@return the Disposable instance that let's one cancel this particular task.
@since 2.0
*/
//這里調度的時候不保證順序
//第二個參數(shù)為0,不延時,直接調度
@NonNull
public Disposable scheduleDirect(@NonNull Runnable run) {
return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS);
}
</pre>
|
</figure>
注意這個方法的注釋,該方法調度的時候不保證順序,所以平時在配合使用 subscribeOn(子線程)/observeOn(主線程) 會出現(xiàn)上下游輸出順序不確定的情況(比如有時候上游生產了3個后才逐個發(fā)送給下游,有時上游生產了2個,就開始發(fā)送給下游),這也是多線程的一個特點。當然這里不會出現(xiàn)這個情況,因為從輸出來看,此時上下游都在一個子線程里。貌似跑遠了…繼續(xù)分析
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 685px;">
public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
final Worker w = createWorker();
final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
DisposeTask task = new DisposeTask(decoratedRun, w);
w.schedule(task, delay, unit);
return task;
}
// Scheduler 中為抽象方法
public abstract Worker createWorker();
</pre>
|
</figure>
前面創(chuàng)建 NewThreadScheduler 的時候說 createWorker() 方法很重要,這里派上用場了:
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 642px;">
//NewThreadScheduler.java
public Worker createWorker() {
return new NewThreadWorker(threadFactory);
}
//NewThreadWorker.java
public NewThreadWorker(ThreadFactory threadFactory) {
//實例化 ScheduledExecutorService 對象 executor 管理線程池
executor = SchedulerPoolFactory.create(threadFactory);
}
//SchedulerPoolFactory.java
public static ScheduledExecutorService create(ThreadFactory factory) {
//默認線程池大小為1
final ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, factory);
if (exec instanceof ScheduledThreadPoolExecutor) {
ScheduledThreadPoolExecutor e = (ScheduledThreadPoolExecutor) exec;
POOLS.put(e, exec);
}
return exec;
}
</pre>
|
</figure>
NewThreadWorker 內部維護一個線程池 ScheduledExecutorService , 主要作用是提供延時調度和周期性調度,默認線程池大小為1,線程池里的線程通過我們傳的線程工廠創(chuàng)建。
之后把 NewThreadWorker 和步驟①中的任務包裝成 DisposeTask,又是一個Runnable
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 278px;">
public void run() {
//注意這里會獲取當前所在線程
runner = Thread.currentThread();
try {
//在當前線程中執(zhí)行
decoratedRun.run();
} finally {
//執(zhí)行完后斷開
dispose();
runner = null;
}
}
</pre>
|
</figure>
最后會執(zhí)行 NewThreadWorker.schedule 方法
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 735px;">
public Disposable schedule(@NonNull final Runnable action, long delayTime, @NonNull TimeUnit unit) {
if (disposed) {
return EmptyDisposable.INSTANCE;
}
return scheduleActual(action, delayTime, unit, null);
}
public Disposable scheduleDirect(final Runnable run, long delayTime, TimeUnit unit) {
ScheduledDirectTask task = new ScheduledDirectTask(RxJavaPlugins.onSchedule(run));
try {
Future<?> f;
//不延時,直接調度
if (delayTime <= 0L) {
//此時任務執(zhí)行在子線程中
f = executor.submit(task);
} else {
f = executor.schedule(task, delayTime, unit);
}
task.setFuture(f);
return task;
} catch (RejectedExecutionException ex) {
RxJavaPlugins.onError(ex);
return EmptyDisposable.INSTANCE;
}
}
</pre>
|
</figure>
到這里終于看到任務(ObservableCreate.subscribeActual)執(zhí)行在子線程中。
步驟③ parent.setDisposable 設置可中斷。至此流程如下
之后所有的事情都是在子線程中進行的,上篇已經分析過了
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 598px;">
protected void subscribeActual(Observer<? super T> observer) {
//創(chuàng)建服務員,并和顧客聯(lián)系,這里的顧客是小紅
CreateEmitter<T> parent = new CreateEmitter<T>(observer);
//執(zhí)行顧客小紅的的 onSubscribe ,注意這里不會再回調顧客小明的onSubscribe
//因為顧客小紅的 onSubscribe 中只是將接收事件的行為設置成一次性,并沒有回調小明方法
observer.onSubscribe(parent);
try {
//廚師做菜,并和服務員聯(lián)系
source.subscribe(parent);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
parent.onError(ex);
}
}
</pre>
|
</figure>
后續(xù)還有:服務員端菜(CreateEmitter.onNext) —> 顧客小紅拿到菜(SubscribeOnObserver.onNext) —> 顧客小明拿到菜(Observer.onNext),模擬如下:
多次subscribeOn
<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 0px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border-radius: 1px; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 335px;">
source.subscribeOn(Schedulers.newThread())
.subscribeOn(Schedulers.newThread())
.subscribe(observer);
</pre>
|
</figure>
上面我先把任務從一個線程切換到另一個線程,但是只有最先指定的有效(可以用 io 線程更容易看出差別),這是為啥呢?通過上面的分析我們知道,subscribeOn() 每次會返回一個 Observable ,為了方便理解,把先指定返回的Observable 叫黃燜雞1號店,后指定返回的 Observable 叫黃燜雞2號店,第一個 subscribeOn() 執(zhí)行:
黃燜雞1號店創(chuàng)建的時候會持有沙縣小吃的引用,接著第二個 subscribeOn() 執(zhí)行:
黃燜雞2號店創(chuàng)建的時候會持有黃燜雞1號店的引用,接著執(zhí)行 subscribe(observer) 方法,會先調用黃燜雞2號店的 subscribeActual() 方法:
接著調用黃燜雞2號店的 subscribeActual() 方法 :
可以看到此時黃燜雞1號店的 Worker 和小紅是創(chuàng)建在子線程2的,并在子線程2中把當前線程切到了新的線程,后面的操作就和上面一樣了,這就是為啥多次通過 subscribeOn 指定線程,只有最先指定的有效。
最后
多次用 subscribeOn 指定上游線程真的只有第一次有效嗎?其實不然,具體可以看Dávid Karnok 的這篇博客,其中涉及到一些 Rx 操作符操作,本篇只是介紹 subscribeOn 的使用和原理,就不引入其他內容,mark 下日后再撿起來看。