談?wù)凴xJava

概述

如果上網(wǎng)去了解Android App架構(gòu)設(shè)計(jì)沛慢,你會(huì)發(fā)現(xiàn)赡若,都會(huì)有RxJava的身影,現(xiàn)在越來(lái)越多人開始接受并使用RxJava团甲。對(duì)于Android開發(fā)而言逾冬,一旦你理解它,你一定會(huì)愛不釋手。接下來(lái)來(lái)從幾個(gè)方面來(lái)介紹RxJava身腻。

RxJava是什么

官方給RxJava的定義如下:

RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.

翻譯:RxJava是 ReactiveX 在JVM上的一個(gè)實(shí)現(xiàn)产还,ReactiveX使用Observable序列組合異步和基于事件的程序。

關(guān)于ReactiveX嘀趟,可以參考官方文檔:
https://mcxiaoke.gitbooks.io/rxdocs/content/Intro.html

ReactiveX.io給的定義是脐区,Rx是一個(gè)使用可觀察數(shù)據(jù)流進(jìn)行異步編程的編程接口,ReactiveX結(jié)合了觀察者模式去件、迭代器模式和函數(shù)式編程的精華坡椒。

RxJava有什么好處

說(shuō)到RxJava的好處,大家肯定會(huì)異口同聲地說(shuō)是:簡(jiǎn)潔尤溜!它可以讓我們的代碼更簡(jiǎn)潔倔叼,邏輯更清晰。

  • 函數(shù)式風(fēng)格:對(duì)可觀察數(shù)據(jù)流使用無(wú)副作用的輸入輸出函數(shù)宫莱,避免了程序里錯(cuò)綜復(fù)雜的狀態(tài)
  • 簡(jiǎn)化代碼:Rx的操作符通痴稍埽可以將復(fù)雜的難題簡(jiǎn)化為很少的幾行代碼
  • 異步錯(cuò)誤處理:傳統(tǒng)的try/catch沒辦法處理異步計(jì)算,Rx提供了合適的錯(cuò)誤處理機(jī)制
  • 輕松使用并發(fā):Rx的Observables和Schedulers讓開發(fā)者可以擺脫底層的線程同步和各種并發(fā)問(wèn)題

為了讓大家更清晰的看到RxJava帶來(lái)的好處授霸,套用網(wǎng)上的一個(gè)例子來(lái)說(shuō)明:

假設(shè)有這樣一個(gè)需求:界面上有一個(gè)自定義的視圖 imageCollectorView 巡验,它的作用是顯示多張圖片,并能使用 addImage(Bitmap) 方法來(lái)任意增加顯示的圖片〉舛現(xiàn)在需要程序?qū)⒁粋€(gè)給出的目錄數(shù)組 File[] folders 中每個(gè)目錄下的 png 圖片都加載出來(lái)并顯示在 imageCollectorView 中显设。需要注意的是,由于讀取圖片的這一過(guò)程較為耗時(shí)辛辨,需要放在后臺(tái)執(zhí)行捕捂,而圖片的顯示則必須在 UI 線程執(zhí)行。常用的實(shí)現(xiàn)方式有多種斗搞,我這里貼出其中一種:

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可以這樣實(shí)現(xiàn):

Observable.from(folders)
    //找到目錄下的所有文件并發(fā)送出去
    .flatMap(new Func1<File, Observable<File>>() {
        @Override
        public Observable<File> call(File file) {
            return Observable.from(file.listFiles());
        }
    })
    //過(guò)濾掉非png結(jié)尾的文件指攒,只發(fā)送png文件
    .filter(new Func1<File, Boolean>() {
        @Override
        public Boolean call(File file) {
            return file.getName().endsWith(".png");
        }
    })
    //把收到的png文件轉(zhuǎn)為Bitmap發(fā)送出去
    .map(new Func1<File, Bitmap>() {
        @Override
        public Bitmap call(File file) {
            return getBitmapFromFile(file);
        }
    })
    //以上操作都在IO線程執(zhí)行
    .subscribeOn(Schedulers.io())
    //訂閱者的操作在UI線程執(zhí)行
    .observeOn(AndroidSchedulers.mainThread())
    //訂閱者
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) {
            //收到Bitmap后添加到控件中顯示出來(lái)
            imageCollectorView.addImage(bitmap);
        }
    });

對(duì)比上述代碼,你會(huì)發(fā)現(xiàn):

  1. 代碼嵌套的層級(jí)少了僻焚,更加扁平允悦,少了很多迷之縮進(jìn)
  2. 代碼邏輯也變得更清晰了,步驟明顯
  3. 線程切換更簡(jiǎn)單虑啤,一行代碼完成線程切換

