1. 響應式編程
1.1 響應式編程概念
- 響應式編程是一種通過異步和數據流來構建事物關系的編程模型。
- 事物的關系 也可以說成是 業務邏輯 ,是響應式編程的核心理念。
- 數據流 和 異步 是實現這個核心理念的關鍵。異步和數據流都是為了正確的構建事物的關系而存在的。
1.2 響應式編程demo
int a=1;
int b=a+1;
System.out.print(“b=”+b) // b=2
a=10;
System.out.print(“b=”+b) // b=2
上面是一段很常見的代碼,簡單的賦值打印語句,但是這種代碼有一個缺陷,那就是如果我們想表達的并不是一個賦值動作,而是b和a之間的關系,即無論a如何變化,b永遠比a大1。那么可以想見,我們就需要花額外的精力去構建和維護一個b和a的關系。
而響應式編程的想法正是企圖用某種操作符幫助你構建這種關系。
它的思想完全可以用下面的代碼片段來表達:
int a=1;
int b <= a+1; // <= 符號只是表示a和b之間關系的操作符
System.out.print(“b=”+b) // b=2
a=10;
System.out.print(“b=”+b) // b=11
響應式編程的思想,它希望有某種方式能夠構建關系,而不是執行某種賦值命令。
比如在收單應用初始化邏輯中,先完成SDK初始化,數據庫初始化,簽到,才會跳轉到交易菜單界面。
在響應式編程中,這一流程可以這樣解讀
在初始化過程中,SDK初始化,數據庫初始化,簽到這些業務完成之后才會去安排頁面跳轉的操作,那么這些上游的業務在自己工作完成之后,就需要通知下游,通知下游的方式有很多種,響應式編程的方式就是通過數據(事件)流。
每一個業務完成后,都會有一條數據(一個事件)流向下游,下游的業務收到這條數據(這個事件),才會開始自己的工作。
我們能發現SDK初始化,數據庫初始化,簽到這三個業務本身相互獨立,應當在不同的線程環境中執行,以保證他們不會相互阻塞。而假如沒有異步編程,我們可能只能在一個線程中順序調用這三個相對耗時較多的業務,最終再去做頁面跳轉,這樣做不僅沒有忠實反映業務本來的關系,而且會讓你的程序“反應”更慢。
總的來說,異步和數據流都是為了正確的構建事務的關系而存在的。只不過,異步是為了區分出無關的事務,而數據流(事件流)是為了聯系起有關的事務。
2. RxJava
Rx是響應式拓展,即支持響應式編程的一種拓展,用來處理事件和異步任務。
2.1 RxJava的優點
簡潔。而且當業務越繁瑣越復雜時這一點就越顯出優勢——它能夠保持簡潔。
2.2 RxJava的基本概念
我們都知道監聽者模式,訂閱模式這些概念。而Observable和Subscribers的英文意思就是如此。我們大概也知道差不多和監聽者模式差不多。
- Observable事件源,被觀察者。
- Observer / Subcriblers 觀察者,事件訂閱者
- subscribe() 方法,綁定Observable與Subcribler或者Observabler
- 事件 (包括 onNext,onComplete,onError 等事件)
以第一章的初始化應用為例:
Observable obserInitSDK=Observable.create((context)->{initSDK(context)}).subscribeOn(Schedulers.newThread())
Observable obserInitDB=Observable.create((context)->{initDatabase(context)}).subscribeOn(Schedulers.newThread())
Observable obserLogin=Observable.create((context)->{Login(context)})
.subscribeOn(Schedulers.newThread())
// 合并多個Observables的發射物
Observable observable = Observable.merge(obserInitSDK,obserInitDB,obserLogin)
// 訂閱被觀察者
observable.subscribe(()->{startActivity()})
當initSDK,initDB,Login都是耗時較長的操作時,遵照業務關系編寫響應式代碼可以極大的提高程序的執行效率,降低阻塞。
從上面代碼中,可以看出,響應式編程有如下優點
- 在業務層面實現代碼邏輯分離,方便后期維護和拓展
- 極大提高程序響應速度,充分發掘CPU的能力
- 幫助開發者提高代碼的抽象能力和充分理解業務邏輯
- Rx豐富的操作符會幫助我們極大的簡化代碼邏輯
2.3 操作符決策樹
RxJava的幾種主要操作符:
- 創建操作:直接創建一個Observable
- 組合操作:組合多個Observable
- 變換操作:對Observable發射的數據執行變換操作
- 過濾操作:從Observable發射的數據中取特定的值
- 條件/布爾/過濾操作:轉發Observable的部分值
- 算術/聚合操作:對Observable發射的數據序列求值
創建操作
用于創建Observable的操作符
-
Create
— 通過調用觀察者的方法從頭創建一個Observable -
Defer
— 在觀察者訂閱之前不創建這個Observable,為每一個觀察者創建一個新的Observable -
Empty/Never/Throw
— 創建行為受限的特殊Observable -
From
— 將其它的對象或數據結構轉換為Observable -
Interval
— 創建一個定時發射整數序列的Observable -
Just
— 將對象或者對象集合轉換為一個會發射這些對象的Observable -
Range
— 創建發射指定范圍的整數序列的Observable -
Repeat
— 創建重復發射特定的數據或數據序列的Observable -
Start
— 創建發射一個函數的返回值的Observable -
Timer
— 創建在一個指定的延遲之后發射單個數據的Observable
變換操作
用于對Observable發射的數據進行變換
-
Buffer
— 緩存,可以簡單的理解為緩存,它定期從Observable收集數據到一個集合,然后把這些數據集合打包發射,而不是一次發射一個 -
FlatMap
— 扁平映射,將Observable發射的數據變換為Observables集合,然后將這些Observable發射的數據平坦化的放進一個單獨的Observable,可以認為是一個將嵌套的數據結構展開的過程。 -
GroupBy
— 分組,將原來的Observable分拆為Observable集合,將原始Observable發射的數據按Key分組,每一個Observable發射一組不同的數據 -
Map
— 映射,通過對序列的每一項都應用一個函數變換Observable發射的數據,實質是對序列中的每一項執行一個函數,函數的參數就是這個數據項 -
Scan
— 掃描,對Observable發射的每一項數據應用一個函數,然后按順序依次發射這些值 -
Window
— 窗口,定期將來自Observable的數據分拆成一些Observable窗口,然后發射這些窗口,而不是每次發射一項。類似于Buffer,但Buffer發射的是數據,Window發射的是Observable,每一個Observable發射原始Observable的數據的一個子集
過濾操作
用于從Observable發射的數據中進行選擇
-
Debounce
— 只有在空閑了一段時間后才發射數據,通俗的說,就是如果一段時間沒有操作,就執行一次操作 -
Distinct
— 去重,過濾掉重復數據項 -
ElementAt
— 取值,取特定位置的數據項 -
Filter
— 過濾,過濾掉沒有通過謂詞測試的數據項,只發射通過測試的 -
First
— 首項,只發射滿足條件的第一條數據 -
IgnoreElements
— 忽略所有的數據,只保留終止通知(onError或onCompleted) -
Last
— 末項,只發射最后一條數據 -
Sample
— 取樣,定期發射最新的數據,等于是數據抽樣,有的實現里叫ThrottleFirst -
Skip
— 跳過前面的若干項數據 -
SkipLast
— 跳過后面的若干項數據 -
Take
— 只保留前面的若干項數據 -
TakeLast
— 只保留后面的若干項數據
組合操作
用于將多個Observable組合成一個單一的Observable
-
And/Then/When
— 通過模式(And條件)和計劃(Then次序)組合兩個或多個Observable發射的數據集 -
CombineLatest
— 當兩個Observables中的任何一個發射了一個數據時,通過一個指定的函數組合每個Observable發射的最新數據(一共兩個數據),然后發射這個函數的結果 -
Join
— 無論何時,如果一個Observable發射了一個數據項,只要在另一個Observable發射的數據項定義的時間窗口內,就將兩個Observable發射的數據合并發射 -
Merge
— 將兩個Observable發射的數據組合并成一個 -
StartWith
— 在發射原來的Observable的數據序列之前,先發射一個指定的數據序列或數據項 -
Switch
— 將一個發射Observable序列的Observable轉換為這樣一個Observable:它逐個發射那些Observable最近發射的數據 -
Zip
— 打包,使用一個指定的函數將多個Observable發射的數據組合在一起,然后將這個函數的結果作為單項數據發射
錯誤處理
用于從錯誤通知中恢復
輔助操作
用于處理Observable的操作符
-
Delay
— 延遲一段時間發射結果數據 -
Do
— 注冊一個動作占用一些Observable的生命周期事件,相當于Mock某個操作 -
Materialize/Dematerialize
— 將發射的數據和通知都當做數據發射,或者反過來 -
ObserveOn
— 指定觀察者觀察Observable的調度程序(工作線程) -
Serialize
— 強制Observable按次序發射數據并且功能是有效的 -
Subscribe
— 收到Observable發射的數據和通知后執行的操作 -
SubscribeOn
— 指定Observable應該在哪個調度程序上執行 -
TimeInterval
— 將一個Observable轉換為發射兩個數據之間所耗費時間的Observable -
Timeout
— 添加超時機制,如果過了指定的一段時間沒有發射數據,就發射一個錯誤通知 -
Timestamp
— 給Observable發射的每個數據項添加一個時間戳 -
Using
— 創建一個只在Observable的生命周期內存在的一次性資源
條件和布爾操作
用于單個或多個數據項,也可用于Observable
-
All
— 判斷Observable發射的所有的數據項是否都滿足某個條件 -
Amb
— 給定多個Observable,只讓第一個發射數據的Observable發射全部數據 -
Contains
— 判斷Observable是否會發射一個指定的數據項 -
DefaultIfEmpty
— 發射來自原始Observable的數據,如果原始Observable沒有發射數據,就發射一個默認數據 -
SequenceEqual
— 判斷兩個Observable是否按相同的數據序列 -
SkipUntil
— 丟棄原始Observable發射的數據,直到第二個Observable發射了一個數據,然后發射原始Observable的剩余數據 -
SkipWhile
— 丟棄原始Observable發射的數據,直到一個特定的條件為假,然后發射原始Observable剩余的數據 -
TakeUntil
— 發射來自原始Observable的數據,直到第二個Observable發射了一個數據或一個通知 -
TakeWhile
— 發射原始Observable的數據,直到一個特定的條件為真,然后跳過剩余的數據
算術和聚合操作
用于整個數據序列
-
Average
— 計算Observable發射的數據序列的平均值,然后發射這個結果 -
Concat
— 不交錯的連接多個Observable的數據 -
Count
— 計算Observable發射的數據個數,然后發射這個結果 -
Max
— 計算并發射數據序列的最大值 -
Min
— 計算并發射數據序列的最小值 -
Reduce
— 按順序對數據序列的每一個應用某個函數,然后返回這個值 -
Sum
— 計算并發射數據序列的和
另外還有連接操作、轉換操作,可以通過文檔查看使用方法。
2.4 RxJava 基礎框架解析
- 先從比較常用的create方法看
public static Completable create(CompletableOnSubscribe source) {
ObjectHelper.requireNonNull(source, "source is null");
return RxJavaPlugins.onAssembly(new CompletableCreate(source));
}
在 create 方法中,其實很簡單,只是對 source 進行判空處理,并將 source 用 ObservableCreate 包裝起來,并返回回去。下面讓我們一起來看一下 ObservableCreate方法
public final class CompletableCreate extends Completable {
final CompletableOnSubscribe source;
public CompletableCreate(CompletableOnSubscribe source) {
this.source = source;
}
// daizy -- 持有了上游 source 的引用,并重寫 subscribeActual 方法
@Override
protected void subscribeActual(CompletableObserver observer) {
Emitter parent = new Emitter(observer);
observer.onSubscribe(parent);
try {
source.subscribe(parent);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
parent.onError(ex);
}
}
ObservableCreate 也很簡單,它是 Observable 的子類,持有了上游 source 的引用,并重寫 subscribeActual 方法,這個方法要結合訂閱Subscribe源碼看。
@SchedulerSupport(SchedulerSupport.NONE)
@Override
public final void subscribe(CompletableObserver observer) {
// 檢查 observer 是否為 null,為 null 拋出異常
ObjectHelper.requireNonNull(observer, "observer is null");
try {
// RxJavaPlugins 插件的,暫時不管
observer = RxJavaPlugins.onSubscribe(this, observer);
// 檢查 observer 是否為 null,為 null 拋出異常
ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null CompletableObserver. Please check the handler provided to RxJavaPlugins.setOnCompletableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins");
subscribeActual(observer);
} catch (NullPointerException ex) { // NOPMD
throw ex;
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
RxJavaPlugins.onError(ex);
throw toNpe(ex);
}
}
subscribe 方法也比較簡單,大概可以分為以下兩步:
- 第一步,對observer 進行判空,為空則拋出異常
- 第二步,調用 subscribeActual 方法,在Observable類 中,subscribeActual 是一個抽象方法,要關注的是其實現類的subscribeActual方法。從上面的分析,我們知道,當我們調用 Observable create(ObservableOnSubscribe source) 方法的時候,最終會返回 ObservableCreate 實例。因此,我們只需要關注 ObservableCreate 的 subscribeActual 方法。
protected void subscribeActual(Observer<? super T> observer) {
// CreateEmitter 是 ObservableCreate 的一個靜態內部類
CreateEmitter<T> parent = new CreateEmitter<T>(observer);
observer.onSubscribe(parent);
try {
// source 是上游 ObservableOnSubscribe 的引用
source.subscribe(parent);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
parent.onError(ex);
}
}
繼續看ObservableCreate的subscribeActual方法,在執行observer.onSubscribe 方法的時候,會將parent對象作為方法參數暴露出去,parent即是CreateEmitter,可以通過它的dispose方法取消訂閱關系。
接著在調用source.subscribe(parent)的時候,會先調用ObservableOnSubscribe 的 subscribe 方法。
因此,我們可以得出,調用的順序是:
Observable.subscrible -> Observable.subscribleActual -> Observable.subscribleActual -> observer.onSubscribe -> ObservableOnSubscribe.subscribe(emitter)
emitter是CreateEmitter的實例,包裝了observe,調用emitter的方法,就會調用observe的 onNext 、onComolete/onError方法。
以上是RxJava基本原理,Observable 和 Observer 通過 subscribe() 方法實現訂閱關系,從而 Observable 可以在需要的時候發出事件來通知 Observer,并且回調 Observer 的相應的方法。
2.5 RxJava 線程切換
Observable通過subscribeOn方法來指定線程
public final Observable<T> subscribeOn(Scheduler scheduler) {
ObjectHelper.requireNonNull(scheduler, "scheduler is null");
return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
}
通過代碼可以看出,先對scheduler進行判空,然后用ObservableSubscribeOn 將scheduler 包裝起來,接下來研究看看ObservableSubscribeOn這個類的源碼。
final Scheduler scheduler;
public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
super(source);
this.scheduler = scheduler;
}
@Override
public void subscribeActual(final Observer<? super T> observer) {
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(observer);
observer.onSubscribe(parent);
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
首先先來看他的構造函數 ,有兩個參數 source ,scheduler。
- source 代表上游的引用,是 Observable 的一個實例
- scheduler 調度器可以通過 Schedulers.newThread() 或者 Schedulers.io() 創建相應的實例。
RxJava 可用的調度器大概有下面幾種,根據需求選擇:
我們主要看下這個方法
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
SubscribeTask 這個類,他是 ObservableSubscribeOn 的一個非靜態內部類,可以看到 其實也比較簡單,他實現了 Runnable 接口,并且持有 parent 引用。
final class SubscribeTask implements Runnable {
private final SubscribeOnObserver<T> parent;
SubscribeTask(SubscribeOnObserver<T> parent) {
this.parent = parent;
}
@Override
public void run() {
source.subscribe(parent);
}
}
在 run 方法中,通過 source.subscribe(parent) 建立聯系。因而,當我們的 SubscribeTask 的 run 方法運行在哪個線程,相應的 observer 的 subscribe 方法就運行在哪個線程。
接下來再看看scheduleDirect的實現
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;
}
這個方法主要是將task包裝成DisposeTask,然后通過Worker進行調度。再看看Worker 是在做啥。
Scheduler我們以NewThreadScheduler為例子
public final class NewThreadScheduler extends Scheduler {
final ThreadFactory threadFactory;
private static final String THREAD_NAME_PREFIX = "RxNewThreadScheduler";
/**
* daizy -- 線程池
*/
private static final RxThreadFactory THREAD_FACTORY;
/** The name of the system property for setting the thread priority for this Scheduler. */
private static final String KEY_NEWTHREAD_PRIORITY = "rx2.newthread-priority";
static {
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);
}
}
通過代碼可以看出來,Worker里頭封裝了線程池,所以RxJava的線程切換,也是基于線程池來處理。
回過來看DisposeTask
static final class DisposeTask implements Disposable, Runnable, SchedulerRunnableIntrospection {
@NonNull
final Runnable decoratedRun;
@NonNull
final Worker w;
@Nullable
Thread runner;
DisposeTask(@NonNull Runnable decoratedRun, @NonNull Worker w) {
this.decoratedRun = decoratedRun;
this.w = w;
}
@Override
public void run() {
runner = Thread.currentThread();
try {
decoratedRun.run();
} finally {
dispose();
runner = null;
}
}
@Override
public void dispose() {
if (runner == Thread.currentThread() && w instanceof NewThreadWorker) {
((NewThreadWorker)w).shutdown();
} else {
w.dispose();
}
}
@Override
public boolean isDisposed() {
return w.isDisposed();
}
@Override
public Runnable getWrappedRunnable() {
return this.decoratedRun;
}
}
DisposeTask 實現了 Disposable,Runnable ,SchedulerRunnableIntrospection 接口,Disposable 接口主要是用來取消訂閱關系的 Disposable。
從上面的分析,可以得出Observable.subscribeOn方法,控制Observable的執行線程是通過將 Observable.subscribe(Observer) 的操作放在了指定線程中,當我們調用 subcribe 的時候,它的過程是從下往上的,即下面的 Observable 調用上面的 Observable。
用流程圖描述如下: