大話RxJava:一、初識RxJava與基本運用

iamxiarui_發表於2018-07-02

寫在前面

關注RxJava已經有很久一段時間了,因為當你有一天開啟技術論壇、開啟Github、開啟簡書的時候滿屏都是各種Rx的時候,心裡是很慌的。所以趁著結課大作業全部搞定後,靜下心花了幾天時間系統地學習了一下RxJava。

現在網上有各種優秀的部落格或文件來講解RxJava,最出名的莫過於扔物線老師(暫且稱為老師吧...)的這篇教程了,我也是看這篇文章入門的,強推。

給 Android 開發者的 RxJava 詳解——扔物線

當然也有很多很多優秀的教程,在文末我會加連結。但是從我這幾天學習的過程和經驗來看,教程不在多,而在於吃透。所以儘管網上已經有了很多優秀的教程了,但在這裡我還是想把自己學習的過程記錄下來。

因為這個RxJava內容不算少,而且應用場景非常廣,所以這個大話RxJava應該會寫很久。

今天就來先來個入門RxJava吧,下面是本文的目錄:

  • 寫在前面
  • 初識RxJava
    • 什麼是Rx
    • 什麼是RxJava
    • 擴充套件的觀察者模式
  • 如何實現RxJava
    • 建立Observer
    • 建立Observable
    • 訂閱(Subscribe)
  • 執行緒控制——Scheduler
  • 第一個RxJava案例
  • 總結
  • 參考資料
  • 專案原始碼

初識RxJava

什麼是Rx

很多教程在講解RxJava的時候,上來就介紹了什麼是RxJava。這裡我先說一下什麼是Rx,Rx就是ReactiveX,官方定義是:

Rx是一個函式庫,讓開發者可以利用可觀察序列和LINQ風格查詢操作符來編寫非同步和基於事件的程式

一臉懵B

看到這個定義我只能呵呵,稍微通俗點說是這樣的:

Rx是微軟.NET的一個響應式擴充套件。Rx藉助可觀測的序列提供一種簡單的方式來建立非同步的,基於事件驅動的程式。

這個有點清晰了,至少看到我們熟悉的非同步事件驅動,所以簡單點且不準確地來說:

Rx就是一種響應式程式設計,來建立基於事件的非同步程式

注意,這個定義是不準確的,但是對於初學者來說,已經可以有個基本的認知了。

另外還有一點就是Rx其實是一種程式設計思想,用很多語言都可以實現,比如RxJava、RxJS、RxPHP等等。而現在我們要說的就是RxJava。

RxJava是什麼

二話不說,先上定義:

RxJava就是一種用Java語言實現的響應式程式設計,來建立基於事件的非同步程式

有人問你這不是廢話麼,好吧那我上官方定義:

一個在 Java VM 上使用可觀測的序列來組成非同步的、基於事件的程式的庫

反正我剛看這句話的時候也呵呵了,當然現在有所領悟了。

除此之外,扔物線老師總結的就更精闢了:非同步,它就是一個實現非同步操作的庫。

擴充套件的觀察者模式

對於普通的觀察者模式,這裡我就不細說了。簡單概括就是,**觀察者(Observer)需要在被觀察者(Observable)**變化的一瞬間做出反應。

而兩者通過**註冊(Register)或者訂閱(Subscribe)**的方式進行繫結。

就拿拋物線老師給的例子來說,我豐富了一下如圖所示:

觀察者模式

其中這個Button就是被觀察者(Observable),OnClickListener就是觀察者(Observer),兩者通過setOnClickListener達成訂閱(Subscribe)關係,之後當Button產生OnClick事件的時候,會直接傳送給OnClickListener,它做出相應的響應處理。

當然還有其他的例子,比如Android四大元件中的ContentProvider與ContentObserver之間也存在這樣的關係。

而RxJava的觀察者模式呢,跟這個差不多,但是也有幾點差別:

  • Observer與Observable是通過 subscribe() 來達成訂閱關係。
  • RxJava中事件回撥有三種:onNext()onCompleted()onError()
  • 如果一個Observerble沒有任何的Observer,那麼這個Observable是不會發出任何事件的。

其中關於第三點,這裡想說明一下,在Rx中,其實Observable有兩種形式:熱啟動Observable和冷啟動Observable。

熱啟動Observable任何時候都會傳送訊息,即使沒有任何觀察者監聽它。

冷啟動Observable只有在至少有一個訂閱者的時候才會傳送訊息

這個地方雖然對於初學者來說區別不大,但是要注意一下,所以上面的第三點其實就針對於冷啟動來說的。

另外,關於RxJava的回撥事件,拋物線老師總結的很好,我就不班門弄斧了:

  • onNext():基本事件。
  • onCompleted(): 事件佇列完結。RxJava 不僅把每個事件單獨處理,還會把它們看做一個佇列。RxJava 規定,當不會再有新的 onNext() 發出時,需要觸發 onCompleted() 方法作為標誌。
  • onError(): 事件佇列異常。在事件處理過程中出異常時,onError() 會被觸發,同時佇列自動終止,不允許再有事件發出。