上面的例子僅體現(xiàn)了一部分的優(yōu)勢(shì)隙弛,RxJava提供很豐富的操作符可用,可以應(yīng)用到很多復(fù)雜的業(yè)務(wù)場(chǎng)景中咐旧。

這里僅列舉一些Android中常用的操作符:

過(guò)濾操作(這些操作符用于從Observable發(fā)射的數(shù)據(jù)中進(jìn)行選擇)

  • Debounce — 只有在空閑了一段時(shí)間后才發(fā)射數(shù)據(jù)驶鹉,通俗的說(shuō),就是如果一段時(shí)間沒有操作铣墨,就執(zhí)行一次操作室埋,應(yīng)用于搜索輸入框
  • Throttle - 節(jié)流,一段時(shí)間內(nèi)的事件只允許發(fā)送一個(gè)事件,應(yīng)用于防止按鈕多次點(diǎn)擊
  • Distinct — 去重姚淆,過(guò)濾掉重復(fù)數(shù)據(jù)項(xiàng)
  • Filter — 過(guò)濾孕蝉,過(guò)濾掉沒有通過(guò)謂詞測(cè)試的數(shù)據(jù)項(xiàng),只發(fā)射通過(guò)測(cè)試的
  • First — 首項(xiàng)腌逢,只發(fā)射滿足條件的第一條數(shù)據(jù)

組合操作(組合操作符用于將多個(gè)Observable組合成一個(gè)單一的Observable)

  • Zip — 打包降淮,使用一個(gè)指定的函數(shù)將多個(gè)Observable發(fā)射的數(shù)據(jù)組合在一起,然后將這個(gè)函數(shù)的結(jié)果作為單項(xiàng)數(shù)據(jù)發(fā)射搏讶。應(yīng)用于一個(gè)界面需要從多個(gè)接口請(qǐng)求數(shù)據(jù)
  • Concat - 順序佳鳖,Concat持有多個(gè)Observable對(duì)象,并將它們按順序串聯(lián)成隊(duì)列媒惕。 first()操作符只從串聯(lián)隊(duì)列中取出并發(fā)送第一個(gè)事件系吩。因此,如果使用concat().first()妒蔚,無(wú)論多少個(gè)數(shù)據(jù)源穿挨,只有第一個(gè)事件會(huì)被檢索出并發(fā)送。應(yīng)用于接口緩存

其它操作

  • RetryWhen - 重試肴盏,指定什么情況重新執(zhí)行發(fā)射流科盛,應(yīng)用于接口重試

更多操作符參考文檔:https://mcxiaoke.gitbooks.io/rxdocs/content/Operators.html

RxJava基本原理

RxJava 的基本原理就是觀察者模式。它有四個(gè)基本概念:

  • Observable - 可觀察者菜皂,即被觀察者
  • Observer - 觀察者
  • Subscribe - 訂閱
  • Event - 事件

與傳統(tǒng)觀察者模式不同贞绵, RxJava 的事件回調(diào)方法除了普通事件 onNext() (相當(dāng)于 onClick() / onEvent())之外,還定義了兩個(gè)特殊的事件:onCompleted() 和 onError()恍飘。

  • onCompleted(): 事件隊(duì)列完結(jié)但壮。RxJava 不僅把每個(gè)事件單獨(dú)處理,還會(huì)把它們看做一個(gè)隊(duì)列常侣。RxJava 規(guī)定,當(dāng)不會(huì)再有新的 onNext() 發(fā)出時(shí)弹渔,需要觸發(fā) onCompleted() 方法作為標(biāo)志胳施。
  • onError(): 事件隊(duì)列異常。在事件處理過(guò)程中出異常時(shí)肢专,onError() 會(huì)被觸發(fā)舞肆,同時(shí)隊(duì)列自動(dòng)終止,不允許再有事件發(fā)出博杖。
  • 在一個(gè)正確運(yùn)行的事件序列中, onCompleted() 和 onError() 有且只有一個(gè)椿胯,并且是事件序列中的最后一個(gè)。需要注意的是剃根,onCompleted() 和 onError() 二者也是互斥的哩盲,即在隊(duì)列中調(diào)用了其中一個(gè),就不應(yīng)該再調(diào)用另一個(gè)。

如何讓整個(gè)事件流跑起來(lái)廉油?

  • 創(chuàng)建被觀察者惠险,可以通過(guò)from、just抒线、create等操作符進(jìn)行創(chuàng)建
  • 創(chuàng)建觀察者班巩,可以直接new一個(gè)Subscriber的實(shí)現(xiàn)
  • 訂閱,通過(guò)subscribe函數(shù)把兩者關(guān)聯(lián)起來(lái)

PS:這里需要執(zhí)行訂閱后事件流才會(huì)開始嘶炭,即如果沒有訂閱者抱慌,被觀察者是不會(huì)執(zhí)行任何操作

以下就是簡(jiǎn)單的創(chuàng)建一個(gè)事件流的示例代碼:

        Observable.create(new Observable.OnSubscribe<Object>() {
            @Override
            public void call(Subscriber<? super Object> subscriber) {
                
            }
        }).subscribe(new Subscriber<Object>() {
            @Override
            public void onCompleted() {
                
            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(Object o) {

            }
        });

上面的代碼里沒有涉及到線程切換,在 RxJava 的默認(rèn)規(guī)則中眨猎,事件的發(fā)出和消費(fèi)都是在同一個(gè)線程的抑进。也就是說(shuō),如果只用上面的方法宵呛,實(shí)現(xiàn)出來(lái)的只是一個(gè)同步的觀察者模式单匣。觀察者模式本身的目的就是『后臺(tái)處理,前臺(tái)回調(diào)』的異步機(jī)制宝穗,因此異步對(duì)于 RxJava 是至關(guān)重要的户秤。而要實(shí)現(xiàn)異步,則需要用到 RxJava 的另一個(gè)概念: Scheduler(調(diào)度器) 逮矛。

RxJava 已經(jīng)內(nèi)置了幾個(gè) Scheduler 鸡号,它們已經(jīng)適合大多數(shù)的使用場(chǎng)景:

  • Schedulers.immediate(): 直接在當(dāng)前線程運(yùn)行,相當(dāng)于不指定線程须鼎。這是默認(rèn)的 Scheduler鲸伴。
  • Schedulers.newThread(): 總是啟用新線程,并在新線程執(zhí)行操作晋控。
  • Schedulers.io(): I/O 操作(讀寫文件汞窗、讀寫數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)信息交互等)所使用的 Scheduler赡译。行為模式和 newThread() 差不多仲吏,區(qū)別在于 io() 的內(nèi)部實(shí)現(xiàn)是是用一個(gè)無(wú)數(shù)量上限的線程池,可以重用空閑的線程蝌焚,因此多數(shù)情況下 io() 比 newThread() 更有效率裹唆。不要把計(jì)算工作放在 io() 中,可以避免創(chuàng)建不必要的線程只洒。
  • Schedulers.computation(): 計(jì)算所使用的 Scheduler许帐。這個(gè)計(jì)算指的是 CPU 密集型計(jì)算,即不會(huì)被 I/O 等操作限制性能的操作毕谴,例如圖形的計(jì)算成畦。這個(gè) Scheduler 使用的固定的線程池距芬,大小為 CPU 核數(shù)。不要把 I/O 操作放在 computation() 中羡鸥,否則 I/O 操作的等待時(shí)間會(huì)浪費(fèi) CPU蔑穴。
  • 另外, Android 還有一個(gè)專用的 AndroidSchedulers.mainThread()惧浴,它指定的操作將在 Android 主線程運(yùn)行存和。

有了這幾個(gè) Scheduler ,就可以使用 subscribeOn() 和 observeOn() 兩個(gè)方法來(lái)對(duì)線程進(jìn)行控制了衷旅。

  • subscribeOn(): 指定 subscribe() 所發(fā)生的線程捐腿,即 Observable.OnSubscribe 被激活時(shí)所處的線程∈炼ィ或者叫做事件產(chǎn)生的線程茄袖。
  • observeOn(): 指定 Subscriber 所運(yùn)行在的線程∴揖猓或者叫做事件消費(fèi)的線程宪祥。

RxJava調(diào)用器還可以隨時(shí)切換線程,讓處理更加靈活:

Observable.just(1, 2, 3, 4) // 運(yùn)行在IO 線程家乘,由最近一個(gè) subscribeOn() 指定
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .map(mapOperator) // 運(yùn)行在新線程蝗羊,由上面最近一個(gè) observeOn() 指定
    .observeOn(Schedulers.io())
    .map(mapOperator2) // 運(yùn)行在IO 線程,由上面最近一個(gè) observeOn() 指定
    .observeOn(AndroidSchedulers.mainThread) 
    .subscribe(subscriber);  // 運(yùn)行在Android 主線程仁锯,由上面最近一個(gè) observeOn() 指定

