RxJava的觀察者模式(二)

上一篇中我們了解了什么是RxJava,用一個詞來總結(jié)就是異步。
這里我們來講講RxJava的異步實現(xiàn)眠副。它是通過一種擴展的觀察者模式來實現(xiàn)网持。

一宜岛、觀察者模式

先簡書一下觀察者模式。
  觀察者模式面向的需求是:觀察者對被觀察者的某種變化作出反應(yīng)功舀。比如警察抓小偷萍倡,警察需要在小偷作案時實施抓捕。在這里面小偷是被觀察者辟汰,警察是觀察者列敲。而程序的觀察者模式跟真正的觀察略有不同,觀察者不需要時時刻刻頂著被觀察者帖汞,而是采用注冊(Register)或者被稱為訂閱(Subscribe)方式告訴觀察者:我需要你的某種狀態(tài)戴而,你要在你變化的時候通知我。
  Android開發(fā)中典型的觀察者模式就是監(jiān)聽器事件Listener翩蘸。對設(shè)置OnClickListener來說所意,View是被觀察者,OnClickListener是觀察者催首,兩者通過setOnClickListener()方法達成注冊(訂閱)關(guān)系扶踊。訂閱之后用戶點擊按鈕的瞬間,Android Framework 就會將點擊事件發(fā)送給已經(jīng)注冊的 OnClickListener郎任。
OnClickListener的模式圖


Paste_Image.png

如圖所示秧耗,通過 setOnClickListener()方法,Button持有 OnClickListener的引用(這一過程沒有在圖上畫出)舶治;當用戶點擊時分井,Button自動調(diào)用 OnClickListener的 onClick()方法胶台。另外,如果把這張圖中的概念抽象出來(Button -> 被觀察者杂抽、OnClickListener-> 觀察者诈唬、setOnClickListener()-> 訂閱,onClick() -> 事件)缩麸,就由專用的觀察者模式(例如只用于監(jiān)聽控件點擊)轉(zhuǎn)變成了通用的觀察者模式铸磅。如下圖:

Paste_Image.png

而 RxJava 作為一個工具庫,使用的就是通用形式的觀察者模式杭朱。

二阅仔、RxJava的觀察者模式

RxJava的四個基本概念
  Observable(被觀察者)
  Observer(觀察者)
  subscribe(訂閱)
  事件
  Oservable和Observer通過 subscribe()方法實現(xiàn)訂閱關(guān)系,從而 Observable可以在需要的時候發(fā)出事件來通知 Observer弧械。
  與傳統(tǒng)觀察者模式不同八酒, RxJava 的事件回調(diào)方法除了普通事件 onNext()(相當于 onClick() / onEvent())之外,還定義了兩個特殊的事件:onCompleted()和 onError()刃唐。
  onCompleted(): 事件隊列完結(jié)羞迷。RxJava 不僅把每個事件單獨處理,還會把它們看做一個隊列画饥。RxJava 規(guī)定衔瓮,當不會再有新的onNext()發(fā)出時,需要觸發(fā) onCompleted()方法作為標志抖甘。
  onError(): 事件隊列異常热鞍。在事件處理過程中出異常時,onError()會被觸發(fā)衔彻,同時隊列自動終止薇宠,不允許再有事件發(fā)出。
在一個正確運行的事件序列中, onCompleted()和 onError()有且只有一個艰额,并且是事件序列中的最后一個澄港。需要注意的是,onCompleted()
和 onError()二者也是互斥的悴晰,即在隊列中調(diào)用了其中一個慢睡,就不應(yīng)該再調(diào)用另一個。

RxJava 的觀察者模式大致如下圖:


Paste_Image.png

三铡溪、基本實現(xiàn)

1、創(chuàng)建Observer

Observer 即觀察者泪喊,它決定事件觸發(fā)的時候?qū)⒂性鯓拥男袨椤?RxJava 中的 Observer接口的實現(xiàn)方式:

Observer<String> observer = new Observer<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!");
    }
};

除了 Observer 接口之外棕硫,RxJava 還內(nèi)置了一個實現(xiàn)了 Observer的抽象類:Subscriber。 Subscriber對 Observer接口進行了一些擴展袒啼,但他們的基本使用方式是完全一樣的:

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!");
    }
};

