Android進階學習(4)-- 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方法。

發佈了81 篇原創文章 · 獲贊 49 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章