RxJava技術(shù)分享
京金所—時(shí)光
2016.9.22
這里我拿出來給 Android 開發(fā)者的 RxJava 詳解中的例子
假設(shè)有這樣一個(gè)需求:界面上有一個(gè)自定義的視圖 imageCollectorView ,它的作用是顯示多張圖片,并能使用 addImage(Bitmap) 方法來任意增加顯示的圖片。現(xiàn)在需要程序?qū)⒁粋€(gè)給出的目錄數(shù)組 File[] folders 中每個(gè)目錄下的 png 圖片都加載出來并顯示在 imageCollectorView 中。需要注意的是,由于讀取圖片的這一過程較為耗時(shí),需要放在后臺執(zhí)行,而圖片的顯示則必須在 UI 線程執(zhí)行。常用的實(shí)現(xiàn)方式有多種,我這里貼出其中一種:
`
//開啟一條子線程
new Thread() {
@Override
public void run() {
super.run();
//遍歷給出的目錄數(shù)組,獲取每一個(gè)目錄里面的文件的數(shù)組
for (File folder : folders) {
File[] files = folder.listFiles();
//遍歷這個(gè)文件數(shù)組,篩選出來png圖片
for (File file : files) {
if (file.getName().endsWith(".png")) {
//將路徑轉(zhuǎn)換成Bitmap圖片
final Bitmap bitmap = getBitmapFromFile(file);
//切換到主線程更新UI。
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
imageCollectorView.addImage(bitmap);
}
});
}
}
}
}
}.start();
`
而如果使用 RxJava ,實(shí)現(xiàn)方式是這樣的:
`
//將文件夾目錄創(chuàng)建成一個(gè)可觀測的序列
Observable.from(folders)
//遍歷目錄數(shù)組中的每一個(gè)文件對象,并將這每一個(gè)文件對象轉(zhuǎn)換成可觀測序列
.flatMap(new Func1<File, Observable<File>>() {
@Override
public Observable<File> call(File file) {
return Observable.from(file.listFiles());
}
})
//對這每一個(gè)文件對象進(jìn)行過濾,選出png圖片
.filter(new Func1<File, Boolean>() {
@Override
public Boolean call(File file) {
return file.getName().endsWith(".png");
}
})
//將這每一個(gè)png文件都轉(zhuǎn)成Bitmap傳遞
.map(new Func1<File, Bitmap>() {
@Override
public Bitmap call(File file) {
return getBitmapFromFile(file);
}
})
//耗時(shí)操作運(yùn)行在子線程
.subscribeOn(Schedulers.io())
//修改UI的操作放在主線程
.observeOn(AndroidSchedulers.mainThread())
//訂閱,展示
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
imageCollectorView.addImage(bitmap);
}
});
`
今天主要從以下幾個(gè)方面來著重介紹RxJava
- RxJava的重要概念
- RxJava的操作符
- RxJava的應(yīng)用場景
一:RxJava的重要概念
1.1 什么是RxJava?
RxJava 在 GitHub 主頁上的自我介紹是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一個(gè)在 Java VM 上使用可觀測的序列來組成異步的、基于事件的程序的庫)。
到底什么是RxJava,一個(gè)詞,就是異步。兩個(gè)詞就是異步+鏈?zhǔn)讲僮鳌?/p>
1.2 RxJava難嗎?
RxJava其實(shí)一點(diǎn)也不難。RxJava能做到的東西,別人也一樣可以做到。但是,如果使用RxJava,那么可以比他們邏輯更簡潔。有人說Rx太好用了,也有人說RxJava太難用了。好用是因?yàn)镽xJava使用操作符(函數(shù)式編程)極大的簡化了我們代碼編寫的邏輯,難用可能就是對操作符用的還不習(xí)慣,對命令式編程的思想有些固話。
1.3 但是很多人說學(xué)習(xí)RxJava起點(diǎn)高?。?/h3>
高在哪里。我認(rèn)為是幾個(gè)重要的思想。在我剛學(xué)習(xí)Java語言的時(shí)候,我認(rèn)識了面向?qū)ο蟮木幊趟枷?。簡單來說就是指揮對象幫我們做事。通過一個(gè)點(diǎn).即可。同樣,我認(rèn)為學(xué)習(xí)RxJava也是學(xué)習(xí)一個(gè)編程思想。就是函數(shù)式編程,和響應(yīng)式編程。我不知道這算不算是編程思想,但我知道一旦明白這兩個(gè)東東,對我們學(xué)習(xí)Rx有著意想不到的好處。
今天,今天通過介紹 Rx里面的概念(這部分我覺得才是最重要的一點(diǎn)),Rx里面的操作符,Rx里面的常用場景,還有Rx的擴(kuò)展,將讀者帶入Rx的大門,了解他,觸摸它,使用它,掌握它。
1.4 響應(yīng)式編程:
什么是響應(yīng)式編程(Reactive programming)? RxJava ==>ReactiveX Java
百度百科的定義:
響應(yīng)式編程是一種==面向數(shù)據(jù)流和變化傳播==的==編程范式==。這意味著可以在編程語言中很方便地表達(dá)靜態(tài)或動態(tài)的數(shù)據(jù)流,而相關(guān)的計(jì)算模型會自動將變化的值通過數(shù)據(jù)流進(jìn)行傳播。
例如,在命令式編程環(huán)境中(面向過程和面向?qū)ο蟮恼Z言中),a=b+c表示將表達(dá)式的結(jié)果賦給a,而之后改變b或c的值不會影響a。但在響應(yīng)式編程中,a的值會隨著b或c的更新而更新。響應(yīng)式編程最初是為了簡化交互式用戶界面的創(chuàng)建和實(shí)時(shí)系統(tǒng)動畫的繪制而提出來的一種方法,但它本質(zhì)上是一種通用的編程范式。
這里什么是編程范式呢?說白了就是一種編碼的風(fēng)格和模式。
此時(shí),我們不免會疑惑,數(shù)據(jù)我們知道,那么數(shù)據(jù)流呢?
在RxJava Essentials一書中,是這么說的。
響應(yīng)式編程是一種基于異步數(shù)據(jù)流概念的編程模式。數(shù)據(jù)流就像一條河:它可以被觀測,被過濾,被操作,或者為新的消費(fèi)者與另外一條流合并為一條新的流。
響應(yīng)式編程的一個(gè)關(guān)鍵概念是事件。事件可以被等待,可以觸發(fā)過程,也可以觸發(fā)其它事件。
我相信看到這里大部分人已經(jīng)暈了。
不過我是越來越清晰,一字一句的多讀幾遍。
在那些年我們錯過的響應(yīng)式編程中是這么介紹的:
響應(yīng)式編程就是與異步數(shù)據(jù)流交互的編程方式。(和上面基本一樣)
一方面,這已經(jīng)不是什么新事物了。事件總線(Event Buses)或一些典型的點(diǎn)擊事件本質(zhì)上就是一個(gè)異步事件流(asynchronous event stream),這樣你就可以觀察它的變化并使其做出一些反應(yīng)(do some side effects)。響應(yīng)式是這樣的一個(gè)思路:除了點(diǎn)擊和懸停(hover)的事件外,你可以給任何事物創(chuàng)建數(shù)據(jù)流。數(shù)據(jù)流無處不在,任何東西都可以成為一個(gè)數(shù)據(jù)流,例如變量、用戶輸入、屬性、緩存、數(shù)據(jù)結(jié)構(gòu)等等。舉個(gè)栗子,你可以把你的微博訂閱功能想象成跟點(diǎn)擊事件一樣的數(shù)據(jù)流,你可以監(jiān)聽這樣的數(shù)據(jù)流,并做出相應(yīng)的反應(yīng)。
最重要的是,你會擁有一些令人驚艷的函數(shù)去結(jié)合、創(chuàng)建和過濾任何一組數(shù)據(jù)流。 這就是”函數(shù)式編程”的魔力所在。一個(gè)數(shù)據(jù)流可以作為另一個(gè)數(shù)據(jù)流的輸入,甚至多個(gè)數(shù)據(jù)流也可以作為另一個(gè)數(shù)據(jù)流的輸入。你可以合并兩個(gè)數(shù)據(jù)流,也可以過濾一個(gè)數(shù)據(jù)流得到另一個(gè)只包含你感興趣的事件的數(shù)據(jù)流,還可以映射一個(gè)數(shù)據(jù)流的值到一個(gè)新的數(shù)據(jù)流里。
最后我說一下我的理解:
首先響應(yīng)式編程僅僅是一種編程的模式,風(fēng)格,并不具有特殊的含義和操作,響應(yīng)式編程他是用來對數(shù)據(jù)流進(jìn)行操作的,所有對數(shù)據(jù)流進(jìn)行異步操作,觀測等行為,我們都可以管這種操作行為叫做響應(yīng)式編程。那么什么又是數(shù)據(jù)流呢?
1.4 數(shù)據(jù)流的概念:
數(shù)據(jù)流是整個(gè)響應(yīng)式編程體系中的核心,要想學(xué)習(xí)響應(yīng)式編程,首先我們就需要搞明白什么是數(shù)據(jù)流。其實(shí),一個(gè)數(shù)據(jù)流是一個(gè)按時(shí)間排序的即將發(fā)生的事件(Ongoing events ordered in time)的序列。就比如我們警察抓小偷,小偷偷東西的時(shí)候,警察抓住他了嗎,小偷偷東西之前做了什么,之后做了什么,警察抓他之前做了什么,抓住他做了什么或者沒抓住又該怎么去做。這一系列事件組成的序列就是數(shù)據(jù)流。數(shù)據(jù)流就像一條河:它可以被觀測,被過濾,被操作,或者為新的消費(fèi)者與另外一條流合并為一條新的流。
也就是說我們的響應(yīng)式編程,就是對這一系列異步事件進(jìn)行處理的一種編程模式。其實(shí),你會發(fā)現(xiàn),看了一大圈,又回到了第一句話,但是我相信,你現(xiàn)在應(yīng)該不會在迷糊了。
1.5 函數(shù)式編程:
其實(shí)我一直搞不明白函數(shù)式編程和響應(yīng)式編程。但是現(xiàn)在我明白了,而且他倆根本就不一樣。
1.5.1 什么是函數(shù)式編程:
應(yīng)該有至少三種編程思想:
- 命令式編程 Imperative programming
- 邏輯式編程 Object-oriented Programming
- 函數(shù)式編程 Functional Programming
命令式編程關(guān)心解決問題的步驟,面向?qū)ο缶幊淌且彩且环N命令式編程,面向過程的C語言必然也是了。
而函數(shù)式編程關(guān)心數(shù)據(jù)的映射,即一種東西和另一種東西之間的對應(yīng)關(guān)系。它的主要思想是把運(yùn)算過程盡量寫成一系列嵌套的函數(shù)調(diào)用。
比如這種運(yùn)算:(1 + 2) * 3 - 4 ;
之前我們寫是這樣:
```
int a = 1 + 2; int b = a * 3; int result = b - 4;
System.out.println(b);
```
然而用函數(shù)式編程思想寫就是這樣:
```
System.out.print(subtract( multiply(add(1,2),3),4) );
```
全部變?yōu)榱撕瘮?shù)調(diào)用,這樣看起來也簡潔、見名之意。(示例來自阮一峰博客)
看到這,想一下,使用RxJava時(shí)是不是全部調(diào)用各種操作符進(jìn)行處理,這就是對事件流進(jìn)行運(yùn)算啊,全部調(diào)用函數(shù)進(jìn)行處理。
在這里我們來對比一下函數(shù)式編程和響應(yīng)式編程。函數(shù)式編程是對數(shù)據(jù)關(guān)系進(jìn)行==映射==,比如說y=ax;這個(gè)一元函數(shù)。函數(shù)式編程我們只需要定義處來這個(gè)映射關(guān)系即參數(shù)a的算法,我們就可以得到y(tǒng)。而響應(yīng)式編程的重點(diǎn)則不在于這個(gè)參數(shù)a,而是當(dāng)我的x發(fā)生改變的時(shí)候(就是我們注冊的x的狀態(tài)改變)的時(shí)候,我要做出一個(gè)什么樣反應(yīng)。這和我大學(xué)學(xué)到的激勵和響應(yīng)是差不多的。我給你一個(gè)什么樣的激勵,你能反饋我一個(gè)什么樣的響應(yīng)。
1.6 觀察者模式
在今天,觀察者模式是出現(xiàn)的最常用的軟件設(shè)計(jì)模式之一。它基于subject這個(gè)概念。subject是一種特殊對象,當(dāng)它改變時(shí),那些由它保存的一系列對象將會得到通知。而這一系列對象被稱作Observers(觀察者),它們會對外暴漏了一個(gè)通知方法,當(dāng)subject狀態(tài)發(fā)生變化時(shí)會調(diào)用的這個(gè)方法。
就比如說,當(dāng)A的某狀態(tài)發(fā)生改變或者接收到某種激勵的時(shí)候,B需要對這種激勵做出一個(gè)反饋,那么我們只需要讓A持有B的引用,同時(shí)去調(diào)用B的方法即可(我們最終的目的是將發(fā)生改變的狀態(tài)傳遞出去,通過方法傳遞,我們的B需要這個(gè)狀態(tài))。但是通常,我們的A不知道自己什么時(shí)候能獲取到這個(gè)激勵(什么時(shí)候狀態(tài)會發(fā)生改變),這個(gè)時(shí)候我們的B呢就可以通過注冊,來處理這個(gè)響應(yīng)。同時(shí),我們也可以不用拿到B的引用,我們可以通過一個(gè)中間接口,將發(fā)生改變的狀態(tài)(或者是B需要的狀態(tài))通過接口傳遞過去,同時(shí)B只需要拿到這個(gè)接口的實(shí)例對象即可,這就是B的注冊過程。也是最簡單的回調(diào)。而回調(diào)也是觀察者模式的一種簡化形式,1-1的關(guān)系,當(dāng)然我們平時(shí)不僅setOnClickListener()也addOnXxxListener(),這種一對多的關(guān)系就是觀察者模式。
1.7 擴(kuò)展的觀察者模式
與傳統(tǒng)觀察者模式不同, RxJava的事件回調(diào)方法除了普通事件 onNext() (相當(dāng)于 onClick() / onEvent())之外,還定義了兩個(gè)特殊的事件:onCompleted() 和 onError()。
1.8 幾個(gè)常見的類
在RxJava的世界里,我們有四種角色:
Observable
Observer
Subscriber
Subjects
Observables和Subjects是兩個(gè)“生產(chǎn)”實(shí)體,Observers和Subscribers是兩個(gè)“消費(fèi)”實(shí)體
```
public class Observable<T> {
final OnSubscribe<T> onSubscribe;
}
public interface Observer<T>{
onComplete();onError;onNext();
}
public abstract class Subscriber<T> implements Observer<T>, Subscription
public abstract class Subject<T, R> extends Observable<R> implements Observer<T>
*/
```
總覽:
Creating Observables
Operators that originate new Observables.
- Create — create an Observable ++from scratch(從頭)++ by calling observer methods ++programmatically(以編程的方式)++
- ++Defer(延遲創(chuàng)建)++ — do not create the Observable until the observer subscribes, and create a fresh Observable for each observer
- Empty/Never/Throw — create Observables that have very ++precise(精確的)++ and limited behavior
- From — ++convert(轉(zhuǎn)換)++ some other object or data structure into an Observable
- ++Interval(間隔)++ — create an Observable that ++emits(發(fā)射)++ a sequence of integers spaced by a particular time interval
- Just — convert an object or a set of objects into an Observable that emits that or those objects
- Range — create an Observable that emits a range of sequential integers
- Repeat — create an Observable that emits a particular item or sequence of items repeatedly
- Start — create an Observable that emits the return value of a function
- Timer — create an Observable that emits a single item after a given delay
一:Create的使用
可以通過create操作去創(chuàng)建一個(gè)被觀察者Observable。Create是最基本的創(chuàng)建Observable的操作符。創(chuàng)建一個(gè)Observable(被觀察者)最重要的就是要和合適的時(shí)機(jī)調(diào)用Subscriber(觀察者)的onNext/onComplete/onError方法。onNext就是發(fā)射處理好的數(shù)據(jù)給Subscriber; onComplete用來告訴Subscriber所有的數(shù)據(jù)都已發(fā)射完畢;onError是在發(fā)生錯誤的時(shí)候發(fā)射一個(gè)Throwable對象給Subscriber。需要注意的一點(diǎn)就是Observable必須調(diào)用所有的Subscriber的onComplete方法并且只能調(diào)用一次,出錯的時(shí)候調(diào)用onError方法也是一樣的,并且一旦調(diào)用后就不能調(diào)用Subscriber的任何其他方法了。
介紹完畢。
我再詳細(xì)介紹一下。
我們可以看到Observable.create(這里經(jīng)常會傳入一個(gè)OnSubscribe接口);
OnSubscribe接口又是什么呢?它繼承自Action1<Subscriber<? super T>這個(gè)接口,他的call(<Subscriber<? super T> subscriber)方法里只有一個(gè)參數(shù),它傳入了一個(gè)觀察者?。?!也就是說我們在使用create的時(shí)候,我們將觀察者包裹在了被觀察者內(nèi)部。并且在被觀察這訂閱觀察者的時(shí)候,我們通過一個(gè)hook對象將觀察者Subscriber的對象傳入到OnSubscribe.call(傳到這里面)。這樣就實(shí)現(xiàn)了由被觀察者在訂閱的時(shí)候調(diào)用觀察者的方法。并且在事件完成的時(shí)候自動取消訂閱。
Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
//將依次發(fā)射3次onNext ,之后發(fā)射onComplete(發(fā)射完這個(gè)將不會再有輸出,同時(shí)自動取消注冊)
subscriber.onNext("Hello第一次發(fā)射");
subscriber.onNext("World第二次發(fā)射");
subscriber.onNext("Over 第三次發(fā)射");
subscriber.onCompleted();
subscriber.onError(new Throwable());
}
})
2.Just、From的使用
為了簡化創(chuàng)建操作,我們平時(shí)最常使用的一般是這兩個(gè)操作符。just(T...):from(T[])。
Just操作符將某個(gè)對象轉(zhuǎn)化為Observable對象,并且將其發(fā)射出去,可以使一個(gè)數(shù)字、一個(gè)字符串、數(shù)組、Iterate對象等。其實(shí)當(dāng)just傳入一個(gè)參數(shù)的時(shí)候,就是調(diào)用create()方法,傳入多參數(shù)的時(shí)候,就是調(diào)用from()方法。從而做到將參數(shù)依次發(fā)射出去
From操作符用來將某個(gè)對象轉(zhuǎn)化為Observable對象,并且依次將其內(nèi)容發(fā)射出去。這個(gè)類似于just,但是just會將這個(gè)對象整個(gè)發(fā)射出去。比如說一個(gè)含有10個(gè)數(shù)字的數(shù)組,使用from就會發(fā)射10次,每次發(fā)射一個(gè)數(shù)字,而使用just會發(fā)射一次來將整個(gè)的數(shù)組發(fā)射出去。
3.Defer
Defer操作符只有當(dāng)有Subscriber來訂閱的時(shí)候才會創(chuàng)建一個(gè)新的Observable對象,也就是說每次訂閱都會得到一個(gè)剛創(chuàng)建的最新的Observable對象,這可以確保Observable對象里的數(shù)據(jù)是最新的
4.Interval
Interval所創(chuàng)建的Observable對象會從0開始,每隔固定的時(shí)間發(fā)射一個(gè)數(shù)字。需要注意的是這個(gè)對象是運(yùn)行在computation Scheduler,基本上和時(shí)間有關(guān)系的都是運(yùn)行在computation Scheduler 中,所以如果需要在view中顯示結(jié)果,要在主線程中訂閱。
5.Range
這個(gè)應(yīng)該好理解一點(diǎn),看圖依次發(fā)射區(qū)間(n,n+m-1)里面的數(shù)字,左閉右開
6.Repeat、Timer
Repeat會將一個(gè)Observable對象重復(fù)發(fā)射,我們可以指定其發(fā)射的次數(shù)
Timer會在指定時(shí)間后發(fā)射一個(gè)數(shù)字0,注意其也是運(yùn)行在computation Scheduler
總覽:
Transforming Observables
Operators that transform items that are emitted by an Observable.
- Buffer — ++periodically(定期的)++ gather items from an Observable into bundles and emit these bundles rather than emitting the items ++one at a time(一次一個(gè))++
- FlatMap — transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
- GroupBy — divide an Observable into a set of Observables that each emit a different group of items from the original Observable, organized by key
- Map— transform the items emitted by an Observable by applying a function to each item
- Scan — apply a function to each item emitted by an Observable, sequentially, and emit each successive value
- Window — periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time
RxJava 提供了對事件序列進(jìn)行變換的支持,這是它的核心功能之一,也是大多數(shù)人說『RxJava 真是太好用了』的最大原因。所謂變換,就是將事件序列中的對象或整個(gè)序列進(jìn)行加工處理,轉(zhuǎn)換成不同的事件或事件序列。
1.Buffer
Buffer操作符所要做的事情就是將數(shù)據(jù)按照規(guī)定的大小、或者時(shí)間做一下緩存,然后將緩存的數(shù)據(jù)作為一個(gè)集合發(fā)射出去。
在上圖中,按照規(guī)定的大小(count = 3)來收集數(shù)據(jù),之后將他們整合成一個(gè)集合發(fā)射出去。
在上圖中,加入了一個(gè)skip參數(shù)用來指定每次發(fā)射一個(gè)集合需要跳過幾個(gè)數(shù)據(jù),圖中指定(count = 2,skip = 3),就會每3個(gè)數(shù)據(jù)發(fā)射一個(gè)包含兩個(gè)數(shù)據(jù)的集合,如果count==skip的話,我們就會發(fā)現(xiàn)其等效于第一種情況了。這里還是很好理解的。
2.flatMap & concatMap
Flatmap是一個(gè)用處特別多的操作符。它可以將數(shù)據(jù)根據(jù)你想要的規(guī)則進(jìn)行轉(zhuǎn)化后再發(fā)射出去。其原理就是將這個(gè)Observable轉(zhuǎn)化為多個(gè)以原Observable發(fā)射的數(shù)據(jù)作為源數(shù)據(jù)的Observable,然后再將這多個(gè)Observable發(fā)射的數(shù)據(jù)整合發(fā)射出來,需要注意的是最后的順序可能會交錯地發(fā)射出來,如果對順序有嚴(yán)格的要求的話可以使用concatmap操作符。
如上圖,我們按照一個(gè)圓對應(yīng)兩個(gè)菱形的規(guī)則,將一個(gè)Observable<圓>轉(zhuǎn)換成兩個(gè)Observable<菱形>。并將兩個(gè)Observable<菱形>發(fā)射出去。
3.Map
Map操作符的功能類似于FlatMap,不同之處在于它對數(shù)據(jù)的轉(zhuǎn)化是直接進(jìn)行的,而FlatMap需要通過一些中間的Observables來進(jìn)行。
4.GroupBy
GroupBy操作符將原始Observable發(fā)射的數(shù)據(jù)按照key來拆分成一些小的Observable,然后這些小的Observable分別發(fā)射其所包含的的數(shù)據(jù),類似于sql里面的groupBy。在使用中,我們需要提供一個(gè)生成key的規(guī)則,所有key相同的數(shù)據(jù)會包含在同一個(gè)小的Observable種。
5.Scan
Scan操作符對一個(gè)序列的數(shù)據(jù)應(yīng)用一個(gè)函數(shù),并將這個(gè)函數(shù)的結(jié)果發(fā)射出去作為下個(gè)數(shù)據(jù)應(yīng)用這個(gè)函數(shù)時(shí)候的第一個(gè)參數(shù)使用,有點(diǎn)類似于遞歸操作
總覽:過濾
Filtering Observables
Operators that selectively emit items from a source Observable.
- Debounce — only emit an item from an Observable if a particular timespan has passed without it emitting another item
- Distinct — suppress duplicate items emitted by an Observable
- ElementAt — emit only item n emitted by an Observable
- Filter — emit only those items from an Observable that pass a predicate test
- First — emit only the first item, or the first item that meets a condition, from an Observable
- IgnoreElements — do not emit any items from an Observable but mirror its termination notification
- Last — emit only the last item emitted by an Observable
- Sample — emit the most recent item emitted by an Observable within periodic time intervals
- Skip — suppress the first n items emitted by an Observable
- SkipLast — suppress the last n items emitted by an Observable
- Take — emit only the first n items emitted by an Observable
- TakeLast — emit only the last n items emitted by an Observable
1.Filter
Filter只會返回滿足過濾條件的數(shù)據(jù).比如說,我們想讓數(shù)據(jù){a1,a2,b1,v1,c1}中以v開頭的數(shù)據(jù)排除掉,或者我們經(jīng)常用到的AppInfo中數(shù)據(jù)為null的過濾掉,我們可以使用filter輕松實(shí)現(xiàn)。
如上圖,我們應(yīng)該filter(東西 == 圓)
2.take/takelast
當(dāng)我們不需要整個(gè)序列時(shí),而是只想取開頭或結(jié)尾的幾個(gè)元素,我們可以用take()或takeLast()。
```
//太簡單了,我就不寫demo了,take(3)從前面取3個(gè),takeLast(3)從后面取3個(gè)
Observable.from(apps).take(3)
```
3.Distinct 有且僅有一次
我們可以對我們的序列使用distinct()函數(shù)去掉重復(fù)的。就像takeLast()一樣,distinct()作用于一個(gè)完整的序列,然后得到重復(fù)的過濾項(xiàng),它需要記錄每一個(gè)發(fā)射的值。如果你在處理一大堆序列或者大的數(shù)據(jù)記得關(guān)注內(nèi)存使用情況。
Distinct操作符的用處就是用來去重,非常好理解。如下圖所示,所有重復(fù)的數(shù)據(jù)都會被過濾掉。還有一個(gè)操作符distinctUntilChanged,是用來過濾掉連續(xù)的重復(fù)數(shù)據(jù)。
4.First and last
first()方法和last()方法很容易弄明白。它們從Observable中只發(fā)射第一個(gè)元素或者最后一個(gè)元素。連圖我都不上了。
firstOrDefault()和lastOrDefault().這兩個(gè)函數(shù)當(dāng)可觀測序列完成時(shí)不再發(fā)射任何值時(shí)用得上。在這種場景下,如果Observable不再發(fā)射任何值時(shí)我們可以指定發(fā)射一個(gè)默認(rèn)的值
5.Skip and SkipLast
skip()和skipLast()函數(shù)與take()和takeLast()相對應(yīng)。它們用整數(shù)N作參數(shù),從本質(zhì)上來說,它們不讓Observable發(fā)射前N個(gè)或者后N個(gè)值。如果我們知道一個(gè)序列以沒有太多用的“可控”元素開頭或結(jié)尾時(shí)我們可以使用它。

