圖解RxJava(二)

概述

在 RxJava 中可以通過 subscribeOn/observeOn 很方便地完成上下游指定線程的切換,日常開發(fā)除了一些常用的Rx 操作符外,這兩個方法也是打交道最多的。最初學習 RxJava 的時候總是死記硬背:subscribeOn 用于指定上游線程,observeOn 用于指定下游線程,多次用 subscribeOn 指定上游線程只有第一次有效,多次用 observeOn 指定下次線程,每次都有效…很久不用之后,總是把這兩個方法搞混,那么這兩個方法內部是怎么實現(xiàn)的呢?本篇先分析subscribeOn 方法。

例子

先回顧上篇文章的流程,飯店(Observable)開張前提要有廚師(ObservableOnSubscribe),接著改名叫沙縣小吃(ObservableCreate),飯店接客(Observable.subscribe(observer)),創(chuàng)建服務員(CreateEmitter)把顧客和廚師關聯(lián)起來,之后廚師每做一道菜都通過服務員端給顧客,整個流程如下:

](http://upload-images.jianshu.io/upload_images/7125608-e0656a14ab601797.gif?imageMogr2/auto-orient/strip)](http://7xjvg5.com1.z0.glb.clouddn.com/rxjava2_01.gif)

我們都知道 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 下日后再撿起來看。

感謝

When multiple subscribeOn()s do have effect

SubscribeOn and ObserveOn

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容