RxJava從今以后你就會了

RxJava技術分享


京金所—時光

2016.9.22

這里我拿出來給 Android 開發者的 RxJava 詳解中的例子

假設有這樣一個需求:界面上有一個自定義的視圖 imageCollectorView ,它的作用是顯示多張圖片,并能使用 addImage(Bitmap) 方法來任意增加顯示的圖片。現在需要程序將一個給出的目錄數組 File[] folders 中每個目錄下的 png 圖片都加載出來并顯示在 imageCollectorView 中。需要注意的是,由于讀取圖片的這一過程較為耗時,需要放在后臺執行,而圖片的顯示則必須在 UI 線程執行。常用的實現方式有多種,我這里貼出其中一種:

`

//開啟一條子線程
new Thread() {
@Override
public void run() {
    super.run();
    //遍歷給出的目錄數組,獲取每一個目錄里面的文件的數組
    for (File folder : folders) {
        File[] files = folder.listFiles();
        //遍歷這個文件數組,篩選出來png圖片
        for (File file : files) {
            if (file.getName().endsWith(".png")) {
                //將路徑轉換成Bitmap圖片
                final Bitmap bitmap = getBitmapFromFile(file);
                //切換到主線程更新UI。
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        imageCollectorView.addImage(bitmap);
                    }
                });
            }
        }
    }
}

}.start();

`

而如果使用 RxJava ,實現方式是這樣的:

`
//將文件夾目錄創建成一個可觀測的序列

Observable.from(folders)

//遍歷目錄數組中的每一個文件對象,并將這每一個文件對象轉換成可觀測序列
.flatMap(new Func1<File, Observable<File>>() {
    @Override
    public Observable<File> call(File file) {
        return Observable.from(file.listFiles());
    }
})
//對這每一個文件對象進行過濾,選出png圖片
.filter(new Func1<File, Boolean>() {
    @Override
    public Boolean call(File file) {
        return file.getName().endsWith(".png");
    }
})
//將這每一個png文件都轉成Bitmap傳遞
.map(new Func1<File, Bitmap>() {
    @Override
    public Bitmap call(File file) {
        return getBitmapFromFile(file);
    }
})
//耗時操作運行在子線程
.subscribeOn(Schedulers.io())
//修改UI的操作放在主線程
.observeOn(AndroidSchedulers.mainThread())
//訂閱,展示
.subscribe(new Action1<Bitmap>() {
    @Override
    public void call(Bitmap bitmap) {
        imageCollectorView.addImage(bitmap);
    }
});

`

今天主要從以下幾個方面來著重介紹RxJava

  • RxJava的重要概念
  • RxJava的操作符
  • RxJava的應用場景

一:RxJava的重要概念

1.1 什么是RxJava?

RxJava 在 GitHub 主頁上的自我介紹是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一個在 Java VM 上使用可觀測的序列來組成異步的、基于事件的程序的庫)。

到底什么是RxJava,一個詞,就是異步。兩個詞就是異步+鏈式操作。

1.2 RxJava難嗎?

RxJava其實一點也不難。RxJava能做到的東西,別人也一樣可以做到。但是,如果使用RxJava,那么可以比他們邏輯更簡潔。有人說Rx太好用了,也有人說RxJava太難用了。好用是因為RxJava使用操作符(函數式編程)極大的簡化了我們代碼編寫的邏輯,難用可能就是對操作符用的還不習慣,對命令式編程的思想有些固話。

1.3 但是很多人說學習RxJava起點高啊?

高在哪里。我認為是幾個重要的思想。在我剛學習Java語言的時候,我認識了面向對象的編程思想。簡單來說就是指揮對象幫我們做事。通過一個點.即可。同樣,我認為學習RxJava也是學習一個編程思想。就是函數式編程,和響應式編程。我不知道這算不算是編程思想,但我知道一旦明白這兩個東東,對我們學習Rx有著意想不到的好處。

今天,今天通過介紹 Rx里面的概念(這部分我覺得才是最重要的一點),Rx里面的操作符,Rx里面的常用場景,還有Rx的擴展,將讀者帶入Rx的大門,了解他,觸摸它,使用它,掌握它。

