RxJava 在業(yè)內(nèi)越來越受歡迎,對于老手來說唧席,RxJava 太好用了擦盾,RxJava 簡直無所不能嘲驾;然而對于新手來說,RxJava 更像一座陡峭的大山迹卢,似乎不能識得 RxJava 的真面目辽故,無法逾越。糾其原因腐碱,新手更像是只知其然不知其所以然誊垢,只停留在會用的角度是無法了解 RxJava 精髓的。
我們學(xué)習(xí)一個新概念症见,應(yīng)該多去思考喂走,如 RxJava 出現(xiàn)的背景,它解決了什么問題谋作,它是如何解決的芋肠,它有什么優(yōu)點(diǎn),它的核心思想是什么遵蚜,它能應(yīng)用在哪些場景等帖池。如果對這幾個問題都有答案的話,對 RxJava 的基本認(rèn)知就算完成了吭净。對于 RxJava 小白來說睡汹,非常不建議一來就源碼分析,否則很容易陷入代碼黑洞無法自拔寂殉,源碼分析可以在 RxJava 進(jìn)階階段去做囚巴。本文將會圍繞上面提出的幾個問題做一些簡要回答。
RxJava 背景
我們都知道 Java 是一門編程語言不撑,那么 Rx 是什么呢文兢?
Github 對 Rx 的介紹如下:
Reactive Extensions for Async Programming
Rx (Reactive Extension)是異步編程的響應(yīng)式擴(kuò)展,再具體一步說焕檬, Rx 是微軟.NET 的一個響應(yīng)式擴(kuò)展姆坚。Rx 借助可觀測的序列提供一種簡單的方式來創(chuàng)建異步的,基于事件驅(qū)動的程序实愚。開發(fā)者可以使用Observables模擬異步數(shù)據(jù)流兼呵,使用LINQ語法查詢Observables,并且很容易管理調(diào)度器的并發(fā)腊敲。
Netflix 在2012年開始意識到他們的架構(gòu)要滿足他們龐大的用戶群體已經(jīng)變得步履維艱击喂。因此他們決定重新設(shè)計(jì)架構(gòu)來減少 REST 調(diào)用的次數(shù)。取代幾十次的 REST 調(diào)用碰辅,而是讓客戶端自己處理需要的數(shù)據(jù)懂昂,他們決定基于客戶端需求創(chuàng)建一個專門優(yōu)化過的 REST 調(diào)用。
為了實(shí)現(xiàn)這一目標(biāo)没宾,他們決定嘗試響應(yīng)式凌彬,開始將.NET Rx 遷移到 JVM 上面沸柔。他們不想只基于 Java 語言;而是整個 JVM铲敛,從而有可能為市場上的每一種基于 JVM 的語言:如 Java褐澎、Clojure、Groovy伐蒋、Scala 等等提供一種新的工具工三。
2013年二月份,Ben Christensen 和 Jafar Husain 發(fā)在 Netflix 技術(shù)博客的一篇文章第一次向世界展示了 RxJava先鱼。主要特點(diǎn)有:
- 易于并發(fā)從而更好的利用服務(wù)器的能力俭正。
- 易于有條件的異步執(zhí)行。
- 一種更好的方式來避免回調(diào)地獄型型。
- 一種響應(yīng)式方法段审。
Github 對 RxJava 的介紹如下:
RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
RxJava 是一個在 JVM 上使用可觀測的序列來組成異步的、基于事件的程序的庫闹蒜。所以要學(xué)習(xí) RxJava 理念和精髓寺枉,重點(diǎn)應(yīng)該是放在 Rx 上,而不是放在 Java 這門編程語言上绷落。并且 Jake Wharton 大神在 Github 上貢獻(xiàn)了 RxAndroid 擴(kuò)展庫姥闪。RxAndroid 是在 RxJava 的基礎(chǔ)上結(jié)合 Android 系統(tǒng)特性而開發(fā)的一個庫,如主線程砌烁、UI 事件等筐喳。
核心思想
RxJava 的核心思想:異步 和 響應(yīng)式。
在響應(yīng)式的世界里函喉,為了給提供更好的用戶體驗(yàn)避归,一些函數(shù)的計(jì)算、網(wǎng)絡(luò)請求和數(shù)據(jù)庫查詢等操作應(yīng)該異步執(zhí)行管呵,軟件的使用者不應(yīng)該等待這些結(jié)果梳毙。同理對于開發(fā)者而言,也不需要等待結(jié)果捐下,而是在結(jié)果返回時(shí)通知他账锹,在這期間開發(fā)者可以做想做的任何事情。
響應(yīng)式編程不同于命令式或面向?qū)ο缶幊炭澜螅瑓⒖家韵麓a:
int a = 1;
int b = 2;
int c = a + b; // c = 3
a = 4; // c 仍然是 3
對于命令式編程來說奸柬, c = a+b 意思是將表達(dá)式的結(jié)果賦值給 c ,而之后 a 和 b 的值改變均不會影響到 c 婴程。所以在給 a 賦值為4 時(shí)廓奕,c 的值仍然是 3;但是在響應(yīng)式編程中,c 的值會隨著 a 或 b 的值更新而更新懂从,當(dāng)給 a 賦值為4 時(shí)授段,c 的值會自動更新為 6,響應(yīng)式就是要關(guān)注值的變化番甩。
舉例: 一個最直觀的例子就是 Excel 中,若規(guī)定 C1 = SUM(A1,B1)届搁,C1 的值一直會隨著 A1 或 B1 的值變化缘薛。
響應(yīng)式編程是一種基于異步數(shù)據(jù)流概念的編程模式。數(shù)據(jù)流就像一條河:它可以被觀測卡睦,被過濾宴胧,被操作,或者為新的消費(fèi)者與另外一條流合并為一條新的流表锻。
響應(yīng)式編程的一個關(guān)鍵概念是事件恕齐。事件可以被等待,可以觸發(fā)過程瞬逊,也可以觸發(fā)其它事件显歧。事件是唯一的以合適的方式將我們的現(xiàn)實(shí)世界映射到我們的軟件中:如果屋里太熱了我們就打開一扇窗戶。同樣的确镊,當(dāng)我們更改電子表(變化的傳播)中的一些數(shù)值時(shí)士骤,我們需要更新整個表格或者我們的機(jī)器人碰到墻時(shí)會轉(zhuǎn)彎(響應(yīng)事件)。
今天蕾域,響應(yīng)式編程最通用的一個場景是 UI:我們的移動 App 必須做出對網(wǎng)絡(luò)調(diào)用拷肌、用戶觸摸輸入和系統(tǒng)彈框的響應(yīng)。在這個世界上旨巷,軟件之所以是事件驅(qū)動并響應(yīng)的是因?yàn)楝F(xiàn)實(shí)生活也是如此巨缘。
響應(yīng)式編程 文章鏈接:Reactive Programming
擴(kuò)展的觀察者模式
觀察者模式
觀察者模式是最常見的設(shè)計(jì)模式之一。它主要基于 Subject 這個概念采呐,Subject 是一種特殊的對象若锁,當(dāng)他改變時(shí),由它保存的一系列對象將會得到通知懈万。而這一系列對象被稱作 Observers 它們會對外暴露一個通知方法拴清,當(dāng) Subject 狀態(tài)變化時(shí)會調(diào)用的這個方法。
上圖中会通,展示了 Subject/Observer 是怎樣的一個 一對多的關(guān)系口予,如果有需要,一個 Subject 可以有無限多個 Observers涕侈,當(dāng) subject 狀態(tài)發(fā)生變化時(shí)沪停,這些 Observers 中的每一個都會收到通知。
觀察者模式很適合下面這些場景中的任何一個:
- 當(dāng)你的架構(gòu)有兩個實(shí)體類,一個依賴另一個木张,你想讓它們互不影響或者是獨(dú)立復(fù)用它們時(shí)众辨。
- 當(dāng)一個變化的對象通知那些與它自身變化相關(guān)聯(lián)的未知數(shù)量的對象時(shí)。
- 當(dāng)一個變化的對象通知那些無需推斷具體是誰的對象時(shí)舷礼。
擴(kuò)展
在 RxJava 中共有4個角色:
- Observable
- Observer
- Subscriber
- Subjects
Observables 和 Subjects 是兩個“生產(chǎn)”實(shí)體鹃彻,Observers 和 Subscribers 是兩個“消費(fèi)”實(shí)體。同時(shí)相比于觀察者模式妻献,RxJava 添加了三個觀察者缺少的功能:
- 生產(chǎn)者在沒有更多數(shù)據(jù)可用時(shí)能夠發(fā)出信號通知:onCompleted() 事件蛛株。
- 生產(chǎn)者在發(fā)生錯誤時(shí)能夠發(fā)出信號通知:onError() 事件。
- RxJava Observables 能夠組合而不是嵌套育拨,從而避免開發(fā)者陷入回調(diào)地獄谨履。
核心概念
Observables
當(dāng)我們異步執(zhí)行一些復(fù)雜的事情,Java提供了傳統(tǒng)的類熬丧,例如 Thread笋粟、Future、FutureTask析蝴、CompletableFuture 來處理這些問題害捕。當(dāng)復(fù)雜度提升,這些方案就會變得麻煩和難以維護(hù)嫌变。最糟糕的是吨艇,它們都不支持鏈?zhǔn)秸{(diào)用。
RxJava Observables 被設(shè)計(jì)用來解決這些問題腾啥。它們靈活东涡,且易于使用,也可以鏈?zhǔn)秸{(diào)用倘待,并且可以作用于單個結(jié)果程序上疮跑,更有甚者,也可以作用于序列上凸舵。無論何時(shí)你想發(fā)射單個標(biāo)量值祖娘,或者一連串值,甚至是無窮個數(shù)值流啊奄,你都可以使用 Observable渐苏。
Observable的生命周期包含了三種可能的易于與Iterable生命周期事件相比較的事件,
使用Iterable時(shí)菇夸,消費(fèi)者從生產(chǎn)者那里以同步的方式得到值琼富,在這些值得到之前線程處于阻塞狀態(tài)。相反庄新,使用Observable時(shí)鞠眉,生產(chǎn)者以異步的方式把值推給觀察者薯鼠,無論何時(shí),這些值都是可用的械蹋。這種方法之所以更靈活是因?yàn)榧幢阒凳峭交虍惒椒绞降竭_(dá)出皇,消費(fèi)者在這兩種場景都可以根據(jù)自己的需要來處理。
為了更好地復(fù)用Iterable接口哗戈,RxJava Observable 類擴(kuò)展了GOF觀察者模式的語義郊艘。引入了兩個新的接口:
- onCompleted() 即通知觀察者Observable沒有更多的數(shù)據(jù)。
- onError() 即觀察者有錯誤出現(xiàn)了谱醇。
從發(fā)射物的角度來看暇仲,有兩種不同的 Observables :熱的和冷的。一個"熱"的 Observable 典型的只要一創(chuàng)建完就開始發(fā)射數(shù)據(jù)副渴,因此所有后續(xù)訂閱它的觀察者能從序列中間的某個位置開始接受數(shù)據(jù)(有一些數(shù)據(jù)錯過了)。一個"冷"的 Observable 會一直等待全度,直到有觀察者訂閱它才開始發(fā)射數(shù)據(jù)煮剧,因此這個觀察者可以確保會收到整個數(shù)據(jù)序列。
Subject
Subject 是一個神奇的對象将鸵,它可以是一個Observable同時(shí)也可以是一個 Observer:它作為連接這兩個世界的一座橋梁勉盅。一個 Subject 可以訂閱一個 Observable,就像一個觀察者顶掉,并且它可以發(fā)射新的數(shù)據(jù)草娜,或者傳遞它接受到的數(shù)據(jù),就像一個 Observable痒筒。很明顯宰闰,作為一個Observable,觀察者們或者其它 Subject 都可以訂閱它簿透。
一旦 Subject 訂閱了 Observable移袍,它將會觸發(fā) Observable開始發(fā)射。如果原始的 Observable 是“冷”的老充,這將會對訂閱一個“熱”的 Observable 變量產(chǎn)生影響葡盗。RxJava 提供四種不同的 Subject:
- PublishSubject,普通的 Subject 對象
- BehaviorSubject啡浊,BehaviorSubject會首先向他的訂閱者發(fā)送截至訂閱前最新的一個數(shù)據(jù)對象(或初始值),然后正常發(fā)送訂閱后的數(shù)據(jù)流觅够。
- ReplaySubject,會緩存它所訂閱的所有數(shù)據(jù),向任意一個訂閱它的觀察者重發(fā):
- AsyncSubjec巷嚣,當(dāng)Observable 完成時(shí) AsyncSubject只會發(fā)布最后一個數(shù)據(jù)給已經(jīng)訂閱的每一個觀察者喘先。
優(yōu)勢
RxJava 的優(yōu)點(diǎn)一搜一籮筐,如提升開發(fā)效率涂籽,降低維護(hù)成本苹祟,簡化邏輯代碼,提升可讀性等。其逆天之處在于當(dāng)程序邏輯變得復(fù)雜的情況下树枫,它已然能夠保持簡潔直焙。
看一個 RxJava 保持簡潔的范例(此例來源于 扔物線大神 的文章):界面上有一個自定義的視圖 imageCollectorView ,它的作用是顯示多張圖片砂轻,并能使用 addImage(Bitmap) 方法來任意增加顯示的圖片”际模現(xiàn)在需要程序?qū)⒁粋€給出的目錄數(shù)組 File[] folders 中每個目錄下的 png 圖片都加載出來并顯示在 imageCollectorView 中。需要注意的是搔涝,由于讀取圖片的這一過程較為耗時(shí)厨喂,需要放在后臺執(zhí)行,而圖片的顯示則必須在 UI 線程執(zhí)行庄呈。貼出一種實(shí)現(xiàn)如下:
new Thread() {
@Override
public void run() {
super.run();
for (File folder : folders) {
File[] files = folder.listFiles();
for (File file : files) {
if (file.getName().endsWith(".png")) {
final Bitmap bitmap = getBitmapFromFile(file);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
imageCollectorView.addImage(bitmap);
}
});
}
}
}
}
}.start();
而用 RxJava 實(shí)現(xiàn)如下:
Observable.from(folders)
.flatMap(new Func1<File, Observable<File>>() {
@Override
public Observable<File> call(File file) {
return Observable.from(file.listFiles());
}
})
.filter(new Func1<File, Boolean>() {
@Override
public Boolean call(File file) {
return file.getName().endsWith(".png");
}
})
.map(new Func1<File, Bitmap>() {
@Override
public Bitmap call(File file) {
return getBitmapFromFile(file);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
imageCollectorView.addImage(bitmap);
}
});
如果 IDE 是 Android Studio 蜕煌,每次打開某個 Java 文件的時(shí)候,你會看到被自動 Lambda 化的預(yù)覽诬留,這將讓你更加清晰地看到程序邏輯:
Observable.from(folders)
.flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })
.filter((Func1) (file) -> { file.getName().endsWith(".png") })
.map((Func1) (file) -> { getBitmapFromFile(file) })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Action1) (bitmap) -> { imageCollectorView.addImage(bitmap) });
流式的 API 調(diào)用風(fēng)格看著很爽有木有P奔汀!文兑! 良好的編碼規(guī)范邏輯清楚盒刚,能方便我們更好的定位問題、解決問題绿贞,進(jìn)而能提升我們的效率因块。
這里引用《代碼大全》第 31 章中“把布局當(dāng)做一種信仰”的一段話:
良好布局的目標(biāo):
1、準(zhǔn)確表現(xiàn)代碼的邏輯結(jié)構(gòu)
2籍铁、始終如一的表現(xiàn)代碼的邏輯結(jié)構(gòu)
3涡上、改善可讀性
4、經(jīng)得起修改
RxJava 的代碼布局既準(zhǔn)確的表示了代碼的邏輯結(jié)構(gòu)寨辩,又增強(qiáng)了可讀性和可維護(hù)性吓懈,對于有代碼潔癖的小伙伴來說是大大的福利啊。
使用范例
1靡狞、在項(xiàng)目的 build.gradle 文件中添加對 RxJava 的支持
對于 RxJava 1.x 版本耻警,
compile 'io.reactivex:rxjava:x.y.z‘
對于 RxJava 2.x 版本
compile "io.reactivex.rxjava2:rxjava:2.x.y"
需要注意的是,在一個項(xiàng)目中甸怕, RxJava 1.x 和 2.x 是可以同時(shí)使用的 甘穿,因?yàn)椴煌姹镜囊蕾嚶窂绞遣灰粯拥模磳τ谕粋€類 1.x 版本和 2.x 版本的全路徑名不一樣梢杭,我們在使用時(shí)只需要注意引入的包名是否正確就可以了温兼。
2、使用范例
- 創(chuàng)建被觀察者
Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello武契,RxJava");
subscriber.onCompleted();
}
});
- 創(chuàng)建觀察者
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
Log.d(tag, "Item: " + s);
}
@Override
public void onCompleted() {
Log.d(tag, "Completed!");
}
@Override
public void onError(Throwable e) {
Log.d(tag, "Error!");
}
};
- 訂閱
observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);
RxJava 的使用還是比較簡單的募判, 如果有更復(fù)雜的需求荡含,可以熟悉相關(guān)操作符。 RxJava 的操作符基本能夠滿足所有的使用需求届垫。
應(yīng)用場景
- RxJava + Retrofit 释液,最熱門的框架組合,實(shí)現(xiàn)網(wǎng)絡(luò)請求及結(jié)果的異步處理
- RxBinding装处,Jake Wharton 的一個開源庫误债,本質(zhì)上為 View 設(shè)置一些 Listenter,只不過是用 Observable 實(shí)現(xiàn)妄迁,如界面按鈕防止連續(xù)點(diǎn)擊寝蹈、Android 一些 CheckBox 點(diǎn)擊更新響應(yīng)視圖等
- 線程切換,在后臺線程取數(shù)據(jù)登淘,主線程展示”的模式中看見
- 異步操作箫老, 耗時(shí)操作,如復(fù)雜計(jì)算黔州、網(wǎng)絡(luò)請求槽惫、I/O操作等
- RxBus,RxBus并不是一個庫辩撑,而是一種模式。其思想是用 RxJava 實(shí)現(xiàn)解耦仿耽,同 GreenRobot 的 EventBus 和 Otto 類似合冀。
對于 RxJava 的基礎(chǔ)部分就介紹就到這里了,后續(xù)文章將會介紹 RxJava 中的常用操作符及實(shí)現(xiàn)原理项贺。