RxJava淺析
前言
RxJava可謂是Android開發人員必備技能,本次總結主要分爲兩部分:基礎概念,源碼跟蹤分析。
RxJava基礎
在學習RxJava時,我們需要先了解一些基礎概念。目前RxJava1.x基本都不在使用,博客內容都以RxJava2.x進行總結。
觀察者模式
觀察者模式,是RxJava的核心思想。首先,我們自己用代碼寫一個觀察者模式,非常簡單的場景,微信公衆號的訂閱。如果我們訂閱了微信公衆號,那麼每當公衆號有新的推文都會通知我們。下面我們一步步實現:
首先,我們抽象出抽象被觀察者和抽象觀察者
抽象被觀察者Observable,它需要具備 訂閱、解除訂閱、推送消息三個功能
// T 爲消息類型, K 爲訂閱者類型 爲了複習下之前講過的泛型 這裏稍微用一下
public interface Observable<T,K> {
//添加訂閱者
void subscribe(K k);
//移除訂閱者
void unSubscribe(K k);
//推送消息
void pushArticle(T t);
}
抽象觀察者Observer,它只需要有一個接受推送的功能即可
//T 爲消息類型
public interface Observer<T> {
//接受推送消息
void update(T t);
}
抽象接口都定義完成後,我們根據實際需求創建具體被觀察者和具體觀察者
具體被觀察者就是我們的微信公衆號WeChat,它需要有一個訂閱者清單,推送文章時要告訴每一個訂閱者:
public class WeChat<T> implements Observable<T , User> {
String TAG = "WeChat";
ArrayList<User> userList;
//構造器中初始化訂閱者列表
public WeChat(){
userList = new ArrayList<>();
}
//添加訂閱者
@Override
public void subscribe(User user) {
userList.add(user);
}
//移除訂閱者
@Override
public void unSubscribe(User user) {
userList.remove(user);
}
//推送文章並且通知訂閱者
@Override
public void pushArticle(T t) {
Log.e(TAG, "公衆號推文: " + t.toString());
notifyAllUser(t.toString());
}
//通知所有訂閱者
public void notifyAllUser(String s){
for (User user : userList){
user.update(s);
}
}
}
具體觀察者就是訂閱用戶User,它只需要一個用戶名和接受消息的方法即可
public class User implements Observer<String> {
String userName;
public User(String name){
userName = name;
}
@Override
public void update(String s) {
Log.e(userName, "接受到推文:" + s);
}
}
基礎代碼編寫完成,我們來模擬一下訂閱,解除訂閱,推送消息:
//實例化 被觀察者
WeChat<String> weChat = new WeChat<>();
//實例化 三個User
User jack = new User("Jack");
User bill = new User("Bill");
User mike = new User("Mike");
//三個User 都訂閱了 公衆號
weChat.subscribe(jack);
weChat.subscribe(bill);
weChat.subscribe(mike);
//公衆號 更新推文
weChat.pushArticle("今年情人節送腦白金");
// Mike 接觸訂閱 不再接受新的消息
weChat.unSubscribe(mike);
// 公衆號 更新推文
weChat.pushArticle("android進階密集");
輸出結果:
第一次推送,三個用戶都訂閱了且全部收到推文;第二次推送,因爲用戶Mike解除了訂閱,所有隻有兩個用戶接收到了推送消息。
這是一個非常簡單的觀察者模式。
裝飾器模式
裝飾器模式,在RxJava中有用到,這裏就簡單說一下,在裝飾器模式下會對當前對象進行包裝,加一些額外的方法再返回包裝後的對象。
背壓
背壓,也是RxJava中會處理的情況。RxJava的消息流可以看成上游和下游,上游發送消息,下游處理消息;正常情況下,上游發送一條數據,下游處理一條,如果下游處理的速度小於上游發送的速度,那麼就會形成阻塞;上游發了100條數據,而下游只處理了10條,那麼剩餘沒處理的消息就可能造成內存溢出。RxJava2.x提供了處理背壓的策略,下面會總結到。
RxJava的 “冷” 與 “熱”
"冷"Cold Observable
是指被觀察者Observable和觀察者Observer之間的關係是 一對一的,事件是相互獨立的。舉個例子:上游發送了10條數據。選擇有三個觀察者 A B C,A 和 B呢,從第一條就開始訂閱了,那麼A,B接受的數據就是 數據1、數據2…數據10;比方說上游發送到第三條數據的時候,C也訂閱了,那麼 A,B接受的數據不影響,C則從第一條開始接受,而且C接受到的數據是:數據1、數據2…數據7;
"熱"Hot Observable
是指被觀察者Observable和觀察者Observer之間的關係是共享的。還是上面的例子說明,AB依然是從第一次發送數據就訂閱,C仍然從第三次發送數據才訂閱;AB最終接受的數據仍然是數據1…數據10,而C接受的數據變成了數據3…數據10。
RxJava中五種觀察者模式
我們先來看一下最常用的Observable<T>
他是如何實現觀察者模式的,一般我們都這樣使用:
Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
//do somethings
}
});
通過Observable.create創建了一個被觀察者,我們來看一下create到底做了什麼:
注意這個create方法,這裏就用到了前面說的裝飾器模式,這個方法返回的就是包裝後的Observable,也就是ObservableOnSubscribe,後面線程切換那裏還會說到。
這裏看類名也可以看出,實際上是創建了一個發射器,我們看一下發射器類提供的方法:
通過觀察發射器的方法,可以得出,在RxJava中的觀察者模式跟我們之前的例子中的觀察者模式是有所不同的,我們上面的微信公衆號例子中,事件是直接由被觀察者發送通知觀察者,而RxJava中實際上就是由發射器去通知各個觀察者。
我們再看一下Single<T>
的源碼:
可以看到Single也是一個抽象類,據上面的Observable源碼推斷,它應該也是由create方法去創建發射器,我們來找一下create方法:
點進去SingleCreate方法:
我們發現它只提供了onSuccess和onError方法,也就意味着它只能發送一個事件,或者發送錯誤事件。
RxJava 線程調度源碼流程深入分析
RxJava最便捷的操作就是它提供的線程切換,使用鏈式編程只需要短短的代碼就能實現線程切換,我們來看下面一段代碼,在被觀察者發送數據時加入了線程調度,子線程發送事件,主線程接受事件:
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
Log.e("發送數據線程:", Thread.currentThread().getName());
emitter.onNext("data");
emitter.onComplete();
}
})
//定義發送事件線程
.subscribeOn(Schedulers.io())
//定義接受事件線程
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String s) {
Log.e("接受數據線程:", Thread.currentThread().getName());
Log.e("onNext:", "接受數據: " + s);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
結果輸出:
就添加了這麼兩個方法就實現了線程切換,那麼RxJava是如何實現的?首先,我們先熟悉一下上面那段代碼對於RxJava的調用流程:
這裏面的每次調用,返回的都是包裝後的對象,也就是上面說的裝飾器模式。我們來根據源碼看一下:
最先調用的Observable.create上面已經說到了,這裏再說一次:
Observable調用create方法,返回的是一個ObservableCreate類型對象。然後,我們接着看我們代碼接着調用的subscribeOn,指定發送事件的線程,我們點進去源碼:
調用subscribeOn後,又返回了一個包裝後的對象ObservableSubscribeOn,注意它的參數,裏面的this實際就是ObservableCreate。
接着,我們看observeOn的源碼:
observerOn又調用了它的一個構造器:
調用observerOn之後,又包裝了一層,返回的是ObservableObserveOn對象。這也就說明,我們之前的代碼,鏈式調用最後一步調用訂閱方法subscribe實際上調用的是ObservableObserveOn裏面的subscribe方法。
但是,我們發現ObservableObserveOn裏面並沒有subscribe方法,這是爲什麼?我們看一下ObservableObserveOn的繼承關係:
通過以上源碼片段,我們能分析出,subscribe調用的是父類中的subscribe,而父類中的subscribe又調用了抽象方法subscribeActual,ObservableObserveOn實現了這個方法:
圖中已經標註了對應的操作說明,那麼下游事件是如何處理的?注意看else裏面的subscribe方法中的ObserverOnObserver:
ObserverOnObserver方法中對應的onNext以及其他的模板方法就是下游的處理:
我們通過跟蹤源碼,已經熟悉了Rxjava的調用流程,那麼RxJava中的線程是如何進行切換的?我們就以我們寫的代碼中的切換爲例,首先我們要了解RxJava中線程的實現:
Rxjava定義的線程都是繼承自Schedulers,對其方法進行實現,我們就以我們代碼中的兩個線程IO線程,和 Android主線程 的源碼 來分析一下:
我們先跟蹤一下 Schedulers.io()的源碼:
IO線程確實是繼承了Scheduler,那麼它是如何創建線程的?我們繼續跟蹤源碼
看到這裏我們大致可以推斷出,IO線程本質上就是創建了線程池,將任務提交進去執行。
在Scheduler中有一個重要的方法,也就是創建線程的方法,上面我們跟蹤RxJava調用流程源碼中也看到過的方法:
我們來看一下IO線程是怎麼實現這個方法的,IoScheduler中:
我們先看一下這個EventLoopWorker類中的方法:
很明顯這個schedule就是執行任務的方法,我們繼續跟蹤下去:
看到沒有,本質上就是將任務提交到線程池中去執行。
看完了IO線程的創建,我們來跟蹤一下AndroidSchedulers.mainThread()
的源碼:
依舊是同樣的套路:
我們進入到HandlerScheduler中,因爲剛剛說了,RxJava中定義的線程都是繼承的Schedluer,那麼HandlerScheduler一定也實現了createworker方法,我們看一下它是如何處理的:
我們依然去看schedule方法實現:
是不是很熟悉的代碼,也就是說切換到主線程,其實本質上就是通過包裝一個Message,通過Handler發送到主線程去。
關於線程這塊的類關係圖:
猛的看上去非常的亂,但是別怕,每個類源碼點進去看一下它是如何創建線程, 看一下是如何執行任務,慢慢看一下並不難理解。
RxJava 操作符源碼跟蹤
RxJava的操作符實在太多了,就舉例說一個比較簡單的 map操作符,就以上面的線程切換代碼進行改造:
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
Log.e("發送數據線程:", Thread.currentThread().getName());
emitter.onNext("data");
emitter.onComplete();
}
})
//這裏加入map操作符,對上游發送的數據進行改造
.map(new Function<String, String>() {
@Override
public String apply(String s) throws Exception {
return s + "-shy";
}
})
//定義發送事件線程
.subscribeOn(Schedulers.io())
//定義接受事件線程
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String s) {
Log.e("接受數據線程:", Thread.currentThread().getName());
Log.e("onNext:", "接受數據: " + s);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
我們看一下輸出結果:
學會了map的使用,我們來看一下map的源碼實現:
這裏還是用到了裝飾者模式,我們繼續跟蹤:
在ObservableMap中,我們看到了熟悉的AbstractObservableWithUpstream和subscribeActual,在RxJava中基本所有的操作符都是繼承自AbstractObservableWithUpstream實現的,然後重寫父類的抽象方法subscribeActual,在這個方法中做對應的操作。在上面的代碼段中,我們看到subscribeActual方法中只有一句代碼,我們注意下它傳入的參數:
t 很明顯就是本身被觀察者
function 則是上一層傳入的,我們回到上一層方法中:
mapper 實際上就是一個方法,也就是我們自己的代碼中傳入的具體轉換操作代碼。
那麼操作代碼是在哪裏執行的?我們繼續看
本質上,ObservableMap就是重寫了onNext方法,調用onNext之前 增加了額外對應的操作。
RxJava 背壓策略
上面之總結了下背壓的概念,接下來了解一下RxJava是如何處理背壓的,我們看以下一個場景:
//背壓測試
Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
for (int i = 0 ;i < 1000; i++){
emitter.onNext(i);
}
//emitter.onComplete();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Object>() {
@Override
public void onSubscribe(Disposable d) {
Log.e("onSubscribe","onSubscribe");
}
@Override
public void onNext(Object o) {
try {
Thread.sleep(100);
Log.e("下游處理數據",o+"");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable e) {
Log.e("onError","" + e.getMessage());
}
@Override
public void onComplete() {
Log.e("onComplete","onComplete");
}
});
上游發送數據非常快,下游處理數據慢就會導致上游發送的未處理的數據堆積在內存中,這是非常容易報錯異常的。
性能分析:
爲了解決這一問題,RxJava給我們提供了Flowable來支持背壓操作,我們對上面的代碼進行改造:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0 ;i<128; i++){
emitter.onNext(i);
}
emitter.onComplete();
}
//BackpressureStrategy.ERROR 是一種背壓策略 下面會總結策略
}, BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>(){
@Override
public void onSubscribe(Subscription s) {
Log.e("onSubscribe","onSubscribe");
//設置背壓數量
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer o) {
try {
Thread.sleep(100);
Log.e("下游處理數據",o+"");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
Log.e("onError","" + t.getMessage());
}
@Override
public void onComplete() {
Log.e("onComplete","onComplete");
}
});
我們看以下輸出結果:
運行沒有任何問題,但是需要注意!我上面的循環設置的是i < 128
,這樣是因爲Flowable不設置Subscription的情況下,默認最大支持的背壓是128,超過128就會走onError事件,注意 這裏並不會閃退,而是拋出異常走onError事件。我們根據源碼來看一下:
我們注意以下這個bufferSize()
這也就是說 如果你的背壓不超過128,隨便搞,都不會出問題。
接下來,我們來看一下RxJava中的幾種背壓策略:
一般我們處理背壓則會根據業務邏輯在合適的時間處理數據,定義一個Subscription,用request方法取數據。
對上面代碼做如下修改:
//注意 request 方法中要從1開始取
int requetCount = 1;
TextView tvTest;
Subscription subscription;
tvTest = findViewById(R.id.tvTest);
tvTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
subscription.request(requetCount++);
}
});
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0 ;i<128; i++){
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>(){
@Override
public void onSubscribe(Subscription s) {
Log.e("onSubscribe","onSubscribe");
subscription = s;
}
@Override
public void onNext(Integer o) {
try {
Thread.sleep(100);
Log.e("下游處理數據",o+"");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
Log.e("onError","" + t.getMessage());
}
@Override
public void onComplete() {
Log.e("onComplete","onComplete");
}
});
我們看一下結果:
每當我單擊TextView 下游就會處理數據。策略用Error的話,背壓超過128會直接走onError方法。