不僅基本使用方式一樣哈扮,實質(zhì)上纬纪,在 RxJava 的 subscribe 過程中,Observer也總是會先被轉(zhuǎn)換成一個 Subscriber再使用滑肉。所以如果你只想使用基本功能包各,選擇 Observer和 Subscriber是完全一樣的。它們的區(qū)別對于使用者來說主要有兩點:
  onStart(): 這是 Subscriber增加的方法靶庙。它會在 subscribe 剛開始问畅,而事件還未發(fā)送之前被調(diào)用,可以用于做一些準備工作六荒,例如數(shù)據(jù)的清零或重置护姆。這是一個可選方法,默認情況下它的實現(xiàn)為空掏击。需要注意的是卵皂,如果對準備工作的線程有要求(例如彈出一個顯示進度的對話框,這必須在主線程執(zhí)行)砚亭, onStart()就不適用了灯变,因為它總是在 subscribe 所發(fā)生的線程被調(diào)用,而不能指定線程捅膘。要在指定的線程來做準備工作柒凉,可以使用 doOnSubscribe()方法,具體可以在后面的文中看到篓跛。
  unsubscribe(): 這是 Subscriber所實現(xiàn)的另一個接口 Subscription的方法膝捞,用于取消訂閱。在這個方法被調(diào)用后愧沟,Subscriber將不再接收事件蔬咬。一般在這個方法調(diào)用前,可以使用 isUnsubscribed()先判斷一下狀態(tài)沐寺。 unsubscribe()這個方法很重要林艘,因為在 subscribe()之后, Observable會持有 Subscriber的引用混坞,這個引用如果不能及時被釋放狐援,將有內(nèi)存泄露的風險。所以最好保持一個原則:要在不再使用的時候盡快在合適的地方(例如 onPause()onStop()等方法中)調(diào)用unsubscribe()來解除引用關(guān)系究孕,以避免內(nèi)存泄露的發(fā)生啥酱。

2、 創(chuàng)建 Observable

Observable 即被觀察者厨诸,它決定什么時候觸發(fā)事件以及觸發(fā)怎樣的事件镶殷。 RxJava 使用 create()方法來創(chuàng)建一個 Observable ,并為它定義事件觸發(fā)規(guī)則:

Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("Hello");
        subscriber.onNext("Hi");
        subscriber.onNext("Aloha");
        subscriber.onCompleted();
    }
});

可以看到微酬,這里傳入了一個 OnSubscribe對象作為參數(shù)绘趋。OnSubscribe會被存儲在返回的 Observable對象中颤陶,它的作用相當于一個計劃表,當 Observable被訂閱的時候陷遮,OnSubscribe的 call()方法會自動被調(diào)用滓走,事件序列就會依照設(shè)定依次觸發(fā)(對于上面的代碼,就是觀察者Subscriber將會被調(diào)用三次 onNext()和一次 onCompleted())帽馋。這樣搅方,由被觀察者調(diào)用了觀察者的回調(diào)方法,就實現(xiàn)了由被觀察者向觀察者的事件傳遞茬斧,即觀察者模式腰懂。
  create()方法是 RxJava 最基本的創(chuàng)造事件序列的方法∠畋基于這個方法绣溜, RxJava 還提供了一些方法用來快捷創(chuàng)建事件隊列,例如:

just(T...): 將傳入的參數(shù)依次發(fā)送出來娄蔼。

Observable observable = Observable.just("Hello", "Hi", "Aloha");
// 將會依次調(diào)用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

from(T[])/ from(Iterable<? extends T>): 將傳入的數(shù)組或 Iterable拆分成具體對象后怖喻,依次發(fā)送出來。

String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
// 將會依次調(diào)用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

上面 just(T...)的例子和 from(T[])的例子岁诉,都和之前的 create(OnSubscribe)的例子是等價的锚沸。

3、Subscribe (訂閱)

創(chuàng)建了 Observable和 Observer之后涕癣,再用 subscribe()方法將它們聯(lián)結(jié)起來哗蜈,整條鏈子就可以工作了。代碼形式很簡單:

observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);

除了 subscribe(Observer)和 subscribe(Subscriber)坠韩,subscribe()還支持不完整定義的回調(diào)距潘,RxJava 會自動根據(jù)定義創(chuàng)建出Subscriber 扳躬。形式如下:

Action1<String> onNextAction = new Action1<String>() {
    // onNext()
    @Override
    public void call(String s) {
        Log.d(tag, s);
    }
};
Action1<Throwable> onErrorAction = new Action1<Throwable>() {
    // onError()
    @Override
    public void call(Throwable throwable) {
        // Error handling
    }
};
Action0 onCompletedAction = new Action0() {
    // onCompleted()
    @Override
    public void call() {
        Log.d(tag, "completed");
    }
};

