RxJava 系列 (一)RxJava 1.0 簡介

RxJava 在業內越來越受歡迎,對于老手來說,RxJava 太好用了,RxJava 簡直無所不能;然而對于新手來說,RxJava 更像一座陡峭的大山,似乎不能識得 RxJava 的真面目,無法逾越。糾其原因,新手更像是只知其然不知其所以然,只停留在會用的角度是無法了解 RxJava 精髓的。

我們學習一個新概念,應該多去思考,如 RxJava 出現的背景,它解決了什么問題,它是如何解決的,它有什么優點,它的核心思想是什么,它能應用在哪些場景等。如果對這幾個問題都有答案的話,對 RxJava 的基本認知就算完成了。對于 RxJava 小白來說,非常不建議一來就源碼分析,否則很容易陷入代碼黑洞無法自拔,源碼分析可以在 RxJava 進階階段去做。本文將會圍繞上面提出的幾個問題做一些簡要回答。

RxJava 背景

我們都知道 Java 是一門編程語言,那么 Rx 是什么呢?
Github 對 Rx 的介紹如下:

Reactive Extensions for Async Programming

Rx (Reactive Extension)是異步編程的響應式擴展,再具體一步說, Rx 是微軟.NET 的一個響應式擴展。Rx 借助可觀測的序列提供一種簡單的方式來創建異步的基于事件驅動的程序。開發者可以使用Observables模擬異步數據流,使用LINQ語法查詢Observables,并且很容易管理調度器的并發。

Netflix 在2012年開始意識到他們的架構要滿足他們龐大的用戶群體已經變得步履維艱。因此他們決定重新設計架構來減少 REST 調用的次數。取代幾十次的 REST 調用,而是讓客戶端自己處理需要的數據,他們決定基于客戶端需求創建一個專門優化過的 REST 調用。

為了實現這一目標,他們決定嘗試響應式,開始將.NET Rx 遷移到 JVM 上面。他們不想只基于 Java 語言;而是整個 JVM,從而有可能為市場上的每一種基于 JVM 的語言:如 Java、Clojure、Groovy、Scala 等等提供一種新的工具。

2013年二月份,Ben Christensen 和 Jafar Husain 發在 Netflix 技術博客的一篇文章第一次向世界展示了 RxJava。主要特點有:

  • 易于并發從而更好的利用服務器的能力。
  • 易于有條件的異步執行。
  • 一種更好的方式來避免回調地獄。
  • 一種響應式方法。

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 上使用可觀測的序列來組成異步的、基于事件的程序的庫。所以要學習 RxJava 理念和精髓,重點應該是放在 Rx 上,而不是放在 Java 這門編程語言上。并且 Jake Wharton 大神在 Github 上貢獻了 RxAndroid 擴展庫。RxAndroid 是在 RxJava 的基礎上結合 Android 系統特性而開發的一個庫,如主線程、UI 事件等。

核心思想

RxJava 的核心思想:異步響應式

在響應式的世界里,為了給提供更好的用戶體驗,一些函數的計算、網絡請求和數據庫查詢等操作應該異步執行,軟件的使用者不應該等待這些結果。同理對于開發者而言,也不需要等待結果,而是在結果返回時通知他,在這期間開發者可以做想做的任何事情。

響應式編程不同于命令式或面向對象編程,參考以下代碼:

int a = 1;
int b = 2;
int c = a + b; // c = 3
a = 4;  // c 仍然是 3

對于命令式編程來說, c = a+b 意思是將表達式的結果賦值給 c ,而之后 a 和 b 的值改變均不會影響到 c 。所以在給 a 賦值為4 時,c 的值仍然是 3;但是在響應式編程中,c 的值會隨著 a 或 b 的值更新而更新,當給 a 賦值為4 時,c 的值會自動更新為 6,響應式就是要關注值的變化。


舉例: 一個最直觀的例子就是 Excel 中,若規定 C1 = SUM(A1,B1),C1 的值一直會隨著 A1 或 B1 的值變化。

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