1.4 響應式編程:

什么是響應式編程(Reactive programming)? RxJava ==>ReactiveX Java

百度百科的定義:

響應式編程是一種==面向數據流和變化傳播==的==編程范式==。這意味著可以在編程語言中很方便地表達靜態或動態的數據流,而相關的計算模型會自動將變化的值通過數據流進行傳播。

例如,在命令式編程環境中(面向過程和面向對象的語言中),a=b+c表示將表達式的結果賦給a,而之后改變b或c的值不會影響a。但在響應式編程中,a的值會隨著b或c的更新而更新。響應式編程最初是為了簡化交互式用戶界面的創建和實時系統動畫的繪制而提出來的一種方法,但它本質上是一種通用的編程范式。

這里什么是編程范式呢?說白了就是一種編碼的風格和模式。

此時,我們不免會疑惑,數據我們知道,那么數據流呢?

RxJava Essentials一書中,是這么說的。

響應式編程是一種基于異步數據流概念的編程模式。數據流就像一條河:它可以被觀測,被過濾,被操作,或者為新的消費者與另外一條流合并為一條新的流。

響應式編程的一個關鍵概念是事件。事件可以被等待,可以觸發過程,也可以觸發其它事件。

我相信看到這里大部分人已經暈了。
不過我是越來越清晰,一字一句的多讀幾遍。

那些年我們錯過的響應式編程中是這么介紹的:

響應式編程就是與異步數據流交互的編程方式。(和上面基本一樣)

一方面,這已經不是什么新事物了。事件總線(Event Buses)或一些典型的點擊事件本質上就是一個異步事件流(asynchronous event stream),這樣你就可以觀察它的變化并使其做出一些反應(do some side effects)。響應式是這樣的一個思路:除了點擊和懸停(hover)的事件外,你可以給任何事物創建數據流。數據流無處不在,任何東西都可以成為一個數據流,例如變量、用戶輸入、屬性、緩存、數據結構等等。舉個栗子,你可以把你的微博訂閱功能想象成跟點擊事件一樣的數據流,你可以監聽這樣的數據流,并做出相應的反應。

最重要的是,你會擁有一些令人驚艷的函數去結合、創建和過濾任何一組數據流。 這就是”函數式編程”的魔力所在。一個數據流可以作為另一個數據流的輸入,甚至多個數據流也可以作為另一個數據流的輸入。你可以合并兩個數據流,也可以過濾一個數據流得到另一個只包含你感興趣的事件的數據流,還可以映射一個數據流的值到一個新的數據流里。

最后我說一下我的理解:

首先響應式編程僅僅是一種編程的模式,風格,并不具有特殊的含義和操作,響應式編程他是用來對數據流進行操作的,所有對數據流進行異步操作,觀測等行為,我們都可以管這種操作行為叫做響應式編程。那么什么又是數據流呢?

1.4 數據流的概念:

數據流是整個響應式編程體系中的核心,要想學習響應式編程,首先我們就需要搞明白什么是數據流。其實,一個數據流是一個按時間排序的即將發生的事件(Ongoing events ordered in time)的序列。就比如我們警察抓小偷,小偷偷東西的時候,警察抓住他了嗎,小偷偷東西之前做了什么,之后做了什么,警察抓他之前做了什么,抓住他做了什么或者沒抓住又該怎么去做。這一系列事件組成的序列就是數據流。數據流就像一條河:它可以被觀測,被過濾,被操作,或者為新的消費者與另外一條流合并為一條新的流。

也就是說我們的響應式編程,就是對這一系列異步事件進行處理的一種編程模式。其實,你會發現,看了一大圈,又回到了第一句話,但是我相信,你現在應該不會在迷糊了。

1.5 函數式編程:

其實我一直搞不明白函數式編程和響應式編程。但是現在我明白了,而且他倆根本就不一樣。

1.5.1 什么是函數式編程:

應該有至少三種編程思想:

  • 命令式編程 Imperative programming
  • 邏輯式編程 Object-oriented Programming
  • 函數式編程 Functional Programming