// 自動創(chuàng)建 Subscriber 月杉,并使用 onNextAction 來定義 onNext()
observable.subscribe(onNextAction);
// 自動創(chuàng)建 Subscriber ,并使用 onNextAction 和 onErrorAction 來定義 onNext() 和 onError()
observable.subscribe(onNextAction, onErrorAction);
// 自動創(chuàng)建 Subscriber 隧哮,并使用 onNextAction氢惋、 onErrorAction 和 onCompletedAction 來定義 onNext()洞翩、 onError() 和 onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction);

簡單解釋一下這段代碼中出現(xiàn)的 Action1和 Action0。 Action0是 RxJava 的一個接口焰望,它只有一個方法 call()骚亿,這個方法是無參無返回值的;由于 onCompleted()方法也是無參無返回值的柿估,因此 Action0可以被當成一個包裝對象循未,將 onCompleted()的內(nèi)容打包起來將自己作為一個參數(shù)傳入 subscribe()以實現(xiàn)不完整定義的回調(diào)。這樣其實也可以看做將 onCompleted() 方法作為參數(shù)傳進了subscribe()秫舌,相當于其他某些語言中的『閉包』的妖。
  Action1也是一個接口,它同樣只有一個方法 call(T param)足陨,這個方法也無返回值嫂粟,但有一個參數(shù);與 Action0同理墨缘,由于 onNext(T obj)和 onError(Throwable error)也是單參數(shù)無返回值的星虹,因此 Action1可以將 onNext(obj)和 onError(error)打包起來傳入 subscribe()以實現(xiàn)不完整定義的回調(diào)。事實上镊讼,雖然 Action0和 Action1在 API 中使用最廣泛宽涌,但 RxJava 是提供了多個 ActionX形式的接口 (例如 Action2, Action3) 的,它們可以被用以包裝不同的無返回值的方法蝶棋。

三卸亮、場景示例

1. 打印字符串數(shù)組

將字符串數(shù)組 names中的所有字符串依次打印出來:

String[] names = ...;
Observable.from(names)
    .subscribe(new Action1<String>() {
        @Override
        public void call(String name) {
            Log.d(tag, name);
        }
    });

在 RxJava 的默認規(guī)則中,事件的發(fā)出和消費都是在同一個線程的玩裙。也就是說兼贸,如果只用上面的方法,實現(xiàn)出來的只是一個同步的觀察者模式吃溅。觀察者模式本身的目的就是『后臺處理溶诞,前臺回調(diào)』的異步機制,因此異步對于 RxJava 是至關(guān)重要的决侈。而要實現(xiàn)異步螺垢,則需要用到 RxJava 的另一個概念: Scheduler。今天就先講到這里赖歌,下篇繼續(xù)枉圃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市俏站,隨后出現(xiàn)的幾起案子讯蒲,更是在濱河造成了極大的恐慌,老刑警劉巖肄扎,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墨林,死亡現(xiàn)場離奇詭異,居然都是意外死亡犯祠,警方通過查閱死者的電腦和手機旭等,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衡载,“玉大人搔耕,你說我怎么就攤上這事。” “怎么了弃榨?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵菩收,是天一觀的道長。 經(jīng)常有香客問我鲸睛,道長娜饵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任官辈,我火速辦了婚禮箱舞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拳亿。我一直安慰自己晴股,他們只是感情好,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布肺魁。 她就那樣靜靜地躺著电湘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪万搔。 梳的紋絲不亂的頭發(fā)上胡桨,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音瞬雹,去河邊找鬼昧谊。 笑死,一個胖子當著我的面吹牛酗捌,可吹牛的內(nèi)容都是我干的呢诬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼胖缤,長吁一口氣:“原來是場噩夢啊……” “哼尚镰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哪廓,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤狗唉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后涡真,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體分俯,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年哆料,在試婚紗的時候發(fā)現(xiàn)自己被綠了缸剪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡东亦,死狀恐怖杏节,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤奋渔,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布镊逝,位于F島的核電站,受9級特大地震影響卒稳,放射性物質(zhì)發(fā)生泄漏蹋半。R本人自食惡果不足惜他巨,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一充坑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧染突,春花似錦捻爷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至司志,卻和暖如春甜紫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骂远。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工囚霸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人激才。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓拓型,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瘸恼。 傳聞我的和親對象是個殘疾皇子劣挫,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容