概述
在 RxJava 中可以通過 subscribeOn/observeOn 很方便地完成上下游指定執行緒的切換,日常開發除了一些常用的Rx 操作符外,這兩個方法也是打交道最多的。最初學習 RxJava 的時候總是死記硬背:subscribeOn 用於指定上游執行緒,observeOn 用於指定下游執行緒,多次用 subscribeOn 指定上游執行緒只有第一次有效,多次用 observeOn 指定下次執行緒,每次都有效…很久不用之後,總是把這兩個方法搞混,那麼這兩個方法內部是怎麼實現的呢?本篇先分析subscribeOn 方法。
例子
先回顧上篇文章的流程,飯店(Observable)開張前提要有廚師(ObservableOnSubscribe),接著改名叫沙縣小吃(ObservableCreate),飯店接客(Observable.subscribe(observer)),建立服務員(CreateEmitter)把顧客和廚師關聯起來,之後廚師每做一道菜都通過服務員端給顧客,整個流程如下:
我們都知道 Andriod 有主執行緒,在未指定執行緒切換操作的情況下,上圖的流程是跑在主執行緒中,另外主執行緒中往往還存在其他任務需要執行,所以結合執行緒來看應該是這樣的
上圖給人一種感覺,好像廚師的菜是「秒做」出來的,然而我們都知道現實生活中廚師做菜是需要時間的,在安卓中,主執行緒執行耗時操作會阻塞後續的任務,還有可能引起 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, "其他任務執行");
複製程式碼
列印如下:
可以看到,由於上游耗時,導致主執行緒中「其他任務」被阻塞了,因此需要新建一個子執行緒來處理上游的耗時任務,使用 RxJava 的 subscribeOn 就能輕鬆實現,修改程式碼:
source.subscribeOn(Schedulers.newThread())
.subscribe(observer);
Log.e(TAG, "其他任務執行");
複製程式碼
列印如下:
此時「其他任務」不會被阻塞。從上面的 log 可以看到,建立了 RxNewThreadScheduler-1 的子執行緒來執行上游的耗時任務,並且此時下游除 onSubscribe 外,所有方法都執行在子執行緒中,它是怎麼做到的?(通常情況下游會呼叫 observeOn(AndroidSchedulers.mainThread()) 來更新UI,下篇分析)。
原始碼分析
source.subscribeOn(Schedulers.newThread())
.subscribe(observer);
複製程式碼
上面的程式碼簡短優雅,其實做了很多事情。基於上篇的分析,在執行完 Observable.create 和 new Observer 後此時主執行緒應該是下面的樣子
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 ;
到目前為止這些操作都是在主執行緒中執行的,子執行緒還未被建立。
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;
}
//省略其他程式碼
}
複製程式碼
目前為止這些操作都是在主執行緒中執行,子執行緒還未建立
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。
註釋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,執行緒池裡的執行緒通過我們傳的執行緒工廠建立。
之後把 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 設定可中斷。至此流程如下
之後所有的事情都是在子執行緒中進行的,上篇已經分析過了
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),模擬如下:
多次subscribeOn
source.subscribeOn(Schedulers.newThread())
.subscribeOn(Schedulers.newThread())
.subscribe(observer);
複製程式碼
上面我先把任務從一個執行緒切換到另一個執行緒,但是隻有最先指定的有效(可以用 io 執行緒更容易看出差別),這是為啥呢?通過上面的分析我們知道,subscribeOn() 每次會返回一個 Observable ,為了方便理解,把先指定返回的Observable 叫黃燜雞1號店,後指定返回的 Observable 叫黃燜雞2號店,第一個 subscribeOn() 執行:
黃燜雞1號店建立的時候會持有沙縣小吃的引用,接著第二個 subscribeOn() 執行:
黃燜雞2號店建立的時候會持有黃燜雞1號店的引用,接著執行 subscribe(observer) 方法,會先呼叫黃燜雞2號店的 subscribeActual() 方法:
接著呼叫黃燜雞2號店的 subscribeActual() 方法 :
可以看到此時黃燜雞1號店的 Worker 和小紅是建立在子執行緒2的,並在子執行緒2中把當前執行緒切到了新的執行緒,後面的操作就和上面一樣了,這就是為啥多次通過 subscribeOn 指定執行緒,只有最先指定的有效。
最後
多次用 subscribeOn 指定上游執行緒真的只有第一次有效嗎?其實不然,具體可以看Dávid Karnok 的這篇部落格,其中涉及到一些 Rx 操作符操作,本篇只是介紹 subscribeOn 的使用和原理,就不引入其他內容,mark 下日後再撿起來看。