命令式編程關心解決問題的步驟,面向對象編程是也是一種命令式編程,面向過程的C語言必然也是了。
而函數式編程關心數據的映射,即一種東西和另一種東西之間的對應關系。它的主要思想是把運算過程盡量寫成一系列嵌套的函數調用。

比如這種運算:(1 + 2) * 3 - 4 ;
之前我們寫是這樣:

```
int a = 1 + 2; int b = a * 3; int result = b - 4;
System.out.println(b);
```

然而用函數式編程思想寫就是這樣:

```
System.out.print(subtract( multiply(add(1,2),3),4) );
```

全部變為了函數調用,這樣看起來也簡潔、見名之意。(示例來自阮一峰博客)

看到這,想一下,使用RxJava時是不是全部調用各種操作符進行處理,這就是對事件流進行運算啊,全部調用函數進行處理。

在這里我們來對比一下函數式編程和響應式編程。函數式編程是對數據關系進行==映射==,比如說y=ax;這個一元函數。函數式編程我們只需要定義處來這個映射關系即參數a的算法,我們就可以得到y。而響應式編程的重點則不在于這個參數a,而是當我的x發生改變的時候(就是我們注冊的x的狀態改變)的時候,我要做出一個什么樣反應。這和我大學學到的激勵和響應是差不多的。我給你一個什么樣的激勵,你能反饋我一個什么樣的響應

1.6 觀察者模式

在今天,觀察者模式是出現的最常用的軟件設計模式之一。它基于subject這個概念。subject是一種特殊對象,當它改變時,那些由它保存的一系列對象將會得到通知。而這一系列對象被稱作Observers(觀察者),它們會對外暴漏了一個通知方法,當subject狀態發生變化時會調用的這個方法。

就比如說,當A的某狀態發生改變或者接收到某種激勵的時候,B需要對這種激勵做出一個反饋,那么我們只需要讓A持有B的引用,同時去調用B的方法即可(我們最終的目的是將發生改變的狀態傳遞出去,通過方法傳遞,我們的B需要這個狀態)。但是通常,我們的A不知道自己什么時候能獲取到這個激勵(什么時候狀態會發生改變),這個時候我們的B呢就可以通過注冊,來處理這個響應。同時,我們也可以不用拿到B的引用,我們可以通過一個中間接口,將發生改變的狀態(或者是B需要的狀態)通過接口傳遞過去,同時B只需要拿到這個接口的實例對象即可,這就是B的注冊過程。也是最簡單的回調。而回調也是觀察者模式的一種簡化形式,1-1的關系,當然我們平時不僅setOnClickListener()也addOnXxxListener(),這種一對多的關系就是觀察者模式。

1.7 擴展的觀察者模式

與傳統觀察者模式不同, RxJava的事件回調方法除了普通事件 onNext() (相當于 onClick() / onEvent())之外,還定義了兩個特殊的事件:onCompleted() 和 onError()。

1.8 幾個常見的類

在RxJava的世界里,我們有四種角色:

Observable

Observer

Subscriber

Subjects

Observables和Subjects是兩個“生產”實體,Observers和Subscribers是兩個“消費”實體