可以這樣簡(jiǎn)單理解耀找,事件流是自上而下發(fā)送的,所以操作運(yùn)行的線程都是在上游指定的业崖,可以隨意切換野芒。但觸發(fā)訂閱這個(gè)動(dòng)作是在最后面,之前說(shuō)過(guò)双炕,需要有訂閱者訂閱了狞悲,被觀察者才會(huì)去執(zhí)行發(fā)送事件。也就是訂閱動(dòng)作發(fā)生后妇斤,會(huì)自下而上去通知被觀察者效诅,即使中間寫了subscribeOn也會(huì)無(wú)效,有效的只會(huì)是最上面的一個(gè)subscribeOn趟济。所以被觀察者發(fā)送事件的動(dòng)作是由下面最近一個(gè)subscribeOn指定的默色。

RxJava在Android中使用的注意事項(xiàng)

  • 內(nèi)存泄漏問(wèn)題

    使用RxJava發(fā)布一個(gè)訂閱后秽荞,當(dāng)頁(yè)面被finish,此時(shí)訂閱邏輯還未完成袭厂,如果沒有及時(shí)取消訂閱剑刑,就會(huì)導(dǎo)致Activity/Fragment無(wú)法被回收媳纬,從而引發(fā)內(nèi)存泄漏双肤。

    解決方案1:使用RxLifecycle庫(kù),使用此庫(kù)要繼承它的Fragment和Activity基類钮惠,然后在事件流中調(diào)用bindToLifecycle()方法茅糜。

    解決方案2:自己管理,在Fragment和Activity素挽、Presenter基類中添加CompositeSubscription, 每次調(diào)用訂閱里把得到的subscription添加進(jìn)去蔑赘,基類會(huì)在onDestroy里自動(dòng)取消訂閱。

  • 空指針問(wèn)題

    使用RxJava發(fā)布一個(gè)訂閱后预明,當(dāng)頁(yè)面被finish缩赛,此時(shí)訂閱邏輯還未完成,如果沒有及時(shí)取消訂閱撰糠,訂閱邏輯完成后一樣會(huì)觸發(fā)訂閱者的onNext方法酥馍,此時(shí)如果操作了頁(yè)面元素,就會(huì)Crash阅酪。

    解決方案:及時(shí)取消訂閱旨袒,同時(shí)把presenter持有的view置空,不執(zhí)行回調(diào)术辐。

總結(jié)

RxJava是一種編程思想的突破砚尽,它給開發(fā)者帶來(lái)了很多便利,讓代碼邏輯更加清晰簡(jiǎn)潔术吗∥炯可能有些開發(fā)團(tuán)隊(duì)認(rèn)為學(xué)習(xí)曲線較長(zhǎng)而放棄使用RxJava,但可以很負(fù)責(zé)任的告訴你较屿,學(xué)習(xí)它隧魄!使用它!享受它隘蝎!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末购啄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嘱么,更是在濱河造成了極大的恐慌狮含,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曼振,死亡現(xiàn)場(chǎng)離奇詭異几迄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)冰评,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門映胁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人甲雅,你說(shuō)我怎么就攤上這事解孙】犹睿” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵弛姜,是天一觀的道長(zhǎng)脐瑰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)廷臼,這世上最難降的妖魔是什么苍在? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮中剩,結(jié)果婚禮上忌穿,老公的妹妹穿的比我還像新娘。我一直安慰自己结啼,他們只是感情好掠剑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著郊愧,像睡著了一般朴译。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上属铁,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天眠寿,我揣著相機(jī)與錄音,去河邊找鬼焦蘑。 笑死盯拱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的例嘱。 我是一名探鬼主播狡逢,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拼卵!你這毒婦竟也來(lái)了奢浑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤腋腮,失蹤者是張志新(化名)和其女友劉穎雀彼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體即寡,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徊哑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了聪富。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片实柠。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖善涨,靈堂內(nèi)的尸體忽然破棺而出窒盐,到底是詐尸還是另有隱情,我是刑警寧澤钢拧,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布蟹漓,位于F島的核電站,受9級(jí)特大地震影響源内,放射性物質(zhì)發(fā)生泄漏葡粒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一膜钓、第九天 我趴在偏房一處隱蔽的房頂上張望嗽交。 院中可真熱鬧,春花似錦颂斜、人聲如沸夫壁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盒让。三九已至,卻和暖如春司蔬,著一層夾襖步出監(jiān)牢的瞬間邑茄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工俊啼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肺缕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓授帕,卻偏偏與公主長(zhǎng)得像同木,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子豪墅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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