RxJava入門(mén)之介紹與基本運(yùn)用
前言
因?yàn)檫@個(gè)RxJava內(nèi)容不算少,而且應(yīng)用場(chǎng)景非常廣,所以這個(gè)關(guān)于RxJava的文章我們會(huì)陸續(xù)更新,今天就來(lái)先來(lái)個(gè)入門(mén)RxJava吧
初識(shí)RxJava
什么是Rx
很多教程在講解RxJava的時(shí)候,上來(lái)就介紹了什么是RxJava。這里我先說(shuō)一下什么是Rx,Rx就是ReactiveX,官方定義是:
Rx是一個(gè)函數(shù)庫(kù),讓開(kāi)發(fā)者可以利用可觀(guān)察序列和LINQ風(fēng)格查詢(xún)操作符來(lái)編寫(xiě)異步和基于事件的程序
看到這個(gè)定義我只能呵呵,稍微通俗點(diǎn)說(shuō)是這樣的:
Rx是微軟.NET的一個(gè)響應(yīng)式擴(kuò)展。Rx借助可觀(guān)測(cè)的序列提供一種簡(jiǎn)單的方式來(lái)創(chuàng)建異步的,基于事件驅(qū)動(dòng)的程序。
這個(gè)有點(diǎn)清晰了,至少看到我們熟悉的異步與事件驅(qū)動(dòng),所以簡(jiǎn)單點(diǎn)且不準(zhǔn)確地來(lái)說(shuō):
Rx就是一種響應(yīng)式編程,來(lái)創(chuàng)建基于事件的異步程序
注意,這個(gè)定義是不準(zhǔn)確的,但是對(duì)于初學(xué)者來(lái)說(shuō),已經(jīng)可以有個(gè)基本的認(rèn)知了。
另外還有一點(diǎn)就是Rx其實(shí)是一種編程思想,用很多語(yǔ)言都可以實(shí)現(xiàn),比如RxJava、RxJS、RxPHP等等。而現(xiàn)在我們要說(shuō)的就是RxJava。
RxJava是什么
二話(huà)不說(shuō),先上定義:
RxJava就是一種用Java語(yǔ)言實(shí)現(xiàn)的響應(yīng)式編程,來(lái)創(chuàng)建基于事件的異步程序
有人問(wèn)你這不是廢話(huà)么,好吧那我上官方定義:
一個(gè)在 Java VM 上使用可觀(guān)測(cè)的序列來(lái)組成異步的、基于事件的程序的庫(kù)
反正我剛看這句話(huà)的時(shí)候也呵呵了,當(dāng)然現(xiàn)在有所領(lǐng)悟了。
除此之外,就是:異步,它就是一個(gè)實(shí)現(xiàn)異步操作的庫(kù)。
擴(kuò)展的觀(guān)察者模式
對(duì)于普通的觀(guān)察者模式,這里我就不細(xì)說(shuō)了。簡(jiǎn)單概括就是,觀(guān)察者(Observer)需要在被觀(guān)察者(Observable)變化的一瞬間做出反應(yīng)。
而兩者通過(guò)注冊(cè)(Register)或者訂閱(Subscribe)的方式進(jìn)行綁定。
就拿扔物線(xiàn)老師給的例子來(lái)說(shuō),我豐富了一下如圖所示:
其中這個(gè)Button就是被觀(guān)察者(Observable),OnClickListener就是觀(guān)察者(Observer),兩者通過(guò)setOnClickListener達(dá)成訂閱(Subscribe)關(guān)系,之后當(dāng)Button產(chǎn)生OnClick事件的時(shí)候,會(huì)直接發(fā)送給OnClickListener,它做出相應(yīng)的響應(yīng)處理。
當(dāng)然還有其他的例子,比如Android四大組件中的ContentProvider與ContentObserver之間也存在這樣的關(guān)系。
而RxJava的觀(guān)察者模式呢,跟這個(gè)差不多,但是也有幾點(diǎn)差別:
Observer與Observable是通過(guò) subscribe()
來(lái)達(dá)成訂閱關(guān)系。
RxJava中事件回調(diào)有三種:onNext()
、 onCompleted()
、 onError()
。
如果一個(gè)Observerble沒(méi)有任何的Observer,那么這個(gè)Observable是不會(huì)發(fā)出任何事件的。
其中關(guān)于第三點(diǎn),這里想說(shuō)明一下,在Rx中,其實(shí)Observable有兩種形式:熱啟動(dòng)Observable和冷啟動(dòng)Observable。
熱啟動(dòng)Observable任何時(shí)候都會(huì)發(fā)送消息,即使沒(méi)有任何觀(guān)察者監(jiān)聽(tīng)它。
冷啟動(dòng)Observable只有在至少有一個(gè)訂閱者的時(shí)候才會(huì)發(fā)送消息
這個(gè)地方雖然對(duì)于初學(xué)者來(lái)說(shuō)區(qū)別不大,但是要注意一下,所以上面的第三點(diǎn)其實(shí)就針對(duì)于冷啟動(dòng)來(lái)說(shuō)的。
另外,關(guān)于RxJava的回調(diào)事件的總結(jié):
onNext()
:基本事件。
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è)。如果在隊(duì)列中調(diào)用了其中一個(gè),就不應(yīng)該再調(diào)用另一個(gè)。
好了,那我們也附一張圖對(duì)比一下吧:
如何實(shí)現(xiàn)RxJava
關(guān)于實(shí)現(xiàn)RxJava的步驟,這里我就大體總結(jié)概括一下。
創(chuàng)建Observer
在Java中,一想到要?jiǎng)?chuàng)建一個(gè)對(duì)象,我們馬上就想要new一個(gè)。沒(méi)錯(cuò),這里我們也是要new一個(gè)Observer出來(lái),其實(shí)就是實(shí)現(xiàn)Observer的接口,注意String是接收參數(shù)的類(lèi)型:
//創(chuàng)建Observer Observer<String> observer = new Observer<String>() { @Override public void onNext(String s) { Log.i("onNext ---> ", "Item: " + s); } @Override public void onCompleted() { Log.i("onCompleted ---> ", "完成"); } @Override public void onError(Throwable e) { Log.i("onError ---> ", e.toString()); } };
當(dāng)然這里也要提一個(gè)實(shí)現(xiàn)了 Observer 接口的抽象類(lèi):Subscriber ,它跟 Observer 接口幾乎完全一樣,只是多了兩個(gè)方法,看看總結(jié):
onStart()
: 它會(huì)在 subscribe 剛開(kāi)始,而事件還未發(fā)送之前被調(diào)用,可以用于做一些準(zhǔn)備工作,例如數(shù)據(jù)的清零或重置。這是一個(gè)可選方法,默認(rèn)情況下它的實(shí)現(xiàn)為空。需要注意的是,如果對(duì)準(zhǔn)備工作的線(xiàn)程有要求(例如彈出一個(gè)顯示進(jìn)度的對(duì)話(huà)框,這必須在主線(xiàn)程執(zhí)行), onStart()
就不適用了,因?yàn)樗偸窃?subscribe 所發(fā)生的線(xiàn)程被調(diào)用,而不能指定線(xiàn)程。
unsubscribe()
: 用于取消訂閱。在這個(gè)方法被調(diào)用后,Subscriber 將不再接收事件。一般在這個(gè)方法調(diào)用前,可以使用 isUnsubscribed()
先判斷一下?tīng)顟B(tài)。 要在不再使用的時(shí)候盡快在合適的地方(例如 onPause() onStop() 等方法中)調(diào)用 unsubscribe()
來(lái)解除引用關(guān)系,以避免內(nèi)存泄露的發(fā)生。
雖然多了兩個(gè)方法,但是基本實(shí)現(xiàn)方式跟Observer是一樣的,所以暫時(shí)可以不考慮兩者的區(qū)別。不過(guò)值得注意的是:
實(shí)質(zhì)上,在 RxJava 的 subscribe 過(guò)程中,Observer 也總是會(huì)先被轉(zhuǎn)換成一個(gè) Subscriber 再使用。
創(chuàng)建Observable
與Observer不同的是,Observable是通過(guò) create()
方法來(lái)創(chuàng)建的。注意String是發(fā)送參數(shù)的類(lèi)型:
//創(chuàng)建Observable Observable observable = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("Hello"); subscriber.onNext("World"); subscriber.onCompleted(); } });
關(guān)于這其中的流程,我們暫且不考慮。
訂閱(Subscribe)
在之前,我們創(chuàng)建了 Observable 和 Observer ,現(xiàn)在就需要用 subscribe()
方法來(lái)將它們連接起來(lái),形成一種訂閱關(guān)系:
//訂閱 observable.subscribe(observer);
這里其實(shí)確實(shí)有點(diǎn)奇怪,為什么是Observable(被觀(guān)察者)訂閱了Observer(觀(guān)察者)呢?其實(shí)我們想一想之前Button的點(diǎn)擊事件:
Button.setOnClickListener(new View.OnClickListener())
Button是被觀(guān)察者,OnClickListener是觀(guān)察者,setOnClickListener是訂閱。我們驚訝地發(fā)現(xiàn),也是被觀(guān)察者訂閱了觀(guān)察者,所以應(yīng)該是一種流式API的設(shè)計(jì)吧,也沒(méi)啥影響。
完整代碼如下:
//創(chuàng)建Observer Observer<String> observer = new Observer<String>() { @Override public void onNext(String s) { Log.i("onNext ---> ", "Item: " + s); } @Override public void onCompleted() { Log.i("onCompleted ---> ", "完成"); } @Override public void onError(Throwable e) { Log.i("onError ---> ", e.toString()); } }; //創(chuàng)建Observable Observable observable = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("Hello"); subscriber.onNext("World"); subscriber.onCompleted(); } }); //訂閱 observable.subscribe(observer);
運(yùn)行的結(jié)果如下,可以看到Observable中發(fā)送的String已經(jīng)被Observer接收并打印了出來(lái):
線(xiàn)程控制——Scheduler
好了,這里就是RxJava的精髓之一了。
在RxJava中,Scheduler相當(dāng)于線(xiàn)程控制器,可以通過(guò)它來(lái)指定每一段代碼運(yùn)行的線(xiàn)程。
RxJava已經(jīng)內(nèi)置了幾個(gè)Scheduler,下面是總結(jié):
Schedulers.immediate()
: 直接在當(dāng)前線(xiàn)程運(yùn)行,相當(dāng)于不指定線(xiàn)程。這是默認(rèn)的Scheduler。
Schedulers.newThread()
: 總是啟用新線(xiàn)程,并在新線(xiàn)程執(zhí)行操作。
Schedulers.io()
: I/O 操作(讀寫(xiě)文件、讀寫(xiě)數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)信息交互等)所使用的Scheduler。行為模式和newThread()差不多,區(qū)別在于io()的內(nèi)部實(shí)現(xiàn)是是用一個(gè)無(wú)數(shù)量上限的線(xiàn)程池,可以重用空閑的線(xiàn)程,因此多數(shù)情況下io()比newThread()
更有效率。不要把計(jì)算工作放在io()中,可以避免創(chuàng)建不必要的線(xiàn)程。
Schedulers.computation()
: 計(jì)算所使用的Scheduler。這個(gè)計(jì)算指的是 CPU 密集型計(jì)算,即不會(huì)被 I/O 等操作限制性能的操作,例如圖形的計(jì)算。這個(gè)Scheduler使用的固定的線(xiàn)程池,大小為 CPU 核數(shù)。不要把 I/O 操作放在computation()
中,否則 I/O 操作的等待時(shí)間會(huì)浪費(fèi) CPU。
AndroidSchedulers.mainThread()
,Android專(zhuān)用線(xiàn)程,指定操作在主線(xiàn)程運(yùn)行。
那我們?nèi)绾吻袚Q線(xiàn)程呢?RxJava中提供了兩個(gè)方法:subscribeOn()
和 observeOn()
,兩者的不同點(diǎn)在于:
subscribeOn()
: 指定subscribe()
訂閱所發(fā)生的線(xiàn)程,即 call()
執(zhí)行的線(xiàn)程。或者叫做事件產(chǎn)生的線(xiàn)程。
observeOn()
: 指定Observer所運(yùn)行在的線(xiàn)程,即onNext()
執(zhí)行的線(xiàn)程?;蛘呓凶鍪录M(fèi)的線(xiàn)程。
具體實(shí)現(xiàn)如下:
//改變運(yùn)行的線(xiàn)程 observable.subscribeOn(Schedulers.io()); observable.observeOn(AndroidSchedulers.mainThread());
這里確實(shí)不好理解,沒(méi)關(guān)系,下面我們?cè)诰唧w例子中觀(guān)察現(xiàn)象。
而這其中的原理,會(huì)在之后的源碼級(jí)分析的文章中詳細(xì)解釋?zhuān)F(xiàn)在我們暫且擱下。
第一個(gè)RxJava案例
好了,當(dāng)看完之前的所有基礎(chǔ)東西,現(xiàn)在我們就完全可以寫(xiě)一個(gè)基于RxJava的Demo了。
這里我們用一個(gè)基于RxJava的異步加載網(wǎng)絡(luò)圖片來(lái)演示。
由于重點(diǎn)在于RxJava對(duì)于異步的處理,所以關(guān)于如何通過(guò)網(wǎng)絡(luò)請(qǐng)求獲取圖片,這里就不詳細(xì)說(shuō)明了。
另外這里采用的是鏈?zhǔn)秸{(diào)用,并為重要位置打上Log日志,觀(guān)察方法執(zhí)行的所在線(xiàn)程。
首先需要添加依賴(lài),這沒(méi)什么好說(shuō)的:
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' ... compile 'io.reactivex:rxjava:1.1.6' }
然后按照步驟來(lái),首先通過(guò)create創(chuàng)建Observable,注意發(fā)送參數(shù)的類(lèi)型是Bitmap:
//創(chuàng)建被觀(guān)察者 Observable.create(new Observable.OnSubscribe<Bitmap>() { /** * 復(fù)寫(xiě)call方法 * * @param subscriber 觀(guān)察者對(duì)象 */ @Override public void call(Subscriber<? super Bitmap> subscriber) { //通過(guò)URL得到圖片的Bitmap對(duì)象 Bitmap bitmap = GetBitmapForURL.getBitmap(url); //回調(diào)觀(guān)察者方法 subscriber.onNext(bitmap); subscriber.onCompleted(); Log.i(" call ---> ", "運(yùn)行在 " + Thread.currentThread().getName() + " 線(xiàn)程"); } })
然后我們需要?jiǎng)?chuàng)建Observer,并進(jìn)行訂閱,這里是鏈?zhǔn)秸{(diào)用
.subscribe(new Observer<Bitmap>() { //訂閱觀(guān)察者(其實(shí)是觀(guān)察者訂閱被觀(guān)察者) @Override public void onNext(Bitmap bitmap) { mainImageView.setImageBitmap(bitmap); Log.i(" onNext ---> ", "運(yùn)行在 " + Thread.currentThread().getName() + " 線(xiàn)程"); } @Override public void onCompleted() { mainProgressBar.setVisibility(View.GONE); Log.i(" onCompleted ---> ", "完成"); } @Override public void onError(Throwable e) { Log.e(" onError --->", e.toString()); } });
當(dāng)然網(wǎng)絡(luò)請(qǐng)求是耗時(shí)操作,我們需要在其他線(xiàn)程中執(zhí)行,而更新UI需要在主線(xiàn)程中執(zhí)行,所以需要設(shè)置線(xiàn)程:
.subscribeOn(Schedulers.io()) // 指定subscribe()發(fā)生在IO線(xiàn)程 .observeOn(AndroidSchedulers.mainThread()) // 指定Subscriber的回調(diào)發(fā)生在UI線(xiàn)程
這樣我們就完成了一個(gè)RxJava的基本編寫(xiě),現(xiàn)在整體看一下代碼:
//創(chuàng)建被觀(guān)察者 Observable.create(new Observable.OnSubscribe<Bitmap>() { /** * 復(fù)寫(xiě)call方法 * * @param subscriber 觀(guān)察者對(duì)象 */ @Override public void call(Subscriber<? super Bitmap> subscriber) { //通過(guò)URL得到圖片的Bitmap對(duì)象 Bitmap bitmap = GetBitmapForURL.getBitmap(url); //回調(diào)觀(guān)察者方法 subscriber.onNext(bitmap); subscriber.onCompleted(); Log.i(" call ---> ", "運(yùn)行在 " + Thread.currentThread().getName() + " 線(xiàn)程"); } }) .subscribeOn(Schedulers.io()) // 指定subscribe()發(fā)生在IO線(xiàn)程 .observeOn(AndroidSchedulers.mainThread()) // 指定Subscriber的回調(diào)發(fā)生在UI線(xiàn)程 .subscribe(new Observer<Bitmap>() { //訂閱觀(guān)察者(其實(shí)是觀(guān)察者訂閱被觀(guān)察者) @Override public void onNext(Bitmap bitmap) { mainImageView.setImageBitmap(bitmap); Log.i(" onNext ---> ", "運(yùn)行在 " + Thread.currentThread().getName() + " 線(xiàn)程"); } @Override public void onCompleted() { mainProgressBar.setVisibility(View.GONE); Log.i(" onCompleted ---> ", "完成"); } @Override public void onError(Throwable e) { Log.e(" onError --->", e.toString()); } });
好了,下面是運(yùn)行的動(dòng)態(tài)圖:
RxJava異步加載網(wǎng)絡(luò)圖片
現(xiàn)在來(lái)看一下運(yùn)行的Log日志:
Log
可以看到,call方法(事件產(chǎn)生)執(zhí)行在IO線(xiàn)程,而onNext方法(事件消費(fèi))執(zhí)行在main線(xiàn)程。說(shuō)明之前分析的是對(duì)的。
總結(jié)
好了,由于本文是一個(gè)RxJava的基礎(chǔ),所以篇幅稍微過(guò)長(zhǎng)了點(diǎn)。即使這樣,很多細(xì)節(jié)性問(wèn)題都沒(méi)有交代清楚。但所幸的是,本文已經(jīng)將RxJava必要的基礎(chǔ)入門(mén)知識(shí)講解完了。可能由于技術(shù)水平有限,文中難免會(huì)有錯(cuò)誤或者疏忽之處,歡迎大家指正與交流。希望這篇文章對(duì)大家的學(xué)習(xí)或者工作帶來(lái)一定的幫助,小編還會(huì)陸續(xù)更新相關(guān)的文章,感興趣的朋友們請(qǐng)繼續(xù)關(guān)注腳本之家。
- Android性能優(yōu)化之利用Rxlifecycle解決RxJava內(nèi)存泄漏詳解
- RxJava入門(mén)指南及其在A(yíng)ndroid開(kāi)發(fā)中的使用示例
- Android中的Retrofit+OkHttp+RxJava緩存架構(gòu)使用
- Android上傳多張圖片的實(shí)例代碼(RxJava異步分發(fā))
- RxJava2.x實(shí)現(xiàn)定時(shí)器的實(shí)例代碼
- RxJava 1升級(jí)到RxJava 2過(guò)程中踩過(guò)的一些“坑”
- 簡(jiǎn)單談?wù)凴xJava和多線(xiàn)程并發(fā)
- Android 使用 RxJava2 實(shí)現(xiàn)倒計(jì)時(shí)功能的示例代碼
- RxJava+Retrofit+OkHttp實(shí)現(xiàn)多文件下載之?dāng)帱c(diǎn)續(xù)傳
- 你用不慣 RxJava,只因缺了這把鑰匙(推薦)
相關(guān)文章
Android 根據(jù)EditText搜索框ListView動(dòng)態(tài)顯示數(shù)據(jù)
這篇文章主要介紹了Android 根據(jù)EditText搜索框ListView動(dòng)態(tài)顯示數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2016-09-09Android學(xué)習(xí)筆記之ContentProvider和Uri詳解
本篇文章主要介紹了Android學(xué)習(xí)筆記之ContentProvider和Uri詳解,對(duì)于學(xué)習(xí)Android的朋友具有一定的參考價(jià)值,有需要可以可以了解一下。2016-11-11Android SQLite數(shù)據(jù)庫(kù)基本操作方法
本篇文章主要介紹了Android SQLite數(shù)據(jù)庫(kù)基本操作方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02在A(yíng)ndroid中 獲取正在運(yùn)行的Service 實(shí)例
本篇文章小編為大家介紹,在A(yíng)ndroid中 獲取正在運(yùn)行的Service 實(shí)例。需要的朋友參考下2013-04-04Android UI控件之ListView實(shí)現(xiàn)圓角效果
這篇文章主要為大家詳細(xì)介紹了Android UI控件之ListView實(shí)現(xiàn)圓角效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Android通過(guò)單點(diǎn)觸摸移動(dòng)圖片
這篇文章主要為大家詳細(xì)介紹了Android通過(guò)單點(diǎn)觸摸移動(dòng)圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Android使用線(xiàn)程獲取網(wǎng)絡(luò)圖片的方法
這篇文章主要為大家詳細(xì)介紹了Android使用線(xiàn)程獲取網(wǎng)絡(luò)圖片的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06