```
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(延遲創建)++ — 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(轉換)++ some other object or data structure into an Observable
  • ++Interval(間隔)++ — create an Observable that ++emits(發射)++ 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操作去創建一個被觀察者Observable。Create是最基本的創建Observable的操作符。
image

創建一個Observable(被觀察者)最重要的就是要和合適的時機調用Subscriber(觀察者)的onNext/onComplete/onError方法。onNext就是發射處理好的數據給Subscriber; onComplete用來告訴Subscriber所有的數據都已發射完畢;onError是在發生錯誤的時候發射一個Throwable對象給Subscriber。需要注意的一點就是Observable必須調用所有的Subscriber的onComplete方法并且只能調用一次,出錯的時候調用onError方法也是一樣的,并且一旦調用后就不能調用Subscriber的任何其他方法了。

介紹完畢。

我再詳細介紹一下。

我們可以看到Observable.create(這里經常會傳入一個OnSubscribe接口);

OnSubscribe接口又是什么呢?它繼承自Action1<Subscriber<? super T>這個接口,他的call(<Subscriber<? super T> subscriber)方法里只有一個參數,它傳入了一個觀察者!!!也就是說我們在使用create的時候,我們將觀察者包裹在了被觀察者內部。并且在被觀察這訂閱觀察者的時候,我們通過一個hook對象將觀察者Subscriber的對象傳入到OnSubscribe.call(傳到這里面)。這樣就實現了由被觀察者在訂閱的時候調用觀察者的方法。并且在事件完成的時候自動取消訂閱。

    Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> subscriber) {
            //將依次發射3次onNext ,之后發射onComplete(發射完這個將不會再有輸出,同時自動取消注冊)
            subscriber.onNext("Hello第一次發射");
            subscriber.onNext("World第二次發射");
            subscriber.onNext("Over 第三次發射");
            subscriber.onCompleted();
            subscriber.onError(new Throwable());
        }
    })

2.Just、From的使用

為了簡化創建操作,我們平時最常使用的一般是這兩個操作符。just(T...):from(T[])。

Just操作符將某個對象轉化為Observable對象,并且將其發射出去,可以使一個數字、一個字符串、數組、Iterate對象等。其實當just傳入一個參數的時候,就是調用create()方法,傳入多參數的時候,就是調用from()方法。從而做到將參數依次發射出去

From操作符用來將某個對象轉化為Observable對象,并且依次將其內容發射出去。這個類似于just,但是just會將這個對象整個發射出去。比如說一個含有10個數字的數組,使用from就會發射10次,每次發射一個數字,而使用just會發射一次來將整個的數組發射出去。


image

3.Defer

Defer操作符只有當有Subscriber來訂閱的時候才會創建一個新的Observable對象,也就是說每次訂閱都會得到一個剛創建的最新的Observable對象,這可以確保Observable對象里的數據是最新的


image

4.Interval

Interval所創建的Observable對象會從0開始,每隔固定的時間發射一個數字。需要注意的是這個對象是運行在computation Scheduler,基本上和時間有關系的都是運行在computation Scheduler 中,所以如果需要在view中顯示結果,要在主線程中訂閱。


image

5.Range

這個應該好理解一點,看圖依次發射區間(n,n+m-1)里面的數字,左閉右開


image

6.Repeat、Timer

Repeat會將一個Observable對象重復發射,我們可以指定其發射的次數


image

Timer會在指定時間后發射一個數字0,注意其也是運行在computation Scheduler


image

總覽:

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(一次一個)++
  • 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 提供了對事件序列進行變換的支持,這是它的核心功能之一,也是大多數人說『RxJava 真是太好用了』的最大原因。所謂變換,就是將事件序列中的對象或整個序列進行加工處理,轉換成不同的事件或事件序列。

1.Buffer

Buffer操作符所要做的事情就是將數據按照規定的大小、或者時間做一下緩存,然后將緩存的數據作為一個集合發射出去。

image

在上圖中,按照規定的大小(count = 3)來收集數據,之后將他們整合成一個集合發射出去。
image

在上圖中,加入了一個skip參數用來指定每次發射一個集合需要跳過幾個數據,圖中指定(count = 2,skip = 3),就會每3個數據發射一個包含兩個數據的集合,如果count==skip的話,我們就會發現其等效于第一種情況了。這里還是很好理解的。

2.flatMap & concatMap

Flatmap是一個用處特別多的操作符。它可以將數據根據你想要的規則進行轉化后再發射出去。其原理就是將這個Observable轉化為多個以原Observable發射的數據作為源數據的Observable,然后再將這多個Observable發射的數據整合發射出來,需要注意的是最后的順序可能會交錯地發射出來,如果對順序有嚴格的要求的話可以使用concatmap操作符。


image

如上圖,我們按照一個圓對應兩個菱形的規則,將一個Observable<圓>轉換成兩個Observable<菱形>。并將兩個Observable<菱形>發射出去。

3.Map

Map操作符的功能類似于FlatMap,不同之處在于它對數據的轉化是直接進行的,而FlatMap需要通過一些中間的Observables來進行。


image

4.GroupBy

GroupBy操作符將原始Observable發射的數據按照key來拆分成一些小的Observable,然后這些小的Observable分別發射其所包含的的數據,類似于sql里面的groupBy。在使用中,我們需要提供一個生成key的規則,所有key相同的數據會包含在同一個小的Observable種。


image

5.Scan

Scan操作符對一個序列的數據應用一個函數,并將這個函數的結果發射出去作為下個數據應用這個函數時候的第一個參數使用,有點類似于遞歸操作


image

總覽:過濾

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只會返回滿足過濾條件的數據.比如說,我們想讓數據{a1,a2,b1,v1,c1}中以v開頭的數據排除掉,或者我們經常用到的AppInfo中數據為null的過濾掉,我們可以使用filter輕松實現。

image

如上圖,我們應該filter(東西 == 圓)

2.take/takelast

當我們不需要整個序列時,而是只想取開頭或結尾的幾個元素,我們可以用take()或takeLast()。


image
```
//太簡單了,我就不寫demo了,take(3)從前面取3個,takeLast(3)從后面取3個
 Observable.from(apps).take(3)