值得注意的是在一個正確執行的事件序列中, onCompleted() 和 onError() 有且只有一個,並且是事件序列中的最後一個。如果在佇列中呼叫了其中一個,就不應該再呼叫另一個。

好了,那我們也附一張圖對比一下吧:

RxJava觀察者模式

如何實現RxJava

關於實現RxJava的步驟,扔物線老師已經說的非常好了,這裡我就大體總結概括一下。

建立Observer

在Java中,一想到要建立一個物件,我們馬上就想要new一個。沒錯,這裡我們也是要new一個Observer出來,其實就是實現Observer的介面,注意String是接收引數的型別:

//建立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());
	}
};
複製程式碼

當然這裡也要提另外一個介面:Subscriber ,它跟Observer介面幾乎完全一樣,只是多了兩個方法,看看扔物線老師的總結:

  • onStart(): 它會在 subscribe 剛開始,而事件還未傳送之前被呼叫,可以用於做一些準備工作,例如資料的清零或重置。這是一個可選方法,預設情況下它的實現為空。需要注意的是,如果對準備工作的執行緒有要求(例如彈出一個顯示進度的對話方塊,這必須在主執行緒執行), onStart() 就不適用了,因為它總是在 subscribe 所發生的執行緒被呼叫,而不能指定執行緒。
  • unsubscribe(): 用於取消訂閱。在這個方法被呼叫後,Subscriber 將不再接收事件。一般在這個方法呼叫前,可以使用 isUnsubscribed() 先判斷一下狀態。 要在不再使用的時候儘快在合適的地方(例如 onPause() onStop() 等方法中)呼叫 unsubscribe() 來解除引用關係,以避免記憶體洩露的發生。

雖然多了兩個方法,但是基本實現方式跟Observer是一樣的,所以暫時可以不考慮兩者的區別。不過值得注意的是:

實質上,在 RxJava 的 subscribe 過程中,Observer 也總是會先被轉換成一個 Subscriber 再使用。

建立Observable

與Observer不同的是,Observable是通過 create() 方法來建立的。注意String是傳送引數的型別:

//建立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();
    }
});
複製程式碼

關於這其中的流程,我們暫且不考慮,在下一篇的原始碼解析中,我們再詳細說明。

訂閱(Subscribe)

在之前,我們建立了 Observable 和 Observer ,現在就需要用 subscribe() 方法來將它們連線起來,形成一種訂閱關係:

//訂閱
observable.subscribe(observer);
複製程式碼

這裡其實確實有點奇怪,為什麼是Observable(被觀察者)訂閱了Observer(觀察者)呢?其實我們想一想之前Button的點選事件:

Button.setOnClickListener(new View.OnClickListener())
複製程式碼

Button是被觀察者,OnClickListener是觀察者,setOnClickListener是訂閱。我們驚訝地發現,也是被觀察者訂閱了觀察者,所以應該是一種流式API的設計吧,也沒啥影響。

完整程式碼如下:

	//建立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());
        }
    };

    //建立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);
複製程式碼

執行的結果如下,可以看到Observable中傳送的String已經被Observer接收並列印了出來:

基本實現

執行緒控制——Scheduler

好了,這裡就是RxJava的精髓之一了。

在RxJava中,Scheduler相當於執行緒控制器,可以通過它來指定每一段程式碼執行的執行緒。

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。

  • AndroidSchedulers.mainThread(),Android專用執行緒,指定操作在主執行緒執行。

那我們如何切換執行緒呢?RxJava中提供了兩個方法:subscribeOn()observeOn() ,兩者的不同點在於:

  • subscribeOn(): 指定subscribe() 訂閱所發生的執行緒,即 call() 執行的執行緒。或者叫做事件產生的執行緒。
  • observeOn(): 指定Observer所執行在的執行緒,即onNext()執行的執行緒。或者叫做事件消費的執行緒。

具體實現如下:

//改變執行的執行緒
observable.subscribeOn(Schedulers.io());
observable.observeOn(AndroidSchedulers.mainThread());
複製程式碼

這裡確實不好理解,沒關係,下面我們在具體例子中觀察現象。

而這其中的原理,會在之後的原始碼級分析的文章中詳細解釋,現在我們暫且擱下。

第一個RxJava案例

好了,當看完之前的所有基礎東西,現在我們就完全可以寫一個基於RxJava的Demo了。

這裡我們用一個基於RxJava的非同步載入網路圖片來演示。

由於重點在於RxJava對於非同步的處理,所以關於如何通過網路請求獲取圖片,這裡就不詳細說明了。

另外這裡採用的是鏈式呼叫,併為重要位置打上Log日誌,觀察方法執行的所線上程。

