圖解RxJava2(二)

HuYounger發表於2017-12-21

概述

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

例子

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

圖解RxJava2(二)

我們都知道 Andriod 有主執行緒,在未指定執行緒切換操作的情況下,上圖的流程是跑在主執行緒中,另外主執行緒中往往還存在其他任務需要執行,所以結合執行緒來看應該是這樣的

圖解RxJava2(二)

上圖給人一種感覺,好像廚師的菜是「秒做」出來的,然而我們都知道現實生活中廚師做菜是需要時間的,在安卓中,主執行緒執行耗時操作會阻塞後續的任務,還有可能引起 ANR,所以廚師做菜的操作不能放在主執行緒中 。下面讓上游睡5秒模擬耗時操作

上游:

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();
            }
        });
複製程式碼

下游:

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());
            }
        };
複製程式碼

建立聯絡,以及執行其他任務(這裡只是打了個 log )

source.subscribe(observer);
Log.d(TAG, "其他任務執行");
複製程式碼

列印如下:

圖解RxJava2(二)

可以看到,由於上游耗時,導致主執行緒中「其他任務」被阻塞了,因此需要新建一個子執行緒來處理上游的耗時任務,使用 RxJava 的 subscribeOn 就能輕鬆實現,修改程式碼:

source.subscribeOn(Schedulers.newThread())
                .subscribe(observer);
Log.e(TAG, "其他任務執行");
複製程式碼

列印如下:

圖解RxJava2(二)

此時「其他任務」不會被阻塞。從上面的 log 可以看到,建立了 RxNewThreadScheduler-1 的子執行緒來執行上游的耗時任務,並且此時下游除 onSubscribe 外,所有方法都執行在子執行緒中,它是怎麼做到的?(通常情況下游會呼叫 observeOn(AndroidSchedulers.mainThread()) 來更新UI,下篇分析)。

原始碼分析

source.subscribeOn(Schedulers.newThread())
                .subscribe(observer);
複製程式碼

上面的程式碼簡短優雅,其實做了很多事情。基於上篇的分析,在執行完 Observable.create 和 new Observer 後此時主執行緒應該是下面的樣子

圖解RxJava2(二)

Schedulers.newThread()

Scheduler 翻譯為排程器,RxJava2 中 Scheduler 的一些常用子類如下:

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();
}
複製程式碼

Schedulers.newThread() 會初始化 NewThreadScheduler ;

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. */
    //用來設定執行緒優先順序的key
    private static final String KEY_NEWTHREAD_PRIORITY = "rx2.newthread-priority";

    //靜態程式碼塊
    static {
        //確定執行緒的優先順序,這裡初始化為5 NORM_PRIORITY
        int priority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY,
                Integer.getInteger(KEY_NEWTHREAD_PRIORITY, Thread.NORM_PRIORITY)));
        //初始化執行緒工廠,傳入執行緒名稱和優先順序
        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);
    }
}
複製程式碼

上面的註釋已經解釋得很清楚了,在初始化 NewThreadScheduler 的時候會建立 RxThreadFactory,並指明瞭該執行緒工廠之後生產執行緒的名稱和預設優先順序;RxThreadFactory 是 ThreadFactory 的子類,也沒多少程式碼

    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;
    }
複製程式碼

RxThreadFactory 中的 newThread 方法用來生產新執行緒。Schedulers.newThread() 到此就完成了它的工作,總結下來就是:

1.建立執行緒排程器 NewThreadScheduler;

2.建立執行緒工廠 RxThreadFactory ;

到目前為止這些操作都是在主執行緒中執行的,子執行緒還未被建立。

圖解RxJava2(二)

subscribeOn(Scheduler scheduler)

public final Observable<T> subscribeOn(Scheduler scheduler) {
    ObjectHelper.requireNonNull(scheduler, "scheduler is null");
    return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
}
複製程式碼

該方法返回 Observable ,建立了 ObservableSubscribeOn ,名字起得又很容易讓人頭暈…這裡就不畫關係圖了,只關心它的屬性即可,它是 Observable(飯店) 的子類,結合我們舉的例子,就給它起名黃燜雞飯店;this 就是上面傳過來的沙縣小吃(ObservableCreate) ;初始化如下:

public final class ObservableSubscribeOn<T> extends AbstractObservableWithUpstream<T, T> {
    final Scheduler scheduler;

    public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
        super(source);
        this.scheduler = scheduler;
    }
    //省略其他程式碼
}
複製程式碼

目前為止這些操作都是在主執行緒中執行,子執行緒還未建立

圖解RxJava2(二)

subscribe(Observer observer)

通過上篇學習可知,subscribe(observer) 內部會呼叫 subscribeActual(observer) ,該方法是個抽象方法,具體實現在 Observable(飯店) 的子類,現在是 ObservableSubscribeOn(黃燜雞飯店)。

    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)));
    }
複製程式碼

註釋1 又冒出來一個 SubscribeOnObserver,同樣只關心它的屬性,SubscribeOnObserver 是AtomicReference的子類(保證原子性),同時實現了 Observer(也是個顧客) 和 Disposable(保證一次性操作) 介面;為了方便理解,假設之前傳的顧客叫小明,這裡的顧客叫小紅,小紅會持有小明的引用(actual),之後一系列的方法實際上會呼叫到小明的方法。

註釋2 執行顧客小明的 onSubscribe 方法,我們發現到目前為止還沒有建立過子執行緒,所以解釋了上面 log 下游 onSubscribe 列印執行緒名為 main。