```

3.Distinct 有且僅有一次

我們可以對我們的序列使用distinct()函數去掉重復的。就像takeLast()一樣,distinct()作用于一個完整的序列,然后得到重復的過濾項,它需要記錄每一個發射的值。如果你在處理一大堆序列或者大的數據記得關注內存使用情況。
Distinct操作符的用處就是用來去重,非常好理解。如下圖所示,所有重復的數據都會被過濾掉。還有一個操作符distinctUntilChanged,是用來過濾掉連續的重復數據。


image

image

4.First and last

first()方法和last()方法很容易弄明白。它們從Observable中只發射第一個元素或者最后一個元素。連圖我都不上了。
firstOrDefault()和lastOrDefault().這兩個函數當可觀測序列完成時不再發射任何值時用得上。在這種場景下,如果Observable不再發射任何值時我們可以指定發射一個默認的值

5.Skip and SkipLast

skip()和skipLast()函數與take()和takeLast()相對應。它們用整數N作參數,從本質上來說,它們不讓Observable發射前N個或者后N個值。如果我們知道一個序列以沒有太多用的“可控”元素開頭或結尾時我們可以使用它。


image
image

6.ElementAt

如果我們只想要可觀測序列發射的第五個元素該怎么辦?elementAt()函數僅從一個序列中發射第n個元素然后就完成了。+

如果我們想查找第五個元素但是可觀測序列只有三個元素可供發射時該怎么辦?我們可以使用elementAtOrDefault()。下圖展示了如何通過使用elementAt(2)從一個序列中選擇第三個元素以及如何創建一個只發射指定元素的新的Observable。


image
image

7.Sample

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


image

image
image

8.Debounce

debounce()函數過濾掉由Observable發射的速率過快的數據;如果在一個指定的時間間隔過去了仍舊沒有發射一個,那么它將發射最后的那個。
下圖展示了多久從Observable發射一次新的數據,debounce()函數開啟一個內部定時器,如果在這個時間間隔內沒有新的數據發射,則新的Observable發射出最后一個數據:


image

Combining Observables

1.Zip

Zip操作符將多個Observable發射的數據按順序組合起來,每個數據只能組合一次,而且都是有序的。最終組合的數據的數量由發射數據最少的Observable來決定。


image

2.Merege

Merge操作符將多個Observable發射的數據整合起來發射,就如同是一個Observable發射的數據一樣。但是其發射的數據有可能是交錯的,如果想要沒有交錯,可以使用concat操作符。當某一個Observable發出onError的時候,merge的過程會被停止并將錯誤分發給Subscriber,如果不想讓錯誤終止merge的過程,可以使用MeregeDelayError操作符,會將錯誤在merge結束后再分發。


image

1.使用Scheduler進行線程的切換

在不指定線程的情況下, RxJava 遵循的是線程不變的原則,即:在哪個線程調用訂閱方法 subscribe(),就在哪個線程生產事件;在哪個線程生產事件,就在哪個線程消費事件。如果需要切換線程,就需要用到 Scheduler (調度器)。

幾個常用的Api:
  • Schedulers.immediate(): 直接在當前線程運行,相當于不指定線程。這是默認的 Scheduler。
  • Schedulers.newThread(): 總是啟用新線程,并在新線程執行操作。
  • Schedulers.io(): I/O 操作(讀寫文件、讀寫數據庫、網絡信息交互等)所使用的 Scheduler。行為模式和 newThread() 差不多,區別在于 io() 的內部實現是是用一個無數量上限的線程池,可以重用空閑的線程,因此多數情況下 io() 比 newThread() 更有效率。不要把計算工作放在 io() 中,可以避免創建不必要的線程。
  • Schedulers.computation(): 計算所使用的 Scheduler。這個計算指的是 CPU 密集型計算,即不會被 I/O 等操作限制性能的操作,例如圖形的計算。這個 Scheduler 使用的固定的線程池,大小為 CPU 核數。不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。
  • Android 還有一個專用的 AndroidSchedulers.mainThread(),它指定的操作將在 Android 主線程運行。

有了這幾個 Scheduler ,就可以使用 subscribeOn()observeOn() 兩個方法來對線程進行控制了。

  • subscribeOn(): 指定 subscribe() 所發生的線程,即 Observable.OnSubscribe 被激活時所處的線程。或者叫做事件產生的線程。

  • observeOn(): 指定 Subscriber 所運行在的線程。或者叫做事件消費的線程。

精簡代碼:(教簡單就不放全部代碼了)

```
 mSubscription = getObservable()//創建一個被觀察者
   .subscribeOn(Schedulers.io())//指定訂閱事件發生的線程
   .doOnSubscribe(new Action0() {//事件發生之前做什么事
//onStart() 也可以用作流程開始前的初始化。但是 onStart() 由于在 subscribe() 發生時就被調用了,因此不能指定線程,而是只能執行在 subscribe() 被調用時的線程。
//doOnSubscribe同樣是在 subscribe() 調用后而且在事件發送前執行,但區別在于它可以指定線程。
//默認情況下, doOnSubscribe() 執行在 subscribe() 發生的線程;而如果在 doOnSubscribe() 之后有 subscribeOn() 的話,它將執行在離它最近的 subscribeOn() 所指定的線程。
                            @Override
                            public void call() {
                                mProgressBar.setVisibility(View.VISIBLE);
                                _log("按鈕被點擊了");
                            }
                        })
                        .observeOn(AndroidSchedulers.mainThread())//指定事件消費在那個線程里面
                        .subscribe(getSubscriber());//訂閱觀察者
