- 原文出自《RxJava Essentials》
- 原文做者 : Ivan Morgillo
- 譯文出自 : 開發技術前線 www.devtf.cn
- 轉載聲明: 本譯文已受權開發者頭條享有獨家轉載權,未經容許,不得轉載!
- 譯者 : yuxingxin
- 項目地址 : RxJava-Essentials-CN
在上一章中,咱們學習了使用RxJava建立一個Android工程以及如何建立一個可觀測的列表來填充RecyclerView。咱們如今知道了如何從頭、從列表、從一個已存在的傳統Java函數來建立Observable。git
這一章中,咱們將研究可觀測序列的本質:過濾。咱們將學到如何從發射的Observable中選取咱們想要的值,如何獲取有限個數的值,如何處理溢出的場景,以及更多的有用的技巧。github
RxJava讓咱們使用filter()
方法來過濾咱們觀測序列中不想要的值,在上一章中,咱們在幾個例子中使用了已安裝的應用列表,可是咱們只想展現以字母C
開頭的已安裝的應用該怎麼辦呢?在這個新的例子中,咱們將使用一樣的列表,可是咱們會過濾它,經過把合適的謂詞傳給filter()
函數來獲得咱們想要的值。app
上一章中loadList()
函數能夠改爲這樣:eclipse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
private void loadList(List<AppInfo> apps) {
mRecyclerView.setVisibility(View.VISIBLE);
Observable.from(apps)
.filter((appInfo) ->
appInfo.getName().startsWith("C"))
.subscribe(new Observable<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
}
});
}
|
咱們從上一章中的loadList()
函數中添加下面一行:ide
1
2
|
.fliter((appInfo -> appInfo.getName().startsWith("C"))
|
建立Observable完之後,咱們從發出的每一個元素中過濾掉開頭字母不是C的。爲了讓這裏更清楚一些,咱們用Java 7的語法來實現:函數
1
2
3
4
5
6
7
|
.filter(new Func1<AppInfo,Boolean>(){
@Override
public Boolean call(AppInfo appInfo){
return appInfo.getName().startsWith("C");
}
})
|
咱們傳一個新的Func1
對象給filter()
函數,即只有一個參數的函數。Func1
有一個AppInfo
對象來做爲它的參數類型而且返回Boolean
對象。只要條件符合filter()
函數就會返回true
。此時,值會發射出去而且全部的觀察者都會接收到。學習
正如你想的那樣,從一個咱們獲得的可觀測序列中建立一個咱們須要的序列filter()
是很好用的。咱們不須要知道可觀測序列的源或者爲何發射這麼多不一樣的數據。咱們只是想要這些元素的子集來建立一個能夠在應用中使用的新序列。這種思想促進了咱們編碼中的分離性與抽象性。編碼
filter()
函數最經常使用的用法之一時過濾null
對象:url
1
2
3
4
5
6
7
|
.filter(new Func1<AppInfo,Boolean>(){
@Override
public Boolean call(AppInfo appInfo){
return appInfo != null;
}
})
|
這看起來簡單,對於簡單的事情有許多模板代碼,可是它幫咱們免去了在onNext()
函數調用中再去檢測null
值,讓咱們把注意力集中在應用業務邏輯上。spa
下圖展現了過濾出的C字母開頭的已安裝的應用列表。
當咱們不須要整個序列時,而是隻想取開頭或結尾的幾個元素,咱們能夠用take()
或takeLast()
。
若是咱們只想要一個可觀測序列中的前三個元素那將會怎麼樣,發射它們,而後讓Observable完成嗎?take()
函數用整數N來做爲一個參數,從原始的序列中發射前N個元素,而後完成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
private void loadList(List<AppInfo> apps) {
mRecyclerView.setVisibility(View.VISIBLE);
Observable.from(apps)
.take(3)
.subscribe(new Observable<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
}
});
}
|
下圖中展現了發射數字的一個可觀測序列。咱們對這個可觀測序列應用take(2)
函數,而後咱們建立一個只發射可觀測源的第一個和第二個數據的新序列。
若是咱們想要最後N個元素,咱們只需使用takeLast()
函數:
1
2
3
4
|
Observable.from(apps)
.takeLast(3)
.subscribe(https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.);
|
正如聽起來那樣不值一提,重點注意takeLast()
函數因爲用一組有限的發射數的本質使得它僅可用於完成的序列。
下圖中展現瞭如何從可觀測源中發射最後一個元素來建立一個新的序列:
下圖中展現了咱們在已安裝的應用列表使用take()
和takeLast()
函數後發生的結果:
一個可觀測序列會在出錯時重複發射或者被設計成重複發射。distinct()
和distinctUntilChanged()
函數能夠方便的讓咱們處理這種重複問題。
若是咱們想對一個指定的值僅處理一次該怎麼辦?咱們能夠對咱們的序列使用distinct()
函數去掉重複的。就像takeLast()
同樣,distinct()
做用於一個完整的序列,而後獲得重複的過濾項,它須要記錄每個發射的值。若是你在處理一大堆序列或者大的數據記得關注內存使用狀況。
下圖展現瞭如何在一個發射1和2兩次的可觀測源上建立一個無重的序列:
爲了建立咱們例子中序列,咱們將使用咱們至今已經學到的幾個方法:
* take()
:它有一小組的可識別的數據項。
* repeat()
:建立一個有重複的大的序列。
而後,咱們將應用distinct()
函數來去除重複。
咱們用程序實現一個重複的序列,而後過濾出它們。這聽起來時難以想象的,可是爲了實現這個例子來使用咱們至今爲止已學習到的東西則是個不錯的練習。
1
2
3
4
|
Observable<AppInfo> fullOfDuplicates = Observable.from(apps)
.take(3)
.repeat(3);
|
fullOfDuplicates
變量裏把咱們已安裝應用的前三個重複了3次:有9個而且許多重複的。而後,咱們使用distinct()
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
fullOfDuplicates.distinct()
.subscribe(new Observable<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
}
});
}
|
結果,很明顯,咱們獲得:
若是在一個可觀測序列發射一個不一樣於以前的一個新值時讓咱們獲得通知這時候該怎麼作?咱們猜測一下咱們觀測的溫度傳感器,每秒發射的室內溫度:
1
2
|
21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.22°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.
|
每次咱們得到一個新值,咱們都會更新當前正在顯示的溫度。咱們出於系統資源保護並不想在每次值同樣時更新數據。咱們想忽略掉重複的值而且在溫度確實改變時纔想獲得通知。ditinctUntilChanged()
過濾函數能作到這一點。它能輕易的忽略掉全部的重複而且只發射出新的值。
下圖用圖形化的方式展現了咱們如何將distinctUntilChanged()
函數應用在一個存在的序列上來建立一個新的不重複發射元素的序列。
下圖展現瞭如何從一個從可觀測源序列中建立只發射第一個元素的序列。
first()
方法和last()
方法很容易弄明白。它們從Observable中只發射第一個元素或者最後一個元素。這兩個均可以傳Func1
做爲參數,:一個能夠肯定咱們感興趣的第一個或者最後一個的謂詞:
下圖展現了last()
應用在一個完成的序列上來建立一個僅僅發射最後一個元素的新的Observable。
與first()
和last()
類似的變量有:firstOrDefault()
和lastOrDefault()
.這兩個函數當可觀測序列完成時再也不發射任何值時用得上。在這種場景下,若是Observable再也不發射任何值時咱們能夠指定發射一個默認的值
下圖中展現瞭如何使用skip(2)
來建立一個不發射前兩個元素而是發射它後面的那些數據的序列。
skip()
和skipLast()
函數與take()
和takeLast()
相對應。它們用整數N做參數,從本質上來講,它們不讓Observable發射前N個或者後N個值。若是咱們知道一個序列以沒有太多用的「可控」元素開頭或結尾時咱們可使用它。
下圖與前一個場景相對應:咱們建立一個新的序列,它會跳事後面兩個元素從源序列中發射剩下的其餘元素。
若是咱們只想要可觀測序列發射的第五個元素該怎麼辦?elementAt()
函數僅從一個序列中發射第n個元素而後就完成了。
若是咱們想查找第五個元素可是可觀測序列只有三個元素可供發射時該怎麼辦?咱們可使用elementAtOrDefault()
。下圖展現瞭如何經過使用elementAt(2)
從一個序列中選擇第三個元素以及如何建立一個只發射指定元素的新的Observable。
讓咱們再回到那個溫度傳感器。它每秒都會發射當前室內的溫度。說實話,咱們並不認爲溫度會變化這麼快,咱們可使用一個小的發射間隔。在Observable後面加一個sample()
,咱們將建立一個新的可觀測序列,它將在一個指定的時間間隔裏由Observable發射最近一次的數值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
Observable<Integer> sensor = [...]
sensor.sample(30,TimeUnit.SECONDS)
.subscribe(new Observable<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer currentTemperature) {
updateDisplay(currentTemperature)
}
});
|
例子中Observable將會觀測溫度Observable而後每隔30秒就會發射最後一個溫度值。很明顯,sample()
支持所有的時間單位:秒,毫秒,天,分等等。
下圖中展現了一個間隔發射字母的Observable如何採樣一個發射數字的Observable。Observable的結果將會發射每一個已發射字母的最後一組數據:1,4,5.
若是咱們想讓它定時發射第一個元素而不是最近的一個元素,咱們可使用throttleFirst()
。
假設咱們工做的是一個時效性的環境,咱們溫度傳感器每秒都在發射一個溫度值。咱們想讓它每隔兩秒至少發射一個,咱們可使用timeout()
函數來監聽源可觀測序列,就是在咱們設定的時間間隔內若是沒有獲得一個值則發射一個錯誤。咱們能夠認爲timeout()
爲一個Observable的限時的副本。若是在指定的時間間隔內Observable不發射值的話,它監聽的原始的Observable時就會觸發onError()
函數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Subscription subscription = getCurrentTemperature()
.timeout(2,TimeUnit.SECONDS)
.subscribe(new Observable<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Log.d("RXJAVA","You should go check the sensor, dude");
}
@Override
public void onNext(Integer currentTemperature) {
updateDisplay(currentTemperature)
}
});
|
和sample()
同樣,timeout()
使用TimeUnit
對象來指定時間間隔。
下圖中展現了一旦Observable超過了限時就會觸發onError()
函數:由於超時後它纔到達,因此最後一個元素將不會發射出去。
debounce()
函數過濾掉由Observable發射的速率過快的數據;若是在一個指定的時間間隔過去了仍舊沒有發射一個,那麼它將發射最後的那個。
就像sample()
和timeout()
函數同樣,debounce()
使用TimeUnit
對象指定時間間隔。
下圖展現了多久從Observable發射一次新的數據,debounce()
函數開啓一個內部定時器,若是在這個時間間隔內沒有新的數據發射,則新的Observable發射出最後一個數據:
這一章中,咱們學習瞭如何過濾一個可觀測序列。咱們如今可使用filter()
,skip()
,和sample()
來建立咱們想要的Observable。
下一章中,咱們將學習如何轉換一個序列,將函數應用到每一個元素,給它們分組和掃描來建立咱們所須要的能完成目標的特定Observable。