圖解RxJava2(二)

註釋3 分為下面3步

               步驟③                    步驟②             步驟①
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
複製程式碼

步驟① SubscribeTask 是 ObservableSubscribeOn(黃燜雞飯店) 的內部類,實現了 Runnable 介面

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);
    }
}
複製程式碼

如果 run 方法被觸發,那麼執行順序是:

Observable.subscribe() —> Observable.subscribeActual() —> ObservableCreate.subscribeActual(),繞了一圈又回到上篇的那個流程。為了方便理解,SubscribeTask 就是黃燜雞飯店(ObservableSubscribeOn)的「任務」也就是沙縣小吃的「做菜」(ObservableCreate.subscribeActual)。所以現在萬事具備,只差子執行緒了。

步驟② Scheduler.scheduleDirect()

/**
 * 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
 */
//這裡排程的時候不保證順序
//第二個引數為0,不延時,直接排程
@NonNull
public Disposable scheduleDirect(@NonNull Runnable run) {
    return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS);
}
複製程式碼

注意這個方法的註釋,該方法排程的時候不保證順序,所以平時在配合使用 subscribeOn(子執行緒)/observeOn(主執行緒) 會出現上下游輸出順序不確定的情況(比如有時候上游生產了3個後才逐個傳送給下游,有時上游生產了2個,就開始傳送給下游),這也是多執行緒的一個特點。當然這裡不會出現這個情況,因為從輸出來看,此時上下游都在一個子執行緒裡。貌似跑遠了...繼續分析

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();
複製程式碼

前面建立 NewThreadScheduler 的時候說 createWorker() 方法很重要,這裡派上用場了:

//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;
}
複製程式碼

NewThreadWorker 內部維護一個執行緒池 ScheduledExecutorService , 主要作用是提供延時排程和週期性排程,預設執行緒池大小為1,執行緒池裡的執行緒通過我們傳的執行緒工廠建立。

圖解RxJava2(二)

之後把 NewThreadWorker 和步驟①中的任務包裝成 DisposeTask,又是一個Runnable

public void run() {
    //注意這裡會獲取當前所線上程
    runner = Thread.currentThread();
    try {
        //在當前執行緒中執行
        decoratedRun.run();
    } finally {
        //執行完後斷開
        dispose();
        runner = null;
    }
}
複製程式碼

最後會執行 NewThreadWorker.schedule 方法

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) {
            //此時任務執行在子執行緒中
            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;
    }
}
複製程式碼

到這裡終於看到任務(ObservableCreate.subscribeActual)執行在子執行緒中。

步驟③ parent.setDisposable 設定可中斷。至此流程如下

圖解RxJava2(二)

之後所有的事情都是在子執行緒中進行的,上篇已經分析過了

protected void subscribeActual(Observer<? super T> observer) {
    //建立服務員,並和顧客聯絡,這裡的顧客是小紅
    CreateEmitter<T> parent = new CreateEmitter<T>(observer);
    //執行顧客小紅的的 onSubscribe ,注意這裡不會再回撥顧客小明的onSubscribe
    //因為顧客小紅的 onSubscribe 中只是將接收事件的行為設定成一次性,並沒有回撥小明方法
    observer.onSubscribe(parent);

    try {
        //廚師做菜,並和服務員聯絡
        source.subscribe(parent);
    } catch (Throwable ex) {
        Exceptions.throwIfFatal(ex);
        parent.onError(ex);
    }
}
複製程式碼

後續還有:服務員端菜(CreateEmitter.onNext) —> 顧客小紅拿到菜(SubscribeOnObserver.onNext) —> 顧客小明拿到菜(Observer.onNext),模擬如下:

圖解RxJava2(二)

多次subscribeOn

source.subscribeOn(Schedulers.newThread())
        .subscribeOn(Schedulers.newThread())
        .subscribe(observer);
複製程式碼

上面我先把任務從一個執行緒切換到另一個執行緒,但是隻有最先指定的有效(可以用 io 執行緒更容易看出差別),這是為啥呢?通過上面的分析我們知道,subscribeOn() 每次會返回一個 Observable ,為了方便理解,把先指定返回的Observable 叫黃燜雞1號店,後指定返回的 Observable 叫黃燜雞2號店,第一個 subscribeOn() 執行:

圖解RxJava2(二)

黃燜雞1號店建立的時候會持有沙縣小吃的引用,接著第二個 subscribeOn() 執行:

圖解RxJava2(二)

黃燜雞2號店建立的時候會持有黃燜雞1號店的引用,接著執行 subscribe(observer) 方法,會先呼叫黃燜雞2號店的 subscribeActual() 方法:

圖解RxJava2(二)

接著呼叫黃燜雞2號店的 subscribeActual() 方法 :

圖解RxJava2(二)

可以看到此時黃燜雞1號店的 Worker 和小紅是建立在子執行緒2的,並在子執行緒2中把當前執行緒切到了新的執行緒,後面的操作就和上面一樣了,這就是為啥多次通過 subscribeOn 指定執行緒,只有最先指定的有效。

最後

多次用 subscribeOn 指定上游執行緒真的只有第一次有效嗎?其實不然,具體可以看Dávid Karnok 的這篇部落格,其中涉及到一些 Rx 操作符操作,本篇只是介紹 subscribeOn 的使用和原理,就不引入其他內容,mark 下日後再撿起來看。

感謝

When multiple subscribeOn()s do have effect

SubscribeOn and ObserveOn

相關文章