```

### 2.使用Buffer收集數據流后在發射

Buffer顧名思義,就相當一個緩沖區,我們可以指定一個時間或者數量來收集這些數據,滿足了這個事件或者數量之后,在統一發射出去。在這里我們舉個例子,來學習Buffer,同時更深入的理解一個更重要的操作符map

//觀察在兩秒的時間內,按鈕被點擊的次數
 public Subscription getClickNumInTime(){
 //這里需要引入compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'依賴,這樣我們可以觀測操作這些控件的數據流
    return RxView
    //將點擊事件包裝成數據(創建一個可觀測的序列)流發射出去
           .clicks(tapButton)
           //這里我們可以很清楚,很明了的看到,我們將點擊事件map成了Integer類型的數據
           .map(new Func1<Void, Integer>() {
               @Override
               public Integer call(Void aVoid) {
                   _log("得到了一個點擊");
                   return 1;
               }
           })
           //通過Buffer指定2秒鐘發射一次收集的數據
            .buffer(2, TimeUnit.SECONDS)
            //Buffer指定的線程是computation線程,我們修改UI要改變線程
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<List<Integer>>() {
                @Override
                public void onCompleted() {
                    _log("你永遠不會走到這里,我不信這句話能打印處來");
                }

                @Override
                public void onError(Throwable e) {
                    _log("出錯了"+e.getMessage());
                }
//一旦訂閱之后,除非取消訂閱,或者徹底殺死進程,否則會一直走下去,造成內存泄露
                @Override
                public void onNext(List<Integer> integers) {
                    if(integers.size() > 0)
                    _log("你在兩秒內一共點擊了:"+integers.size()+"次");
                }
            });
}

### 3.使用Debounce做TextSearch
比如說當我們輸入的內容發生改變的時候,我們需要實時的去異步請求網絡/數據庫來提示用戶某樣東西也需要改變。就比如,當我輸入金額的時候,沒當我EditText內容發生改變的時候我去請求服務器,將得到的京金幣展示在桌面上。有些時候,用戶連續幾個數字輸入的過快,沒必要去請求網絡,這個時候,我們可以使用Debounce來做限流操作,一定時間內,我們只要最后一次得到的數據。

    ```
    mSubscription = RxTextView
    //將EditText的內容改變事件轉換成一組可觀測的序列(數據流)
                .textChangeEvents(inputTxtDebounce)
                //開啟限流操作,在400毫秒內,我們只要最后一次改變的結果
                .debounce(400, TimeUnit.MILLISECONDS)
                //過濾掉空字符串,只有滿足條件的能留下
                .filter(new Func1<TextViewTextChangeEvent, Boolean>() {
                    @Override
                    public Boolean call(TextViewTextChangeEvent textViewTextChangeEvent) {
                        return !inputTxtDebounce.getText().toString().trim().isEmpty();
                    }
                })
                //和Buffer一樣,生產事件運行在computation線程
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getSubscriber());
    ```
很多時候我們都需要給訂閱事件來一個返回值,來方便我們在離開頁面的時候取消訂閱。

### 4.Retrofit+RxJava
目前最火的網絡請求就是Retrofit了,而目前最火的框架,二者也有一合之地,而二者的無縫結合,完全使我們的網絡請求隨心所欲。
Retrofit我就不多做介紹了。

    ```
    //先來看一個最簡單的。
    //首先我們用一個裝訂閱set集合,將這一個網絡請求的訂閱裝進來
    mSubscriptions.add(
    //通過Retrofit創建網絡請求
    mGithubApi
        .contributors(_username.getText().toString(),_repo.getText().toString())
        //指定網絡請求(生產事件)運行的線程
        .subscribeOn(Schedulers.io())
        //指定響應的線程(消費事件)
        .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) {
             //網絡請求得到了一組集合,我們對這一組集合遍歷拿到我們想要的數據
                for (int i = 0; i < contributors.size(); i++) {
                    _adapter.add(format("%s 為 %s 庫做出了 %d 個貢獻",contributors.get(i).login,_repo.getText().toString(),contributors.get(i).contributions));
            }
        }
      })
    );
    ```
如果!我在拿到List<Contributor>響應數據的時候,我要根據里面的作者,去搜索每一個作者的詳細信息,怎么辦?我想一想就覺得頭疼,先把這些作者收集起來,遍歷,再去挨個請求,再將得到的數據和剛才的數據整合起來,顯示到桌面?
不,RxJava的優點就是,你的需求越復雜,我的邏輯越簡單!!!!!!

    ```
    mSubscriptions.add(
    //先創建網絡請求
    mGithubApi.contributors(_username.getText().toString(),_repo.getText().toString())
    //我們先將得到的數據<List<Contributor>,遍歷發射并觀測其中的每一個元素
    .flatMap(new Func1<List<Contributor>, Observable<Contributor>>() {
        @Override
        public Observable<Contributor> call(List<Contributor> contributors) {
            return Observable.from(contributors);
        }
    })
    //這里,重點來了,先說一下Pair,他就是一個包含了兩個對象的容器。在這里我們用它來裝User和Contributor
    .flatMap(new Func1<Contributor, Observable<Pair<User,Contributor>>>() {
        @Override
        public Observable<Pair<User,Contributor>> call(Contributor contributor) {
        //在這里,我們在根據longin參數再去請求user的詳細信息
        Observable<User> userObservable = mGithubApi.user(contributor.login)
            //過濾掉非空數據
            .filter(new Func1<User, Boolean>() {
                @Override
                public Boolean call(User user) {
                    return !isEmpty(user.name) && !isEmpty(user.email);
                }
            });
            //到了整合的時候了。我們將user的詳細信息的數據流,同時將contributor這個值再次包裝成數據流發整合之后
            //怎么整合呢,這時候就要看Func2這個函數了
            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);
                }
            });
        }
    })
    //下面的不用多說了,我只想說,這個代碼寫的真的很精彩
    .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 個貢獻",
                                        user.name,
                                        user.email,
                                        _repo.getText().toString()
                                        ,contributor.contributions));

                                _adapter.notifyDataSetChanged();
                            }
                        })
                );

