作者寄語
很久之前就想寫一個專題
,專寫Android開發框架
,專題的名字叫 XXX 從入門到放棄
,沉淀了這么久,看過網絡諸多大神的博客,靜下心來開始寫這個專題
,為什么叫入門到放棄
呢;相信大家學習新框架的時候,尤其是像Rxjava
或者Dagger
等等這種新的編程思想;需要一定的閱讀理解能力和思維邏輯;那么本專題旨在幫助大家不要太過急功近利,不要被冗長的代碼和文章,晦澀的思想所打敗,相信大家只要堅持看完,一定會有所收獲的;廢話不多說,那么這個專題開篇就以RxJava
來講吧,預計后面還會有幾篇大型框架的講解,想想還有點小激動;
友情提示:文章較長,請耐心看完;
前言
RxJava
等編程思想正在Android
開發者中變的越來越流行。唯一的問題就是上手不容易,尤其是大部分人之前都是使用命令式編程語言
。
首先要先理清這么一個問題:Rxjava和我們平時寫的程序有什么不同。相信稍微對Rxjava有點認知的朋友都會深深感受到用這種方式寫的程序和我們一般寫的程序有很明顯的不同。我們一般寫的程序 統稱為命令式程序
,是以流程
為核心的,每一行代碼實際上都是機器實際上要執行的指令。而Rxjava這樣的編程風格,稱為函數響應式編程
。函數響應式編程是以數據流為核心,處理數據的輸入,處理以及輸出的。這種思路寫出來的代碼就會跟機器實際執行的指令大相徑庭。所以對于已經習慣命令式編程
的我們來說,剛開始接觸Rxjava
的時候必然會很不適應,而且也不太符合我們平時的思維習慣。但是久而久之你會發現這個框架的精髓,尤其是你運用到大項目中的時候,簡直愛不釋手,隨著程序邏輯變得越來越復雜,它依然能夠保持代碼簡潔。
RxJava是什么
a library for composing asynchronous and event-based programs using observable sequences for the Java VM
解釋:一個對于構成使用的Java虛擬機觀察序列異步
和基于事件的程序庫
RxJava 是一個響應式編程框架,采用觀察者設計模式。所以自然少不了 Observable 和 Subscriber 這兩個東東了。
RxJava 是一個開源項目,地址:https://github.com/ReactiveX/RxJava
RxAndroid,用于 Android 開發,添加了 Android 用的接口。地址: https://github.com/ReactiveX/RxAndroid
基本概念
網上關于
RxJava
的博文也有很多,我也看過許多,其中不乏有優秀的文章,但絕大部分文章都有一個共同點,就是側重于講RxJava中各種強大的操作符,而忽略了最基本的東西——概念,所以一開始我也看的一臉懵逼,看到后面又忘了前面的,腦子里全是問號,這個是什么,那個又是什么,這兩個長得怎么那么像。舉個不太恰當的例子,概念之于初學者,就像食物之于人,當你餓了,你會想吃面包、牛奶,那你為什么不去吃土呢,因為你知道面包牛奶是用來干嘛的,土是用來干嘛的。同理,前面已經說過,RxJava
無非是發送數據與接收數據,那么什么是發射源,什么是接收源,這就是你應該明確的事,也是RxJava
的入門條件之一,下面就依我個人理解,對發射源和接收源做個歸類,以及RxJava
中頻繁出現的幾個“單詞”解釋一通;
Observable
:發射源,英文釋義“可觀察的”,在觀察者模式中稱為“被觀察者”或“可觀察對象”;Observer
:接收源,英文釋義“觀察者”,沒錯!就是觀察者模式中的“觀察者”,可接收Observable
、Subject
發射的數據;Subject
:Subject
是一個比較特殊的對象,既可充當發射源,也可充當接收源,為避免初學者被混淆,本章將不對Subject
做過多的解釋和使用,重點放在Observable
和Observer
上,先把最基本方法的使用學會,后面再學其他的都不是什么問題;Subscriber
:“訂閱者”,也是接收源,那它跟Observer有什么區別呢?Subscriber
實現了Observer
接口,比Observer多了一個最重要的方法unsubscribe( ),用來取消訂閱,當你不再想接收數據了,可以調用unsubscribe( )
方法停止接收,Observer 在 subscribe() 過程中,最終也會被轉換成 Subscriber 對象,一般情況下,建議使用Subscriber作為接收源;Subscription
:Observable
調用subscribe( )
方法返回的對象,同樣有unsubscribe( )
方法,可以用來取消訂閱事件;Action0
:RxJava
中的一個接口,它只有一個無參call()
方法,且無返回值,同樣還有Action1,Action2...Action9等,Action1封裝了含有 1 個參的call()方法,即call(T t),Action2封裝了含有 2 個參數的call方法,即call(T1 t1,T2 t2),以此類推;Func0
:與Action0非常相似,也有call()方法,但是它是有返回值的,同樣也有Func0、Func1...Func9;
RxJava最核心的兩個東西是Observables
(被觀察者,事件源)和Subscribers
(觀察者)。Observables
發出一系列事件,Subscribers
處理這些事件。這里的事件可以是任何你感興趣的東西(觸摸事件,web接口調用返回的數據...)
一個Observable
可以發出零個或者多個事件,知道結束或者出錯。每發出一個事件,就會調用它的Subscriber
的onNext
方法,最后調用Subscriber.onNext()
或者Subscriber.onError()
結束。
Rxjava
的看起來很想設計模式中的觀察者模式
,但是有一點明顯不同,那就是如果一個Observerble
沒有任何的的Subscriber
,那么這個Observable
是不會發出任何事件的。
基本用法
Observable的創建
使用create( )
,最基本的創建方式:
Observable<String> myObservable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello, world!"); //發射一個"Hello, world!"的String
subscriber.onCompleted();//發射完成,這種方法需要手動調用onCompleted,才會回調Observer的onCompleted方法
}});
可以看到,這里傳入了一個 OnSubscribe
對象作為參數。OnSubscribe
會被存儲在返回的 Observable
對象中,它的作用相當于一個計劃表,當 Observable
被訂閱的時候,OnSubscribe
的 call()
方法會自動被調用,事件序列就會依照設定依次觸發(對于上面的代碼,就是觀察者Subscriber
將會被調用一次 onNext() 和一次 onCompleted())。這樣,由被觀察者調用了觀察者的回調方法,就實現了由被觀察者向觀察者的事件傳遞,即觀察者模式
。
這個例子很簡單:事件的內容是字符串,而不是一些復雜的對象;事件的內容是已經定好了的,而不像有的觀察者模式一樣是待確定的(例如網絡請求的結果在請求返回之前是未知的);所有事件在一瞬間被全部發送出去,而不是夾雜一些確定或不確定的時間間隔或者經過某種觸發器來觸發的。總之,這個例子看起來毫無實用價值。但這是為了便于說明,實質上只要你想,各種各樣的事件發送規則你都可以自己來寫。至于具體怎么做,后面都會講到,但現在不行。只有把基礎原理先說明白了,上層的運用才能更容易說清楚。
Subscriber的創建
上面定義的Observable
對象僅僅發出一個Hello World字符串,然后就結束了。接著我們創建一個Subscriber
來處理Observable
對象發出的字符串:
Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println(s); //打印出"Hello, world!"
}
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { }
};
除了 Observer
接口之外,RxJava 還內置了一個實現了 Observer
的抽象類:Subscriber
。 Subscriber
對 Observer
接口進行了一些擴展,但他們的基本使用方式是完全一樣的:
Observer<String> myObserver = new Observer<String>() {
@Override
public void onNext(String s) {
System.out.println(s); //打印出"Hello, world!"
}
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { }
};
不僅基本使用方式一樣,實質上,在 RxJava
的 subscribe
過程中,Observer
也總是會先被轉換成一個 Subscriber
再使用。所以如果你只想使用基本功能,選擇 Observer
和 Subscriber
是完全一樣
的。它們的區別對于使用者來說主要有兩點:
onStart()
: 這是Subscriber
增加的方法。它會在 subscribe 剛開始,而事件還未發送之前被調用,可以用于做一些準備工作,例如數據的清零或重置。這是一個可選方法,默認情況下它的實現為空。需要注意的是,如果對準備工作的線程有要求(例如彈出一個顯示進度的對話框,這必須在主線程執行),onStart()
就不適用了,因為它總是在subscribe
所發生的線程被調用,而不能指定線程。要在指定的線程來做準備工作,可以使用doOnSubscribe()
方法,具體可以在后面的文中看到。unsubscribe()
: 這是Subscriber
所實現的另一個接口Subscription
的方法,用于取消訂閱。在這個方法被調用后,Subscriber
將不再接收事件。一般在這個方法調用前,可以使用isUnsubscribed()
先判斷一下狀態。unsubscribe()
這個方法很重要,因為在 subscribe() 之后,Observable
會持有Subscriber
的引用,這個引用如果不能及時被釋放,將有內存泄露的風險。所以最好保持一個原則:要在不再使用的時候盡快在合適的地方(例如 onPause() onStop() 等方法中)調用 unsubscribe() 來解除引用關系,以避免內存泄露的發生。
Observable與Subscriber的關聯
這里subscriber
僅僅就是打印observable
發出的字符串。通過subscribe
函數就可以將我們定義的myObservable
對象和mySubscriber
對象關聯起來,這樣就完成了subscriber
對observable
的訂閱。
myObservable.subscribe(myObserver);
// 或者:
myObservable.subscribe(mySubscriber);
一旦mySubscriber
訂閱了myObservable
,myObservable
就是調用mySubscriber
對象的onNext
和onComplete
方法,mySubscriber
就會打印出Hello World!
訂閱(Subscriptions)
當調用Observable.subscribe()
,會返回一個Subscription
對象。這個對象代表了被觀察者和訂閱者之間的聯系。
Subscription subscription = Observable.just("Hello, World!")
.subscribe(s -> System.out.println(s));
你可以在后面使用這個Subscription
對象來操作被觀察者和訂閱者之間的聯系.
subscription.unsubscribe();//接觸訂閱關系
System.out.println("Unsubscribed=" + subscription.isUnsubscribed());
// Outputs "Unsubscribed=true"
RxJava的另外一個好處就是它處理unsubscribing
的時候,會停止整個調用鏈。如果你使用了一串很復雜的操作符,調用unsubscribe
將會在他當前執行的地方終止。不需要做任何額外的工作!
簡化代碼(Observable與Subscriber)
簡化Observable
:
是不是覺得僅僅為了打印一個hello world要寫這么多代碼太啰嗦?我這里主要是為了展示RxJava背后的原理而采用了這種比較啰嗦的寫法,RxJava其實提供了很多便捷的函數來幫助我們減少代碼。
首先來看看如何簡化Observable
對象的創建過程。RxJava內置了很多簡化創建Observable
對象的函數,比如Observable.just
就是用來創建只發出一個事件就結束的Observable
對象,上面創建Observable
對象的代碼可以簡化為一行:
Observable<String> myObservable = Observable.just("Hello, world!"); //發送"Hello, world!"
其他方法:
1.使用just( ),將為你創建一個Observable并自動為你調用onNext( )發射數據:
justObservable = Observable.just("just1","just2");//依次發送"just1"和"just2"
2.使用from( ),遍歷集合,發送每個item:
List<String> list = new ArrayList<>();
list.add("from1");
list.add("from2");
list.add("from3");
fromObservable = Observable.from(list); //遍歷list 每次發送一個
/** 注意,just()方法也可以傳list,但是發送的是整個list對象,而from()發送的是list的一個item** /
3.使用defer( ),有觀察者訂閱時才創建Observable,并且為每個觀察者創建一個新的Observable:
deferObservable = Observable.defer(new Func0<Observable<String>>() {
@Override
//注意此處的call方法沒有Subscriber參數
public Observable<String> call() {
return Observable.just("deferObservable");
}});
4.使用interval( ),創建一個按固定時間間隔發射整數序列的Observable,可用作定時器:
intervalObservable = Observable.interval(1, TimeUnit.SECONDS);//每隔一秒發送一次
5.使用range( ),創建一個發射特定整數序列的Observable,第一個參數為起始值,第二個為發送的個數,如果為0則不發送,負數則拋異常:
rangeObservable = Observable.range(10, 5);//將發送整數10,11,12,13,14
6.使用timer( ),創建一個Observable,它在一個給定的延遲后發射一個特殊的值,等同于Android中Handler的postDelay( )方法:
timeObservable = Observable.timer(3, TimeUnit.SECONDS); //3秒后發射一個值
7.使用repeat( ),創建一個重復發射特定數據的Observable:
repeatObservable = Observable.just("repeatObservable").repeat(3);//重復發射3次
簡化Subscriber
:
接下來看看如何簡化Subscriber,上面的例子中,我們其實并不關心OnComplete和OnError,我們只需要在onNext的時候做一些處理,這時候就可以使用Action1類。
Action1<String> onNextAction = new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
};
subscribe
方法有一個重載版本,接受三個Action1類型的參數,分別對應OnNext,OnComplete, OnError函數:
myObservable.subscribe(onNextAction, onErrorAction, onCompleteAction);
這里我們并不關心onError和onComplete,所以只需要第一個參數就可以
myObservable.subscribe(onNextAction);
// Outputs "Hello, world!"
上面的代碼最終可以寫成這樣:
Observable.just("Hello, world!")
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
});
使用java8的lambda可以使代碼更簡潔:
不熟悉Lambda的可以看我之前寫的:Java8之Lambda表達式(Android用法)
Observable.just("Hello, world!")
.subscribe(s -> System.out.println(s));
簡單解釋一下這段代碼中出現的 Action1
和 Action0
。 Action0
是 RxJava
的一個接口,它只有一個方法 call()
,這個方法是無參無返回值的;由于 onCompleted()
方法也是無參無返回值的,因此 Action0
可以被當成一個包裝對象,將 onCompleted()
的內容打包起來將自己作為一個參數傳入 subscribe()
以實現不完整定義的回調。這樣其實也可以看做將onCompleted()
方法作為參數傳進了 subscribe(),相當于其他某些語言中的『閉包』。 Action1
也是一個接口,它同樣只有一個方法 call(T param),這個方法也無返回值,但有一個參數;與 Action0
同理,由于 onNext(T obj)
和 onError(Throwable error)
也是單參數無返回值的,因此 Action1 可以將 onNext(obj) 和 onError(error)
打包起來傳入 subscribe() 以實現不完整定義的回調。事實上,雖然 Action0 和 Action1 在 API 中使用最廣泛,但 RxJava
是提供了多個 ActionX
形式的接口 (例如 Action2, Action3) 的,它們可以被用以包裝不同的無返回值的方法。
注:正如前面所提到的,Observer
和 Subscriber
具有相同的角色,而且 Observer
在 subscribe()
過程中最終會被轉換成 Subscriber
對象,因此,從這里開始,后面的描述我將用 Subscriber
來代替 Observer
,這樣更加嚴謹。
操作符(Operators)
操作符就是為了解決對Observable對象的 變換
(關鍵詞) 的問題,操作符用于在Observable和最終的Subscriber之間修改Observable發出的事件。RxJava提供了很多很有用的操作符。
比如map操作符,就是用來把把一個事件轉換為另一個事件的。
map()操作符:
Observable.just("images/logo.png") // 輸入類型 String
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String filePath) { // 參數類型 String
return getBitmapFromPath(filePath); // 返回類型 Bitmap
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) { // 參數類型 Bitmap
showBitmap(bitmap);
}
});
使用lambda
可以簡化為:
Observable.just("images/logo.png") // 輸入類型 String
.map(
filePath -> getBitmapFromPath(filePath); // 返回類型 Bitmap
)
.subscribe(
bitmap -> showBitmap(bitmap);
);
可以看到,map()
方法將參數中的 String
對象轉換成一個 Bitmap
對象后返回,而在經過 map()
方法后,事件的參數類型也由 String
轉為了 Bitmap
。這種直接變換對象并返回的,是最常見的也最容易理解的變換。不過 RxJava
的變換遠不止這樣,它不僅可以針對事件對象,還可以針對整個事件隊列,這使得 RxJava
變得非常靈活。
map()
操作符進階:
Observable.just("Hello, world!")
.map(s -> s.hashCode())
.map(i -> Integer.toString(i))
.subscribe(s -> System.out.println(s));
是不是很酷?map()
操作符就是用于變換Observable
對象的,map操作符返回一個Observable
對象,這樣就可以實現鏈式調用
,在一個Observable對象上多次
使用map操作符,最終將最簡潔的數據傳遞給Subscriber
對象。
flatMap()操作符:
假設我有這樣一個方法:
這個方法根據輸入的字符串返回一個網站的url列表
Observable<List<String>> query(String text);
Observable.flatMap()接收一個Observable的輸出作為輸入,同時輸出另外一個Observable。直接看代碼:
query("Hello, world!")
.flatMap(new Func1<List<String>, Observable<String>>() {
@Override
public Observable<String> call(List<String> urls) {
return Observable.from(urls);
}
})
.subscribe(url -> System.out.println(url));
這里我貼出了整個的函數代碼,以方便你了解發生了什么,使用lambda可以大大簡化代碼長度:
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.subscribe(url -> System.out.println(url));
flatMap()
是不是看起來很奇怪?為什么它要返回另外一個Observable
呢?理解flatMap
的關鍵點在于,flatMap
輸出的新的Observable
正是我們在Subscriber
想要接收的。現在Subscriber
不再收到List<String>
,而是收到一些列單個
的字符串,就像Observable.from()
的輸出一樣。
flatMap()
和map()
有一個相同點:它也是把傳入的參數轉化之后返回另一個對象。但需要注意,和 map()
不同的是, flatMap()
中返回的是個 Observable
對象,并且這個 Observable
對象并不是被直接發送到了 Subscriber
的回調方法中。flatMap()
的原理是這樣的:
- 使用傳入的事件對象創建一個
Observable
對象; - 并不發送這個
Observable
, 而是將它激活,于是它開始發送事件; - 每一個創建出來的
Observable
發送的事件,都被匯入同一個Observable
,而這個Observable
負責將這些事件統一交給Subscriber
的回調方法。這三個步驟,把事件拆成了兩級,通過一組新創建的Observable
將初始的對象『鋪平』
之后通過統一路徑分發了下去。而這個『鋪平』
就是flatMap()
所謂的flat
。
值得注意的是.from()
是Observable創建時候用的,.flatMap()
才是操作符;
其他操作符:
目前為止,我們已經接觸了兩個操作符,RxJava中還有更多的操作符,那么我們如何使用其他的操作符來改進我們的代碼呢?
更多RxJava的操作符請查看:RxJava操作符大全
getTitle()
返回null如果url不存在。我們不想輸出"null",那么我們可以從返回的title列表中過濾掉null值!
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(url -> getTitle(url))
.filter(title -> title != null)
.subscribe(title -> System.out.println(title));
filter()
輸出和輸入相同的元素,并且會過濾掉那些不滿足檢查條件的。
如果我們只想要最多5個結果:
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(url -> getTitle(url))
.filter(title -> title != null)
.take(5)
.subscribe(title -> System.out.println(title));
take()
輸出最多指定數量的結果。
如果我們想在打印之前,把每個標題保存到磁盤:
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(url -> getTitle(url))
.filter(title -> title != null)
.take(5)
.doOnNext(title -> saveTitle(title))
.subscribe(title -> System.out.println(title));
doOnNext()
允許我們在每次輸出一個元素之前做一些額外的事情,比如這里的保存標題。
看到這里操作數據流是多么簡單了么。你可以添加任意多的操作,并且不會搞亂你的代碼。
RxJava
包含了大量的操作符。操作符的數量是有點嚇人,但是很值得你去挨個看一下,這樣你可以知道有哪些操作符可以使用。弄懂這些操作符可能會花一些時間,但是一旦弄懂了,你就完全掌握了RxJava
的威力。
感覺如何?
好吧,你是一個懷疑主義者,并且還很難被說服,那為什么你要關心這些操作符呢?
因為操作符可以讓你對數據流做任何操作。
將一系列的操作符鏈接起來就可以完成復雜的邏輯。代碼被分解成一系列可以組合的片段。這就是響應式函數編程
的魅力。用的越多,就會越多的改變你的編程思維。
線程控制(Scheduler)
假設你編寫的Android app
需要從網絡請求數據。網絡請求需要花費較長的時間,因此你打算在另外一個線程中加載數據。那么問題來了!
編寫多線程的Android
應用程序是很難的,因為你必須確保代碼在正確的線程中運行,否則的話可能會導致app
崩潰。最常見的就是在非主線程更新UI。
在不指定線程的情況下, RxJava
遵循的是線程不變的原則,即:在哪個線程調用 subscribe()
,就在哪個線程生產事件;在哪個線程生產事件,就在哪個線程消費事件。如果需要切換線程,就需要用到 Scheduler
(調度器)。
使用RxJava,你可以使用subscribeOn()
指定觀察者代碼運行的線程,使用observerOn()
指定訂閱者運行的線程
Scheduler 的 API
在RxJava 中,Scheduler
——調度器,相當于線程控制器,RxJava
通過它來指定每一段代碼應該運行在什么樣的線程。RxJava
已經內置了幾個 Scheduler
,它們已經適合大多數的使用場景:
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
所運行在的線程。或者叫做事件消費的線程。
注意:observeOn()
指定的是 Subscriber
的線程,而這個 Subscriber
并不一定是 subscribe()
參數中的 Subscriber
(這塊參考RxJava變換部分),而是 observeOn()
執行時的當前 Observable
所對應的 Subscriber
,即它的直接下級 Subscriber
。
換句話說,observeOn()
指定的是它之后的操作所在的線程。因此如果有多次切換線程的需求,只要在每個想要切換線程的位置調用一次 observeOn()
即可。
代碼示例:
Observable.just(1, 2, 3, 4)
.subscribeOn(Schedulers.io()) // 指定 subscribe() 發生在 IO 線程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回調發生在主線程
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer number) {
Log.d(tag, "number:" + number);
}
});
上面這段代碼中,由于 subscribeOn(Schedulers.io())
的指定,被創建的事件的內容 1、2、3、4 將會在 IO
線程發出;
而由于 observeOn(AndroidScheculers.mainThread())
的指定,因此 subscriber
數字的打印將發生在主線程
。
事實上,這種在 subscribe()
之前寫上兩句subscribeOn(Scheduler.io())
和 observeOn(AndroidSchedulers.mainThread())
的使用方式非常常見,它適用于多數的 『后臺線程取數據,主線程顯示』
的程序策略。
下面的實例,在Observable.OnSubscribe
的call()
中模擬了長時間獲取數據過程,在Subscriber
的noNext()
中顯示數據到UI。
Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("info1");
SystemClock.sleep(2000);
subscriber.onNext("info2-sleep 2s");
SystemClock.sleep(3000);
subscriber.onNext("info2-sleep 3s");
SystemClock.sleep(5000);
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io()) //指定 subscribe() 發生在 IO 線程
.observeOn(AndroidSchedulers.mainThread()) //指定 Subscriber 的回調發生在主線程
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
Log.v(TAG, "onCompleted()");
}
@Override
public void onError(Throwable e) {
Log.v(TAG, "onError() e=" + e);
}
@Override
public void onNext(String s) {
showInfo(s); //UI view顯示數據
}
});
至此,我們可以看到call()
將會發生在 IO 線程,而showInfo(s)
則被設定在了主線程。這就意味著,即使加載call()
耗費了幾十甚至幾百毫秒的時間,也不會造成絲毫界面的卡頓。
值得注意:subscribeOn ()
與 observeOn()
都會返回了一個新的Observable
,因此若不是采用上面這種直接流方式,而是分步調用方式,需要將新返回的Observable
賦給原來的Observable
,否則線程調度將不會起作用。
使用下面方式,最后發現“OnSubscribe”還是在默認線程中運行;原因是subscribeOn這類操作后,返回的是一個新的Observable。
observable.subscribeOn(Schedulers.io());
observable.observeOn(AndroidSchedulers.mainThread());
observable .subscribe(subscribe);
可以修改為下面兩種方式:
observable = observable.subscribeOn(Schedulers.io());
observable = observable.observeOn(AndroidSchedulers.mainThread());
observable .subscribe(subscribe);
//OR
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscribe);
前面講到了,可以利用 subscribeOn()
結合 observeOn()
來實現線程控制,讓事件的產生和消費發生在不同的線程。可是在了解了 map()
flatMap()
等變換方法后,有些好事的(其實就是當初剛接觸 RxJava 時的我)就問了:能不能多切換幾次線程?
答案是:能。
因為 observeOn()
指定的是 Subscriber
的線程,而這個 Subscriber
并不是(嚴格說應該為『不一定是』,但這里不妨理解為『不是』)subscribe()
參數中的 Subscriber
,而是 observeOn()
執行時的當前 Observable
所對應的 Subscriber
,即它的直接下級 Subscriber
。換句話說,observeOn()
指定的是它之后的操作所在的線程。因此如果有多次切換線程的需求,只要在每個想要切換線程的位置調用一次 observeOn()
即可。上代碼:
Observable.just(1, 2, 3, 4) // IO 線程,由 subscribeOn() 指定
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.map(mapOperator) // 新線程,由 observeOn() 指定
.observeOn(Schedulers.io())
.map(mapOperator2) // IO 線程,由 observeOn() 指定
.observeOn(AndroidSchedulers.mainThread)
.subscribe(subscriber); // Android 主線程,由 observeOn() 指定
如上,通過 observeOn() 的多次調用,程序實現了線程的多次切換。
不過,不同于 observeOn()
, subscribeOn()
的位置放在哪里都可以,但它是只能調用一次的。
又有好事的(其實還是當初的我)問了:如果我非要調用多次 subscribeOn()
呢?會有什么效果?
這個問題先放著,我們還是從 RxJava
線程控制的原理說起吧。
Scheduler 的原理
其實, subscribeOn()
和 observeOn()
的內部實現,也是用的 lift()
。具體看圖(不同顏色的箭頭表示不同的線程):
subscribeOn()
原理圖:
observeOn()
原理圖:
從圖中可以看出,subscribeOn()
和 observeOn()
都做了線程切換的工作(圖中的 "schedule..." 部位)。不同的是, subscribeOn()
的線程切換發生在 OnSubscribe
中,即在它通知上一級 OnSubscribe
時,這時事件還沒有開始發送,因此 subscribeOn()
的線程控制可以從事件發出的開端就造成影響;而 observeOn()
的線程切換則發生在它內建的 Subscriber
中,即發生在它即將給下一級 Subscriber
發送事件時,因此 observeOn()
控制的是它后面的線程。
最后,我用一張圖來解釋當多個 subscribeOn()
和 observeOn()
混合使用時,線程調度是怎么發生的(由于圖中對象較多,相對于上面的圖對結構做了一些簡化調整):
圖中共有 5 處含有對事件的操作。由圖中可以看出,①和②兩處受第一個 subscribeOn()
影響,運行在紅色線程;③和④處受第一個 observeOn()
的影響,運行在綠色線程;⑤處受第二個 onserveOn()
影響,運行在紫色線程;而第二個 subscribeOn()
,由于在通知過程中線程就被第一個 subscribeOn()
截斷,因此對整個流程并沒有任何影響。這里也就回答了前面的問題:當使用了多個 subscribeOn()
的時候,只有第一個 subscribeOn()
起作用。
延伸:doOnSubscribe()
doOnSubscribe()
一般用于執行一些初始化操作.
然而,雖然超過一個的 subscribeOn()
對事件處理的流程沒有影響,但在流程之前卻是可以利用的。
在前面講 Subscriber
的時候,提到過 Subscriber
的 onStart()
可以用作流程開始前的初始化。然而 onStart()
由于在 subscribe()
發生時就被調用了,因此不能指定線程,而是只能執行在 subscribe()
被調用時的線程。這就導致如果 onStart()
中含有對線程有要求的代碼(例如在界面上顯示一個 ProgressBar
,這必須在主線程執行),將會有線程非法的風險,因為有時你無法預測 subscribe()
將會在什么線程執行。
而與 Subscriber.onStart()
相對應的,有一個方法 Observable.doOnSubscribe()
。它和 Subscriber.onStart()
同樣是在 subscribe()
調用后而且在事件發送前執行,但區別在于它可以指定線程。默認情況下, doOnSubscribe()
執行在 subscribe()
發生的線程;而如果在 doOnSubscribe()
之后有 subscribeOn()
的話,它將執行在離它最近的 subscribeOn()
所指定的線程。
示例:
Observable.create(onSubscribe)
.subscribeOn(Schedulers.io())
.doOnSubscribe(new Action0() {
@Override
public void call() {
progressBar.setVisibility(View.VISIBLE); // 需要在主線程執行
}
})
.subscribeOn(AndroidSchedulers.mainThread()) // 指定主線程
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
如上,在 doOnSubscribe()
的后面跟一個 subscribeOn()
,就能指定準備工作的線程了。
RxJava 的適用場景和使用方式
RxJava + Retrofit
Retrofit 是 Square 的一個著名的網絡請求庫。對于
Retrofit
不了解的同學
可以參考我之前寫的文章:全新的網絡加載框架Retrofit2,上位的小三
Retrofit
除了提供了傳統的 Callback
形式的 API
,還有 RxJava
版本的 Observable
形式 API
。下面我用對比的方式來介紹 Retrofit
的 RxJava
版 API
和傳統版本的區別。
以獲取一個 MovieEntity
對象的接口作為例子。使用Retrofit
的傳統 API,你可以用這樣的方式來定義請求:
@GET("top250")
Call<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);//正常返回Call對象
我們來寫getMovie
方法的代碼:
//進行網絡請求
private void getMovie(){
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
MovieService movieService = retrofit.create(MovieService.class);
Call<MovieEntity> call = movieService.getTopMovie(0, 10);
call.enqueue(new Callback<MovieEntity>() {
@Override
public void onResponse(Call<MovieEntity> call, Response<MovieEntity> response) {
resultTV.setText(response.body().toString());
}
@Override
public void onFailure(Call<MovieEntity> call, Throwable t) {
resultTV.setText(t.getMessage());
}
});
}
以上為沒有經過封裝的、原生態的Retrofit
寫網絡請求的代碼。
而使用 RxJava
形式的 API
,定義同樣的請求是這樣的:
@GET("top250")
Observable<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);//RxJava返回Observable對象
Retrofit
本身對Rxjava
提供了支持,getMovie
方法改為:
//進行網絡請求
private void getMovie(){
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//提供RXjava支持
.build();
MovieService movieService = retrofit.create(MovieService.class);
movieService.getTopMovie(0, 10)//返回Observable對象
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MovieEntity>() {
@Override
public void onCompleted() {
Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
resultTV.setText(e.getMessage());
}
@Override
public void onNext(MovieEntity movieEntity) {
resultTV.setText(movieEntity.toString());
}
});
}
這樣基本上就完成了Retrofit
和Rxjava
的結合,大家可以自己進行封裝;那么用上了RxJava
,我們就可以用它強大的操作符
來對數據進行處理和操作,各位看官可以具體去實現,我在這里不做多做贅述。
參考文章:RxJava 與 Retrofit 結合的最佳實踐
RxBinding
RxBinding
是 Jake Wharton
的一個開源庫,它提供了一套在 Android
平臺上的基于 RxJava
的 Binding API
。所謂 Binding
,就是類似設置 OnClickListener
、設置 TextWatcher
這樣的注冊綁定對象的 API
。
舉個設置點擊監聽的例子。使用 RxBinding
,可以把事件監聽用這樣的方法來設置:
Button button = ...;
RxView.clickEvents(button) // 以 Observable 形式來反饋點擊事件
.subscribe(new Action1<ViewClickEvent>() {
@Override
public void call(ViewClickEvent event) {
// Click handling
}
});
看起來除了形式變了沒什么區別,實質上也是這樣。甚至如果你看一下它的源碼,你會發現它連實現都沒什么驚喜:它的內部是直接用一個包裹著的 setOnClickListener()
來實現的。然而,僅僅這一個形式的改變,卻恰好就是 RxBinding
的目的:擴展性。通過 RxBinding
把點擊監聽轉換成 Observable
之后,就有了對它進行擴展的可能。擴展的方式有很多,根據需求而定。一個例子是前面提到過的 throttleFirst()
操作符,用于去抖動,也就是消除手抖導致的快速連環點擊:
RxView.clickEvents(button)
.throttleFirst(500, TimeUnit.MILLISECONDS)
.subscribe(clickAction);
如果想對 RxBinding
有更多了解,可以去它的 GitHub
項目 下面看看。
RxLifecyle
RxLifecycle
配合 Activity/Fragment
生命周期來管理訂閱的。 由于 RxJava
Observable
訂閱后(調用 subscribe
函數),一般會在后臺線程執行一些操作(比如訪問網絡請求數據),當后臺操作返回后,調用 Observer
的 onNext
等函數,然后在 更新 UI 狀態。 但是后臺線程請求是需要時間的,如果用戶點擊刷新按鈕請求新的微博信息,在刷新還沒有完成的時候,用戶退出了當前界面返回前面的界面,這個時候刷新的 Observable
如果不取消訂閱,則會導致之前的 Activity
無法被 JVM
回收導致內存泄露。 這就是 Android
里面的生命周期管理需要注意的地方,RxLifecycle
就是用來干這事的。比如下面的示例:
myObservable
.compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
.subscribe();
這樣Activity
在destroy
的時候就會自動取消這個observer
RxBus
RxBus
并不是一個庫,而是一種模式。相信大多數開發者都使用過EventBus
或者Otto
,作為事件總線通信庫,如果你的項目已經加入RxJava
和EventBus
,不妨用RxBus
代替EventBus
,以減少庫的依賴。RxJava
也可以輕松實現事件總線,因為它們都依據于觀察者模式。
拓展鏈接:
用RxJava實現事件總線(Event Bus)
[深入RxBus]:支持Sticky事件
RxPermission
RxPermission
是基于RxJava
開發的用于幫助在Android 6.0
中處理運行時權限檢測的框架。在Android 6.0
中,系統新增了部分權限的運行時動態獲取。而不再是在以前的版本中安裝的時候授予權限。
拓展鏈接:
使用RxPermission框架對android6.0權限進行檢測
總結
簡而言之Rxjava
是一個很牛逼的庫,如果你的項目中還沒有使用RxJava
的話,建議可以嘗試去集成使用;對大多數人而已RxJava
是一個比較難上手的庫了,不亞于Dagger
的上手難度;不過當你認識學習使用過了,你就會發現RxJava
的魅力所在;如果看一遍沒有看懂的童鞋,建議多看幾次;動手寫寫代碼,我想信本文可以給到你們一些幫助;你們真正的體會到什么是 從入門到放棄再到不離不棄
;這就是RxJava
的魅力所在。
拓展閱讀:
給 Android 開發者的 RxJava 詳解 - 拋物線
如果有疑問和見解,也歡迎大家在下面留言,我會一一回復大家
以上