6.ElementAt
如果我們只想要可觀測序列發(fā)射的第五個(gè)元素該怎么辦?elementAt()函數(shù)僅從一個(gè)序列中發(fā)射第n個(gè)元素然后就完成了。+
如果我們想查找第五個(gè)元素但是可觀測序列只有三個(gè)元素可供發(fā)射時(shí)該怎么辦?我們可以使用elementAtOrDefault()。下圖展示了如何通過使用elementAt(2)從一個(gè)序列中選擇第三個(gè)元素以及如何創(chuàng)建一個(gè)只發(fā)射指定元素的新的Observable。

7.Sample
在Observable后面加一個(gè)sample(),我們將創(chuàng)建一個(gè)新的可觀測序列,它將在一個(gè)指定的時(shí)間間隔里由Observable發(fā)射最近一次的數(shù)值:
如果我們想讓它定時(shí)發(fā)射第一個(gè)元素而不是最近的一個(gè)元素,我們可以使用throttleFirst()。

8.Debounce
debounce()函數(shù)過濾掉由Observable發(fā)射的速率過快的數(shù)據(jù);如果在一個(gè)指定的時(shí)間間隔過去了仍舊沒有發(fā)射一個(gè),那么它將發(fā)射最后的那個(gè)。
下圖展示了多久從Observable發(fā)射一次新的數(shù)據(jù),debounce()函數(shù)開啟一個(gè)內(nèi)部定時(shí)器,如果在這個(gè)時(shí)間間隔內(nèi)沒有新的數(shù)據(jù)發(fā)射,則新的Observable發(fā)射出最后一個(gè)數(shù)據(jù):
Combining Observables
1.Zip
Zip操作符將多個(gè)Observable發(fā)射的數(shù)據(jù)按順序組合起來,每個(gè)數(shù)據(jù)只能組合一次,而且都是有序的。最終組合的數(shù)據(jù)的數(shù)量由發(fā)射數(shù)據(jù)最少的Observable來決定。
2.Merege
Merge操作符將多個(gè)Observable發(fā)射的數(shù)據(jù)整合起來發(fā)射,就如同是一個(gè)Observable發(fā)射的數(shù)據(jù)一樣。但是其發(fā)射的數(shù)據(jù)有可能是交錯的,如果想要沒有交錯,可以使用concat操作符。當(dāng)某一個(gè)Observable發(fā)出onError的時(shí)候,merge的過程會被停止并將錯誤分發(fā)給Subscriber,如果不想讓錯誤終止merge的過程,可以使用MeregeDelayError操作符,會將錯誤在merge結(jié)束后再分發(fā)。
1.使用Scheduler進(jìn)行線程的切換
在不指定線程的情況下, RxJava 遵循的是線程不變的原則,即:在哪個(gè)線程調(diào)用訂閱方法 subscribe(),就在哪個(gè)線程生產(chǎn)事件;在哪個(gè)線程生產(chǎn)事件,就在哪個(gè)線程消費(fèi)事件。如果需要切換線程,就需要用到 Scheduler (調(diào)度器)。
幾個(gè)常用的Api:
- Schedulers.immediate(): 直接在當(dāng)前線程運(yùn)行,相當(dāng)于不指定線程。這是默認(rèn)的 Scheduler。
- Schedulers.newThread(): 總是啟用新線程,并在新線程執(zhí)行操作。
- Schedulers.io(): I/O 操作(讀寫文件、讀寫數(shù)據(jù)庫、網(wǎng)絡(luò)信息交互等)所使用的 Scheduler。行為模式和 newThread() 差不多,區(qū)別在于 io() 的內(nèi)部實(shí)現(xiàn)是是用一個(gè)無數(shù)量上限的線程池,可以重用空閑的線程,因此多數(shù)情況下 io() 比 newThread() 更有效率。不要把計(jì)算工作放在 io() 中,可以避免創(chuàng)建不必要的線程。
- Schedulers.computation(): 計(jì)算所使用的 Scheduler。這個(gè)計(jì)算指的是 CPU 密集型計(jì)算,即不會被 I/O 等操作限制性能的操作,例如圖形的計(jì)算。這個(gè) Scheduler 使用的固定的線程池,大小為 CPU 核數(shù)。不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時(shí)間會浪費(fèi) CPU。
- Android 還有一個(gè)專用的 AndroidSchedulers.mainThread(),它指定的操作將在 Android 主線程運(yùn)行。
有了這幾個(gè) Scheduler ,就可以使用 subscribeOn() 和 observeOn() 兩個(gè)方法來對線程進(jìn)行控制了。
subscribeOn(): 指定 subscribe() 所發(fā)生的線程,即 Observable.OnSubscribe 被激活時(shí)所處的線程?;蛘呓凶鍪录a(chǎn)生的線程。
observeOn(): 指定 Subscriber 所運(yùn)行在的線程?;蛘呓凶鍪录M(fèi)的線程。
精簡代碼:(教簡單就不放全部代碼了)
```
mSubscription = getObservable()//創(chuàng)建一個(gè)被觀察者
.subscribeOn(Schedulers.io())//指定訂閱事件發(fā)生的線程
.doOnSubscribe(new Action0() {//事件發(fā)生之前做什么事
//onStart() 也可以用作流程開始前的初始化。但是 onStart() 由于在 subscribe() 發(fā)生時(shí)就被調(diào)用了,因此不能指定線程,而是只能執(zhí)行在 subscribe() 被調(diào)用時(shí)的線程。
//doOnSubscribe同樣是在 subscribe() 調(diào)用后而且在事件發(fā)送前執(zhí)行,但區(qū)別在于它可以指定線程。
//默認(rèn)情況下, doOnSubscribe() 執(zhí)行在 subscribe() 發(fā)生的線程;而如果在 doOnSubscribe() 之后有 subscribeOn() 的話,它將執(zhí)行在離它最近的 subscribeOn() 所指定的線程。
@Override
public void call() {
mProgressBar.setVisibility(View.VISIBLE);
_log("按鈕被點(diǎn)擊了");
}
})
.observeOn(AndroidSchedulers.mainThread())//指定事件消費(fèi)在那個(gè)線程里面
.subscribe(getSubscriber());//訂閱觀察者
```
### 2.使用Buffer收集數(shù)據(jù)流后在發(fā)射
Buffer顧名思義,就相當(dāng)一個(gè)緩沖區(qū),我們可以指定一個(gè)時(shí)間或者數(shù)量來收集這些數(shù)據(jù),滿足了這個(gè)事件或者數(shù)量之后,在統(tǒng)一發(fā)射出去。在這里我們舉個(gè)例子,來學(xué)習(xí)Buffer,同時(shí)更深入的理解一個(gè)更重要的操作符map
//觀察在兩秒的時(shí)間內(nèi),按鈕被點(diǎn)擊的次數(shù)
public Subscription getClickNumInTime(){
//這里需要引入compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'依賴,這樣我們可以觀測操作這些控件的數(shù)據(jù)流
return RxView
//將點(diǎn)擊事件包裝成數(shù)據(jù)(創(chuàng)建一個(gè)可觀測的序列)流發(fā)射出去
.clicks(tapButton)
//這里我們可以很清楚,很明了的看到,我們將點(diǎn)擊事件map成了Integer類型的數(shù)據(jù)
.map(new Func1<Void, Integer>() {
@Override
public Integer call(Void aVoid) {
_log("得到了一個(gè)點(diǎn)擊");
return 1;
}
})
//通過Buffer指定2秒鐘發(fā)射一次收集的數(shù)據(jù)
.buffer(2, TimeUnit.SECONDS)
//Buffer指定的線程是computation線程,我們修改UI要改變線程
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Integer>>() {
@Override
public void onCompleted() {
_log("你永遠(yuǎn)不會走到這里,我不信這句話能打印處來");
}
@Override
public void onError(Throwable e) {
_log("出錯了"+e.getMessage());
}
//一旦訂閱之后,除非取消訂閱,或者徹底殺死進(jìn)程,否則會一直走下去,造成內(nèi)存泄露
@Override
public void onNext(List<Integer> integers) {
if(integers.size() > 0)
_log("你在兩秒內(nèi)一共點(diǎn)擊了:"+integers.size()+"次");
}
});
}
### 3.使用Debounce做TextSearch
比如說當(dāng)我們輸入的內(nèi)容發(fā)生改變的時(shí)候,我們需要實(shí)時(shí)的去異步請求網(wǎng)絡(luò)/數(shù)據(jù)庫來提示用戶某樣?xùn)|西也需要改變。就比如,當(dāng)我輸入金額的時(shí)候,沒當(dāng)我EditText內(nèi)容發(fā)生改變的時(shí)候我去請求服務(wù)器,將得到的京金幣展示在桌面上。有些時(shí)候,用戶連續(xù)幾個(gè)數(shù)字輸入的過快,沒必要去請求網(wǎng)絡(luò),這個(gè)時(shí)候,我們可以使用Debounce來做限流操作,一定時(shí)間內(nèi),我們只要最后一次得到的數(shù)據(jù)。
```
mSubscription = RxTextView
//將EditText的內(nèi)容改變事件轉(zhuǎn)換成一組可觀測的序列(數(shù)據(jù)流)
.textChangeEvents(inputTxtDebounce)
//開啟限流操作,在400毫秒內(nèi),我們只要最后一次改變的結(jié)果
.debounce(400, TimeUnit.MILLISECONDS)
//過濾掉空字符串,只有滿足條件的能留下
.filter(new Func1<TextViewTextChangeEvent, Boolean>() {
@Override
public Boolean call(TextViewTextChangeEvent textViewTextChangeEvent) {
return !inputTxtDebounce.getText().toString().trim().isEmpty();
}
})
//和Buffer一樣,生產(chǎn)事件運(yùn)行在computation線程
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getSubscriber());
```
很多時(shí)候我們都需要給訂閱事件來一個(gè)返回值,來方便我們在離開頁面的時(shí)候取消訂閱。
### 4.Retrofit+RxJava
目前最火的網(wǎng)絡(luò)請求就是Retrofit了,而目前最火的框架,二者也有一合之地,而二者的無縫結(jié)合,完全使我們的網(wǎng)絡(luò)請求隨心所欲。
Retrofit我就不多做介紹了。
```
//先來看一個(gè)最簡單的。
//首先我們用一個(gè)裝訂閱set集合,將這一個(gè)網(wǎng)絡(luò)請求的訂閱裝進(jìn)來
mSubscriptions.add(
//通過Retrofit創(chuàng)建網(wǎng)絡(luò)請求
mGithubApi
.contributors(_username.getText().toString(),_repo.getText().toString())
//指定網(wǎng)絡(luò)請求(生產(chǎn)事件)運(yùn)行的線程
.subscribeOn(Schedulers.io())
//指定響應(yīng)的線程(消費(fèi)事件)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Contributor>>() {
@Override
public void onCompleted() {
_adapter.add("完畢");
}
@Override
public void onError(Throwable e) {
_adapter.add("加載失敗"+e.getMessage());
}
@Override
public void onNext(List<Contributor> contributors) {
//網(wǎng)絡(luò)請求得到了一組集合,我們對這一組集合遍歷拿到我們想要的數(shù)據(jù)
for (int i = 0; i < contributors.size(); i++) {
_adapter.add(format("%s 為 %s 庫做出了 %d 個(gè)貢獻(xiàn)",contributors.get(i).login,_repo.getText().toString(),contributors.get(i).contributions));
}
}
})
);
```
如果!我在拿到List<Contributor>響應(yīng)數(shù)據(jù)的時(shí)候,我要根據(jù)里面的作者,去搜索每一個(gè)作者的詳細(xì)信息,怎么辦?我想一想就覺得頭疼,先把這些作者收集起來,遍歷,再去挨個(gè)請求,再將得到的數(shù)據(jù)和剛才的數(shù)據(jù)整合起來,顯示到桌面?
不,RxJava的優(yōu)點(diǎn)就是,你的需求越復(fù)雜,我的邏輯越簡單?。。。。。?
```
mSubscriptions.add(
//先創(chuàng)建網(wǎng)絡(luò)請求
mGithubApi.contributors(_username.getText().toString(),_repo.getText().toString())
//我們先將得到的數(shù)據(jù)<List<Contributor>,遍歷發(fā)射并觀測其中的每一個(gè)元素
.flatMap(new Func1<List<Contributor>, Observable<Contributor>>() {
@Override
public Observable<Contributor> call(List<Contributor> contributors) {
return Observable.from(contributors);
}
})
//這里,重點(diǎn)來了,先說一下Pair,他就是一個(gè)包含了兩個(gè)對象的容器。在這里我們用它來裝User和Contributor
.flatMap(new Func1<Contributor, Observable<Pair<User,Contributor>>>() {
@Override
public Observable<Pair<User,Contributor>> call(Contributor contributor) {
//在這里,我們在根據(jù)longin參數(shù)再去請求user的詳細(xì)信息
Observable<User> userObservable = mGithubApi.user(contributor.login)
//過濾掉非空數(shù)據(jù)
.filter(new Func1<User, Boolean>() {
@Override
public Boolean call(User user) {
return !isEmpty(user.name) && !isEmpty(user.email);
}
});
//到了整合的時(shí)候了。我們將user的詳細(xì)信息的數(shù)據(jù)流,同時(shí)將contributor這個(gè)值再次包裝成數(shù)據(jù)流發(fā)整合之后
//怎么整合呢,這時(shí)候就要看Func2這個(gè)函數(shù)了
return Observable.zip(userObservable, Observable.just(contributor), new Func2<User, Contributor, Pair<User, Contributor>>() {
@Override
public Pair<User, Contributor> call(User user, Contributor contributor) {
//它將我們的user用戶信息和contributor整合在Pair容器中返回回去
return new Pair<User, Contributor>(user,contributor);
}
});
}
})
//下面的不用多說了,我只想說,這個(gè)代碼寫的真的很精彩
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Pair<User, Contributor>>() {
@Override
public void onCompleted() {
_adapter.add("完畢");
}
@Override
public void onError(Throwable e) {
_adapter.add("加載失敗");
}
@Override
public void onNext(Pair<User, Contributor> pair) {
User user = pair.first;
Contributor contributor = pair.second;
_adapter.add(format("%s(%s) 為 %s 庫做出了 %d 個(gè)貢獻(xiàn)",
user.name,
user.email,
_repo.getText().toString()
,contributor.contributions));
_adapter.notifyDataSetChanged();
}
})
);
5.使用combineLatest做登錄注冊模塊
CombineLatest操作符可以將2~9個(gè)Observable發(fā)射的數(shù)據(jù)組裝起來然后再發(fā)射出來。不過還有兩個(gè)前提:
1.所有的Observable都發(fā)射過數(shù)據(jù)。(用戶操作APP可以感覺到,只有當(dāng)我三個(gè)EditText全部輸入過內(nèi)容,才會觸發(fā)combineLatest的發(fā)射事件)
2.滿足條件1的時(shí)候任何一個(gè)Observable發(fā)射一個(gè)數(shù)據(jù),就將所有Observable最新發(fā)射的數(shù)據(jù)按照提供的函數(shù)組裝起來發(fā)射出去。(每當(dāng)我任何一個(gè)EditText發(fā)生改變的時(shí)候都會重新走一遍combineLatest的發(fā)射事件)
這里我們看著例子,來解釋這些話語
這個(gè)也是挺常用的,他可以將我們需要的數(shù)據(jù)流整合在一起。比如:
注冊的時(shí)候所有輸入信息(郵箱、密碼、電話號碼等)合法才點(diǎn)亮注冊按鈕。
```
//這里我們分別將3個(gè)EditText的內(nèi)容變化的事件轉(zhuǎn)換成數(shù)據(jù)流
_emailChangeObservable = RxTextView.textChanges(_email).skip(1);
_passwordChangeObservable = RxTextView.textChanges(_password).skip(1);
_numberChangeObservable = RxTextView.textChanges(_number).skip(1);
private void _combineLatestEvents() {
//我們將三個(gè)數(shù)據(jù)流整合起來,并加入整合的規(guī)則(這里可以將規(guī)則看成返回值Boolean,滿足規(guī)則我返回True,否則返回false)
_subscription = Observable.combineLatest(_emailChangeObservable,
_passwordChangeObservable, _numberChangeObservable, new Func3<CharSequence, CharSequence, CharSequence, Boolean>() {
@Override
public Boolean call(CharSequence email, CharSequence psw, CharSequence num) {
boolean emailB = "123456".equals(email.toString());
if(!emailB){
_email.setError("必須是123456");
}
boolean pswB = "654321".equals(psw.toString());
if(!pswB){
_password.setError("必須是654321");
}
boolean numB = "521".equals(num.toString());
if(!numB){
_number.setError("必須是521");
}
return emailB && pswB && numB;
}
})
.subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Boolean aBoolean) {
if (aBoolean) {
_btnValidIndicator.setBackgroundColor(Color.BLUE);
} else {
_btnValidIndicator.setBackgroundColor(Color.parseColor("#888888"));
}
}
});
}
### 6.使用timer做定時(shí)操作。當(dāng)有“x秒后執(zhí)行y操作”類似的需求的時(shí)候想到使用timer
我們一般做APP會用到定時(shí)器,當(dāng)用到定時(shí)器的。比如說再按一次退出程序,我們可以這么做。我們通過Timer計(jì)時(shí),如果退出了的話會造成不到兩秒的內(nèi)存泄露,當(dāng)然也可以先取消訂閱在finish()頁面。這里使用Timer來記錄用戶點(diǎn)擊的次數(shù),如果時(shí)間不足兩秒,復(fù)原count
` int count = 1 ;
onBackPressed(){
if(count != 2){
Toast.makeText(this, "再按一次退出應(yīng)用", Toast.LENGTH_SHORT).show();
}else{ finish();
}
count = 2;
Observable.timer(2, TimeUnit.SECONDS)
.subscribe(new Action1<Long>() {
@Override
public void call(Long aLong) {
count = 1;
}
});
}`
### 7.使用interval做周期性操作。當(dāng)有“每隔xx秒后執(zhí)行yy操作”類似的需求的時(shí)候,想到使用interval.
這也是我們經(jīng)常用到的,就比如首頁的輪播圖就可以通過Interval來實(shí)現(xiàn),我們每3秒讓ViewPager當(dāng)前顯示的頁面+1即可。具體代碼可以見我的另一篇簡書。
### 8.防抖點(diǎn)擊
這里我使用的Fragmention也做了防抖點(diǎn)擊,而且思想特別好,簡單。他的做法是當(dāng)我開啟一個(gè)Fragmention的時(shí)候,我禁用掉Activity全屏的觸摸事件,在開啟之后在恢復(fù)。
這里呢,我們使用RxJava的限流操作符,在1秒內(nèi)只允許流出來一個(gè)點(diǎn)擊事件。
```
RxView.clicks(button)
.throttleFirst(1, TimeUnit.SECONDS)
.subscribe(new Observer<Object>() {
@Override
public void onCompleted() {
log.d ("completed");
}
@Override
public void onError(Throwable e) {
log.e("error");
}
@Override
public void onNext(Object o) {
log.d("button clicked");
}
});
```
RxJava的使用場景很多,遇到自己沒見到過得操作符,可以看看圖片和文檔介紹,很詳細(xì),加油。
最后感謝awesome-Rxjava;你想要就的都在這里=>https://github.com/lzyzsd/Awesome-RxJava
附上本文的Demo:https://github.com/SshiGguang/Kits