響應式編程的一個關鍵概念是事件。事件可以被等待,可以觸發過程,也可以觸發其它事件。事件是唯一的以合適的方式將我們的現實世界映射到我們的軟件中:如果屋里太熱了我們就打開一扇窗戶。同樣的,當我們更改電子表(變化的傳播)中的一些數值時,我們需要更新整個表格或者我們的機器人碰到墻時會轉彎(響應事件)。

今天,響應式編程最通用的一個場景是 UI:我們的移動 App 必須做出對網絡調用、用戶觸摸輸入和系統彈框的響應。在這個世界上,軟件之所以是事件驅動并響應的是因為現實生活也是如此。
響應式編程 文章鏈接:Reactive Programming

擴展的觀察者模式

觀察者模式

觀察者模式是最常見的設計模式之一。它主要基于 Subject 這個概念,Subject 是一種特殊的對象,當他改變時,由它保存的一系列對象將會得到通知。而這一系列對象被稱作 Observers 它們會對外暴露一個通知方法,當 Subject 狀態變化時會調用的這個方法。


觀察者模式.jpg

上圖中,展示了 Subject/Observer 是怎樣的一個 一對多的關系,如果有需要,一個 Subject 可以有無限多個 Observers,當 subject 狀態發生變化時,這些 Observers 中的每一個都會收到通知。

觀察者模式很適合下面這些場景中的任何一個:

  • 當你的架構有兩個實體類,一個依賴另一個,你想讓它們互不影響或者是獨立復用它們時。
  • 當一個變化的對象通知那些與它自身變化相關聯的未知數量的對象時。
  • 當一個變化的對象通知那些無需推斷具體是誰的對象時。
擴展

在 RxJava 中共有4個角色:

  • Observable
  • Observer
  • Subscriber
  • Subjects

Observables 和 Subjects 是兩個“生產”實體,Observers 和 Subscribers 是兩個“消費”實體。同時相比于觀察者模式,RxJava 添加了三個觀察者缺少的功能:

  • 生產者在沒有更多數據可用時能夠發出信號通知:onCompleted() 事件。
  • 生產者在發生錯誤時能夠發出信號通知:onError() 事件。
  • RxJava Observables 能夠組合而不是嵌套,從而避免開發者陷入回調地獄。

核心概念

Observables

當我們異步執行一些復雜的事情,Java提供了傳統的類,例如 Thread、Future、FutureTask、CompletableFuture 來處理這些問題。當復雜度提升,這些方案就會變得麻煩和難以維護。最糟糕的是,它們都不支持鏈式調用。

RxJava Observables 被設計用來解決這些問題。它們靈活,且易于使用,也可以鏈式調用,并且可以作用于單個結果程序上,更有甚者,也可以作用于序列上。無論何時你想發射單個標量值,或者一連串值,甚至是無窮個數值流,你都可以使用 Observable。

Observable的生命周期包含了三種可能的易于與Iterable生命周期事件相比較的事件,


使用Iterable時,消費者從生產者那里以同步的方式得到值,在這些值得到之前線程處于阻塞狀態。相反,使用Observable時,生產者以異步的方式把值推給觀察者,無論何時,這些值都是可用的。這種方法之所以更靈活是因為即便值是同步或異步方式到達,消費者在這兩種場景都可以根據自己的需要來處理。

為了更好地復用Iterable接口,RxJava Observable 類擴展了GOF觀察者模式的語義。引入了兩個新的接口:

  • onCompleted() 即通知觀察者Observable沒有更多的數據。
  • onError() 即觀察者有錯誤出現了。

從發射物的角度來看,有兩種不同的 Observables :熱的和冷的。一個"熱"的 Observable 典型的只要一創建完就開始發射數據,因此所有后續訂閱它的觀察者能從序列中間的某個位置開始接受數據(有一些數據錯過了)。一個"冷"的 Observable 會一直等待,直到有觀察者訂閱它才開始發射數據,因此這個觀察者可以確保會收到整個數據序列。