首先需要新增依賴,這沒什麼好說的:

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    ...
    compile 'io.reactivex:rxjava:1.1.6'
    
}
複製程式碼

然後按照步驟來,首先通過create建立Observable,注意傳送引數的型別是Bitmap:

//建立被觀察者
Observable.create(new Observable.OnSubscribe<Bitmap>() {
	/**
	* 複寫call方法
	*
	* @param subscriber 觀察者物件
	*/
	@Override
	public void call(Subscriber<? super Bitmap> subscriber) {
		//通過URL得到圖片的Bitmap物件
		Bitmap bitmap = GetBitmapForURL.getBitmap(url);
		//回撥觀察者方法
		subscriber.onNext(bitmap);
		subscriber.onCompleted();
		Log.i(" call ---> ", "執行在 " + Thread.currentThread().getName() + " 執行緒");
	}
})
複製程式碼

然後我們需要建立Observer,並進行訂閱,這裡是鏈式呼叫

.subscribe(new Observer<Bitmap>() {   //訂閱觀察者(其實是觀察者訂閱被觀察者)

	@Override
    public void onNext(Bitmap bitmap) {
    	mainImageView.setImageBitmap(bitmap);
        Log.i(" onNext ---> ", "執行在 " + Thread.currentThread().getName() + " 執行緒");
    }

    @Override
    public void onCompleted() {
    	mainProgressBar.setVisibility(View.GONE);
        Log.i(" onCompleted ---> ", "完成");
    }

    @Override
    public void onError(Throwable e) {
    	Log.e(" onError --->", e.toString());
    }
 });
複製程式碼

當然網路請求是耗時操作,我們需要在其他執行緒中執行,而更新UI需要在主執行緒中執行,所以需要設定執行緒:

.subscribeOn(Schedulers.io()) // 指定subscribe()發生在IO執行緒
.observeOn(AndroidSchedulers.mainThread()) // 指定Subscriber的回撥發生在UI執行緒
複製程式碼

這樣我們就完成了一個RxJava的基本編寫,現在整體看一下程式碼:

//建立被觀察者
Observable.create(new Observable.OnSubscribe<Bitmap>() {
	/**
	* 複寫call方法
	*
	* @param subscriber 觀察者物件
	*/
	@Override
	public void call(Subscriber<? super Bitmap> subscriber) {
		//通過URL得到圖片的Bitmap物件
		Bitmap bitmap = GetBitmapForURL.getBitmap(url);
		//回撥觀察者方法
		subscriber.onNext(bitmap);
		subscriber.onCompleted();
		Log.i(" call ---> ", "執行在 " + Thread.currentThread().getName() + " 執行緒");
	}
})
.subscribeOn(Schedulers.io()) // 指定subscribe()發生在IO執行緒
.observeOn(AndroidSchedulers.mainThread()) // 指定Subscriber的回撥發生在UI執行緒
.subscribe(new Observer<Bitmap>() {   //訂閱觀察者(其實是觀察者訂閱被觀察者)

	@Override
    public void onNext(Bitmap bitmap) {
    	mainImageView.setImageBitmap(bitmap);
        Log.i(" onNext ---> ", "執行在 " + Thread.currentThread().getName() + " 執行緒");
    }

    @Override
    public void onCompleted() {
    	mainProgressBar.setVisibility(View.GONE);
        Log.i(" onCompleted ---> ", "完成");
    }

    @Override
    public void onError(Throwable e) {
    	Log.e(" onError --->", e.toString());
    }
 });
複製程式碼

好了,下面是執行的動態圖:

RxJava非同步載入網路圖片

現在來看一下執行的Log日誌:

Log

可以看到,call方法(事件產生)執行在IO執行緒,而onNext方法(事件消費)執行在main執行緒。說明之前分析的是對的。

總結

好了,由於本文是一個RxJava的基礎,所以篇幅稍微過長了點。即使這樣,很多細節性問題都沒有交代清楚。但所幸的是,本文已經將RxJava必要的基礎入門知識講解完了。

在後期的文章中,主要是對RxJava的原始碼進行必要的分析,以及RxJava中各種操作符(比如常用的map、from、just、take、flatmap等等)的使用方式進行講解。

跟現有的部落格或者教程不一樣的是,我不打算直接用理論知識來講解,而是會用一個具體的專案來完成一系列的說明。

其實在寫部落格的時候,對自己所學也是一個複習的過程,能發現之前學習過程中所疏忽的問題。但由於技術水平有限,文中難免會有錯誤或者疏忽之處,歡迎大家指正與交流。

最後感謝扔物線老師的文章,寫的真的是太好了。

參考資料

給 Android 開發者的 RxJava 詳解——扔物線

深入淺出RxJava(一:基礎篇)

ReactiveX 的理念和特點

專案原始碼

IamXiaRui-Github-FirstRxJavaDemo


個人部落格:www.iamxiarui.com

原文連結:www.iamxiarui.com/?p=741

相關文章