概述
如果上網(wǎng)去了解Android App架構(gòu)設(shè)計(jì)沛慢,你會(huì)發(fā)現(xiàn)赡若,都會(huì)有RxJava的身影,現(xiàn)在越來(lái)越多人開始接受并使用RxJava团甲。對(duì)于Android開發(fā)而言逾冬,一旦你理解它,你一定會(huì)愛不釋手。接下來(lái)來(lái)從幾個(gè)方面來(lái)介紹RxJava身腻。
RxJava是什么
官方給RxJava的定義如下:
RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
翻譯:RxJava是 ReactiveX 在JVM上的一個(gè)實(shí)現(xiàn)产还,ReactiveX使用Observable序列組合異步和基于事件的程序。
關(guān)于ReactiveX嘀趟,可以參考官方文檔:
https://mcxiaoke.gitbooks.io/rxdocs/content/Intro.html
ReactiveX.io給的定義是脐区,Rx是一個(gè)使用可觀察數(shù)據(jù)流進(jìn)行異步編程的編程接口,ReactiveX結(jié)合了觀察者模式去件、迭代器模式和函數(shù)式編程的精華坡椒。
RxJava有什么好處
說(shuō)到RxJava的好處,大家肯定會(huì)異口同聲地說(shuō)是:簡(jiǎn)潔尤溜!它可以讓我們的代碼更簡(jiǎn)潔倔叼,邏輯更清晰。
- 函數(shù)式風(fēng)格:對(duì)可觀察數(shù)據(jù)流使用無(wú)副作用的輸入輸出函數(shù)宫莱,避免了程序里錯(cuò)綜復(fù)雜的狀態(tài)
- 簡(jiǎn)化代碼:Rx的操作符通痴稍埽可以將復(fù)雜的難題簡(jiǎn)化為很少的幾行代碼
- 異步錯(cuò)誤處理:傳統(tǒng)的try/catch沒辦法處理異步計(jì)算,Rx提供了合適的錯(cuò)誤處理機(jī)制
- 輕松使用并發(fā):Rx的Observables和Schedulers讓開發(fā)者可以擺脫底層的線程同步和各種并發(fā)問(wèn)題
為了讓大家更清晰的看到RxJava帶來(lái)的好處授霸,套用網(wǎng)上的一個(gè)例子來(lái)說(shuō)明:
假設(shè)有這樣一個(gè)需求:界面上有一個(gè)自定義的視圖 imageCollectorView 巡验,它的作用是顯示多張圖片,并能使用 addImage(Bitmap) 方法來(lái)任意增加顯示的圖片〉舛現(xiàn)在需要程序?qū)⒁粋€(gè)給出的目錄數(shù)組 File[] folders 中每個(gè)目錄下的 png 圖片都加載出來(lái)并顯示在 imageCollectorView 中显设。需要注意的是,由于讀取圖片的這一過(guò)程較為耗時(shí)辛辨,需要放在后臺(tái)執(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)
//找到目錄下的所有文件并發(fā)送出去
.flatMap(new Func1<File, Observable<File>>() {
@Override
public Observable<File> call(File file) {
return Observable.from(file.listFiles());
}
})
//過(guò)濾掉非png結(jié)尾的文件指攒,只發(fā)送png文件
.filter(new Func1<File, Boolean>() {
@Override
public Boolean call(File file) {
return file.getName().endsWith(".png");
}
})
//把收到的png文件轉(zhuǎn)為Bitmap發(fā)送出去
.map(new Func1<File, Bitmap>() {
@Override
public Bitmap call(File file) {
return getBitmapFromFile(file);
}
})
//以上操作都在IO線程執(zhí)行
.subscribeOn(Schedulers.io())
//訂閱者的操作在UI線程執(zhí)行
.observeOn(AndroidSchedulers.mainThread())
//訂閱者
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
//收到Bitmap后添加到控件中顯示出來(lái)
imageCollectorView.addImage(bitmap);
}
});
對(duì)比上述代碼,你會(huì)發(fā)現(xiàn):
- 代碼嵌套的層級(jí)少了僻焚,更加扁平允悦,少了很多迷之縮進(jìn)
- 代碼邏輯也變得更清晰了,步驟明顯
- 線程切換更簡(jiǎn)單虑啤,一行代碼完成線程切換
上面的例子僅體現(xiàn)了一部分的優(yōu)勢(shì)隙弛,RxJava提供很豐富的操作符可用,可以應(yīng)用到很多復(fù)雜的業(yè)務(wù)場(chǎng)景中咐旧。
這里僅列舉一些Android中常用的操作符:
過(guò)濾操作(這些操作符用于從Observable發(fā)射的數(shù)據(jù)中進(jìn)行選擇)
- Debounce — 只有在空閑了一段時(shí)間后才發(fā)射數(shù)據(jù)驶鹉,通俗的說(shuō),就是如果一段時(shí)間沒有操作铣墨,就執(zhí)行一次操作室埋,應(yīng)用于搜索輸入框
- Throttle - 節(jié)流,一段時(shí)間內(nèi)的事件只允許發(fā)送一個(gè)事件,應(yīng)用于防止按鈕多次點(diǎn)擊
- Distinct — 去重姚淆,過(guò)濾掉重復(fù)數(shù)據(jù)項(xiàng)
- Filter — 過(guò)濾孕蝉,過(guò)濾掉沒有通過(guò)謂詞測(cè)試的數(shù)據(jù)項(xiàng),只發(fā)射通過(guò)測(cè)試的
- First — 首項(xiàng)腌逢,只發(fā)射滿足條件的第一條數(shù)據(jù)
組合操作(組合操作符用于將多個(gè)Observable組合成一個(gè)單一的Observable)
- Zip — 打包降淮,使用一個(gè)指定的函數(shù)將多個(gè)Observable發(fā)射的數(shù)據(jù)組合在一起,然后將這個(gè)函數(shù)的結(jié)果作為單項(xiàng)數(shù)據(jù)發(fā)射搏讶。應(yīng)用于一個(gè)界面需要從多個(gè)接口請(qǐng)求數(shù)據(jù)
- Concat - 順序佳鳖,Concat持有多個(gè)Observable對(duì)象,并將它們按順序串聯(lián)成隊(duì)列媒惕。 first()操作符只從串聯(lián)隊(duì)列中取出并發(fā)送第一個(gè)事件系吩。因此,如果使用concat().first()妒蔚,無(wú)論多少個(gè)數(shù)據(jù)源穿挨,只有第一個(gè)事件會(huì)被檢索出并發(fā)送。應(yīng)用于接口緩存
其它操作
- RetryWhen - 重試肴盏,指定什么情況重新執(zhí)行發(fā)射流科盛,應(yīng)用于接口重試
更多操作符參考文檔:https://mcxiaoke.gitbooks.io/rxdocs/content/Operators.html
RxJava基本原理
RxJava 的基本原理就是觀察者模式。它有四個(gè)基本概念:
- Observable - 可觀察者菜皂,即被觀察者
- Observer - 觀察者
- Subscribe - 訂閱
- Event - 事件
與傳統(tǒng)觀察者模式不同贞绵, RxJava 的事件回調(diào)方法除了普通事件 onNext() (相當(dāng)于 onClick() / onEvent())之外,還定義了兩個(gè)特殊的事件:onCompleted() 和 onError()恍飘。
- onCompleted(): 事件隊(duì)列完結(jié)但壮。RxJava 不僅把每個(gè)事件單獨(dú)處理,還會(huì)把它們看做一個(gè)隊(duì)列常侣。RxJava 規(guī)定,當(dāng)不會(huì)再有新的 onNext() 發(fā)出時(shí)弹渔,需要觸發(fā) onCompleted() 方法作為標(biāo)志胳施。
- onError(): 事件隊(duì)列異常。在事件處理過(guò)程中出異常時(shí)肢专,onError() 會(huì)被觸發(fā)舞肆,同時(shí)隊(duì)列自動(dòng)終止,不允許再有事件發(fā)出博杖。
- 在一個(gè)正確運(yùn)行的事件序列中, onCompleted() 和 onError() 有且只有一個(gè)椿胯,并且是事件序列中的最后一個(gè)。需要注意的是剃根,onCompleted() 和 onError() 二者也是互斥的哩盲,即在隊(duì)列中調(diào)用了其中一個(gè),就不應(yīng)該再調(diào)用另一個(gè)。
如何讓整個(gè)事件流跑起來(lái)廉油?
- 創(chuàng)建被觀察者惠险,可以通過(guò)from、just抒线、create等操作符進(jìn)行創(chuàng)建
- 創(chuàng)建觀察者班巩,可以直接new一個(gè)Subscriber的實(shí)現(xiàn)
- 訂閱,通過(guò)subscribe函數(shù)把兩者關(guān)聯(lián)起來(lái)
PS:這里需要執(zhí)行訂閱后事件流才會(huì)開始嘶炭,即如果沒有訂閱者抱慌,被觀察者是不會(huì)執(zhí)行任何操作
以下就是簡(jiǎn)單的創(chuàng)建一個(gè)事件流的示例代碼:
Observable.create(new Observable.OnSubscribe<Object>() {
@Override
public void call(Subscriber<? super Object> subscriber) {
}
}).subscribe(new Subscriber<Object>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Object o) {
}
});
上面的代碼里沒有涉及到線程切換,在 RxJava 的默認(rèn)規(guī)則中眨猎,事件的發(fā)出和消費(fèi)都是在同一個(gè)線程的抑进。也就是說(shuō),如果只用上面的方法宵呛,實(shí)現(xiàn)出來(lái)的只是一個(gè)同步的觀察者模式单匣。觀察者模式本身的目的就是『后臺(tái)處理,前臺(tái)回調(diào)』的異步機(jī)制宝穗,因此異步對(duì)于 RxJava 是至關(guān)重要的户秤。而要實(shí)現(xiàn)異步,則需要用到 RxJava 的另一個(gè)概念: Scheduler(調(diào)度器) 逮矛。
RxJava 已經(jīng)內(nèi)置了幾個(gè) Scheduler 鸡号,它們已經(jīng)適合大多數(shù)的使用場(chǎng)景:
- Schedulers.immediate(): 直接在當(dāng)前線程運(yùn)行,相當(dāng)于不指定線程须鼎。這是默認(rèn)的 Scheduler鲸伴。
- Schedulers.newThread(): 總是啟用新線程,并在新線程執(zhí)行操作晋控。
- Schedulers.io(): I/O 操作(讀寫文件汞窗、讀寫數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)信息交互等)所使用的 Scheduler赡译。行為模式和 newThread() 差不多仲吏,區(qū)別在于 io() 的內(nèi)部實(shí)現(xiàn)是是用一個(gè)無(wú)數(shù)量上限的線程池,可以重用空閑的線程蝌焚,因此多數(shù)情況下 io() 比 newThread() 更有效率裹唆。不要把計(jì)算工作放在 io() 中,可以避免創(chuàng)建不必要的線程只洒。
- Schedulers.computation(): 計(jì)算所使用的 Scheduler许帐。這個(gè)計(jì)算指的是 CPU 密集型計(jì)算,即不會(huì)被 I/O 等操作限制性能的操作毕谴,例如圖形的計(jì)算成畦。這個(gè) Scheduler 使用的固定的線程池距芬,大小為 CPU 核數(shù)。不要把 I/O 操作放在 computation() 中羡鸥,否則 I/O 操作的等待時(shí)間會(huì)浪費(fèi) CPU蔑穴。
- 另外, Android 還有一個(gè)專用的 AndroidSchedulers.mainThread()惧浴,它指定的操作將在 Android 主線程運(yùn)行存和。
有了這幾個(gè) Scheduler ,就可以使用 subscribeOn() 和 observeOn() 兩個(gè)方法來(lái)對(duì)線程進(jìn)行控制了衷旅。
- subscribeOn(): 指定 subscribe() 所發(fā)生的線程捐腿,即 Observable.OnSubscribe 被激活時(shí)所處的線程∈炼ィ或者叫做事件產(chǎn)生的線程茄袖。
- observeOn(): 指定 Subscriber 所運(yùn)行在的線程∴揖猓或者叫做事件消費(fèi)的線程宪祥。
RxJava調(diào)用器還可以隨時(shí)切換線程,讓處理更加靈活:
Observable.just(1, 2, 3, 4) // 運(yùn)行在IO 線程家乘,由最近一個(gè) subscribeOn() 指定
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.map(mapOperator) // 運(yùn)行在新線程蝗羊,由上面最近一個(gè) observeOn() 指定
.observeOn(Schedulers.io())
.map(mapOperator2) // 運(yùn)行在IO 線程,由上面最近一個(gè) observeOn() 指定
.observeOn(AndroidSchedulers.mainThread)
.subscribe(subscriber); // 運(yùn)行在Android 主線程仁锯,由上面最近一個(gè) observeOn() 指定
可以這樣簡(jiǎn)單理解耀找,事件流是自上而下發(fā)送的,所以操作運(yùn)行的線程都是在上游指定的业崖,可以隨意切換野芒。但觸發(fā)訂閱這個(gè)動(dòng)作是在最后面,之前說(shuō)過(guò)双炕,需要有訂閱者訂閱了狞悲,被觀察者才會(huì)去執(zhí)行發(fā)送事件。也就是訂閱動(dòng)作發(fā)生后妇斤,會(huì)自下而上去通知被觀察者效诅,即使中間寫了subscribeOn也會(huì)無(wú)效,有效的只會(huì)是最上面的一個(gè)subscribeOn趟济。所以被觀察者發(fā)送事件的動(dòng)作是由下面最近一個(gè)subscribeOn指定的默色。
RxJava在Android中使用的注意事項(xiàng)
-
內(nèi)存泄漏問(wèn)題
使用RxJava發(fā)布一個(gè)訂閱后秽荞,當(dāng)頁(yè)面被finish,此時(shí)訂閱邏輯還未完成袭厂,如果沒有及時(shí)取消訂閱剑刑,就會(huì)導(dǎo)致Activity/Fragment無(wú)法被回收媳纬,從而引發(fā)內(nèi)存泄漏双肤。
解決方案1:使用RxLifecycle庫(kù),使用此庫(kù)要繼承它的Fragment和Activity基類钮惠,然后在事件流中調(diào)用bindToLifecycle()方法茅糜。
解決方案2:自己管理,在Fragment和Activity素挽、Presenter基類中添加CompositeSubscription, 每次調(diào)用訂閱里把得到的subscription添加進(jìn)去蔑赘,基類會(huì)在onDestroy里自動(dòng)取消訂閱。
-
空指針問(wèn)題
使用RxJava發(fā)布一個(gè)訂閱后预明,當(dāng)頁(yè)面被finish缩赛,此時(shí)訂閱邏輯還未完成,如果沒有及時(shí)取消訂閱撰糠,訂閱邏輯完成后一樣會(huì)觸發(fā)訂閱者的onNext方法酥馍,此時(shí)如果操作了頁(yè)面元素,就會(huì)Crash阅酪。
解決方案:及時(shí)取消訂閱旨袒,同時(shí)把presenter持有的view置空,不執(zhí)行回調(diào)术辐。
總結(jié)
RxJava是一種編程思想的突破砚尽,它給開發(fā)者帶來(lái)了很多便利,讓代碼邏輯更加清晰簡(jiǎn)潔术吗∥炯可能有些開發(fā)團(tuán)隊(duì)認(rèn)為學(xué)習(xí)曲線較長(zhǎng)而放棄使用RxJava,但可以很負(fù)責(zé)任的告訴你较屿,學(xué)習(xí)它隧魄!使用它!享受它隘蝎!