5.使用combineLatest做登錄注冊模塊

CombineLatest操作符可以將2~9個Observable發射的數據組裝起來然后再發射出來。不過還有兩個前提:
1.所有的Observable都發射過數據。(用戶操作APP可以感覺到,只有當我三個EditText全部輸入過內容,才會觸發combineLatest的發射事件)
2.滿足條件1的時候任何一個Observable發射一個數據,就將所有Observable最新發射的數據按照提供的函數組裝起來發射出去。(每當我任何一個EditText發生改變的時候都會重新走一遍combineLatest的發射事件)
這里我們看著例子,來解釋這些話語


image

這個也是挺常用的,他可以將我們需要的數據流整合在一起。比如:
注冊的時候所有輸入信息(郵箱、密碼、電話號碼等)合法才點亮注冊按鈕。

```
//這里我們分別將3個EditText的內容變化的事件轉換成數據流
_emailChangeObservable = RxTextView.textChanges(_email).skip(1);
_passwordChangeObservable = RxTextView.textChanges(_password).skip(1);
_numberChangeObservable = RxTextView.textChanges(_number).skip(1);

private void _combineLatestEvents() {
//我們將三個數據流整合起來,并加入整合的規則(這里可以將規則看成返回值Boolean,滿足規則我返回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做定時操作。當有“x秒后執行y操作”類似的需求的時候想到使用timer
我們一般做APP會用到定時器,當用到定時器的。比如說再按一次退出程序,我們可以這么做。我們通過Timer計時,如果退出了的話會造成不到兩秒的內存泄露,當然也可以先取消訂閱在finish()頁面。這里使用Timer來記錄用戶點擊的次數,如果時間不足兩秒,復原count

    `    int count = 1 ;
    onBackPressed(){
    if(count != 2){
        Toast.makeText(this, "再按一次退出應用", 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做周期性操作。當有“每隔xx秒后執行yy操作”類似的需求的時候,想到使用interval.
這也是我們經常用到的,就比如首頁的輪播圖就可以通過Interval來實現,我們每3秒讓ViewPager當前顯示的頁面+1即可。具體代碼可以見我的另一篇簡書。

### 8.防抖點擊
這里我使用的Fragmention也做了防抖點擊,而且思想特別好,簡單。他的做法是當我開啟一個Fragmention的時候,我禁用掉Activity全屏的觸摸事件,在開啟之后在恢復。
這里呢,我們使用RxJava的限流操作符,在1秒內只允許流出來一個點擊事件。

    ```
    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的使用場景很多,遇到自己沒見到過得操作符,可以看看圖片和文檔介紹,很詳細,加油。

最后感謝awesome-Rxjava;你想要就的都在這里=>https://github.com/lzyzsd/Awesome-RxJava

附上本文的Demo:https://github.com/SshiGguang/Kits
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容

  • 本篇文章介主要紹RxJava中操作符是以函數作為基本單位,與響應式編程作為結合使用的,對什么是操作、操作符都有哪些...
    嘎啦果安卓獸閱讀 2,880評論 0 10
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,591評論 25 707
  • 作者寄語 很久之前就想寫一個專題,專寫Android開發框架,專題的名字叫 XXX 從入門到放棄 ,沉淀了這么久,...
    戴定康閱讀 7,638評論 13 85
  • 參考:給 Android 開發者的 RxJava 詳解-扔物線深入淺出RxJava 基礎 "a library f...
    Vincen1024閱讀 546評論 0 1
  • 如果這個世界都在蒼老 我也可以寫一些明亮的句子 氤氳柔軟、美好、希望 就算把謊言藏在糖果里 我依然在一千次重復中 ...
    客秋一閱讀 172評論 0 0