Subject

Subject 是一個神奇的對象,它可以是一個Observable同時也可以是一個 Observer:它作為連接這兩個世界的一座橋梁。一個 Subject 可以訂閱一個 Observable,就像一個觀察者,并且它可以發射新的數據,或者傳遞它接受到的數據,就像一個 Observable。很明顯,作為一個Observable,觀察者們或者其它 Subject 都可以訂閱它。

一旦 Subject 訂閱了 Observable,它將會觸發 Observable開始發射。如果原始的 Observable 是“冷”的,這將會對訂閱一個“熱”的 Observable 變量產生影響。RxJava 提供四種不同的 Subject:

  • PublishSubject,普通的 Subject 對象
  • BehaviorSubject,BehaviorSubject會首先向他的訂閱者發送截至訂閱前最新的一個數據對象(或初始值),然后正常發送訂閱后的數據流。
  • ReplaySubject,會緩存它所訂閱的所有數據,向任意一個訂閱它的觀察者重發:
  • AsyncSubjec,當Observable 完成時 AsyncSubject只會發布最后一個數據給已經訂閱的每一個觀察者。

優勢

RxJava 的優點一搜一籮筐,如提升開發效率,降低維護成本,簡化邏輯代碼,提升可讀性等。其逆天之處在于當程序邏輯變得復雜的情況下,它已然能夠保持簡潔。
看一個 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();
            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 實現如下:

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 文件的時候,你會看到被自動 Lambda 化的預覽,這將讓你更加清晰地看到程序邏輯:

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 調用風格看著很爽有木有!!! 良好的編碼規范邏輯清楚,能方便我們更好的定位問題、解決問題,進而能提升我們的效率。

這里引用《代碼大全》第 31 章中“把布局當做一種信仰”的一段話:

良好布局的目標:
1、準確表現代碼的邏輯結構
2、始終如一的表現代碼的邏輯結構
3、改善可讀性
4、經得起修改

RxJava 的代碼布局既準確的表示了代碼的邏輯結構,又增強了可讀性和可維護性,對于有代碼潔癖的小伙伴來說是大大的福利啊。

使用范例

1、在項目的 build.gradle 文件中添加對 RxJava 的支持

對于 RxJava 1.x 版本,

compile 'io.reactivex:rxjava:x.y.z‘

對于 RxJava 2.x 版本

compile "io.reactivex.rxjava2:rxjava:2.x.y"

需要注意的是,在一個項目中, RxJava 1.x 和 2.x 是可以同時使用的 ,因為不同版本的依賴路徑是不一樣的,即對于同一個類 1.x 版本和 2.x 版本的全路徑名不一樣,我們在使用時只需要注意引入的包名是否正確就可以了。

2、使用范例
  • 創建被觀察者
Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("Hello,RxJava");
        subscriber.onCompleted();
    }
});
  • 創建觀察者
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 的使用還是比較簡單的, 如果有更復雜的需求,可以熟悉相關操作符。 RxJava 的操作符基本能夠滿足所有的使用需求。

應用場景

  • RxJava + Retrofit ,最熱門的框架組合,實現網絡請求及結果的異步處理
  • RxBinding,Jake Wharton 的一個開源庫,本質上為 View 設置一些 Listenter,只不過是用 Observable 實現,如界面按鈕防止連續點擊、Android 一些 CheckBox 點擊更新響應視圖等
  • 線程切換,在后臺線程取數據,主線程展示”的模式中看見
  • 異步操作, 耗時操作,如復雜計算、網絡請求、I/O操作等
  • RxBus,RxBus并不是一個庫,而是一種模式。其思想是用 RxJava 實現解耦,同 GreenRobot 的 EventBus 和 Otto 類似。

對于 RxJava 的基礎部分就介紹就到這里了,后續文章將會介紹 RxJava 中的常用操作符及實現原理。

參考文獻

Git官網
給 Android 開發者的 RxJava 詳解
RxJava Essentials

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