RxJava 與觀察者模式

RxJava究竟是什麼?讓咱們直接跳過官方那種晦澀的追求精確的定義,其實初學RxJava只要把握兩點:觀察者模式和異步,就基本能夠熟練使用RxJava了。javascript

異步在這裏並不須要作太多的解釋,由於在概念和使用上,並無太多高深的東西。大概就是你腦子裏想能到的那些多線程,線程切換這些東西。我會在後面會講解它的用法。java

咱們先把觀察者模式說清楚 
圖片描述
「按下開關,檯燈燈亮」git

在這個事件中,檯燈做爲觀察者,開關做爲被觀察者,檯燈透過電線來觀察開關的狀態來並作出相應的處理 
圖片描述
觀察上圖,其實已經很明瞭了,不過須要指出一下幾點(對於下面理解RxJava很重要):github

  • 開關(被觀察者)做爲事件的產生方(生產「開」和「關」這兩個事件),是主動的,是整個開燈事理流程的起點。多線程

  • 檯燈(觀察者)做爲事件的處理方(處理「燈亮」和「燈滅」這兩個事件),是被動的,是整個開燈事件流程的終點。架構

  • 在起點和終點之間的過程,事件傳遞的過程當中是能夠被加工,過濾,轉換,合併等等方式處理的(上圖沒有體現,後面對會講到)。異步

我必須苦口婆心的告訴你:咱們總結的這三點對於咱們理解RxJava很是重要。由於上述三條分別對應了RxJava中被觀察者(Observable),觀察者(Observer)和操做符的職能。而觀察者模式又是RxJava程序運行的骨架。ide

好了,我假設你已經徹底理解了我上面講述的東西。咱們正式進入RxJava!ui

RxJava也是基於觀察者模式來組建本身的程序邏輯的,就是構建被觀察者(Observable),觀察者(Observer/Subscriber),而後創建兩者的訂閱關係(就像那根電線,鏈接起檯燈和開關)實現觀察,在事件傳遞過程當中還能夠對事件作各類處理。this

Tips: Observer是觀察者的接口, Subscriber是實現這個接口的抽象類,所以兩個類均可以被當作觀察者,因爲它在Observe的基礎上作了一些拓展,加入了新的方法,通常會更加傾向於使用Subscriber。

建立被觀察者

  • 正常模式:
Observable switcher=Observable.create(new Observable.OnSubscribe<String>(){ @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("On"); subscriber.onNext("Off"); subscriber.onNext("On"); subscriber.onNext("On"); subscriber.onCompleted(); } });

這是最正宗的寫法,建立了一個開關類,產生了五個事件,分別是:開,關,開,開,結束。

  • 偷懶模式1
Observable switcher=Observable.just("On","Off","On","On");
  • 偷懶模式2
String [] kk={"On","Off","On","On"}; Observable switcher=Observable.from(kk);

偷懶模式是一種簡便的寫法,實際上也都是被觀察者把那些信息」On」,」Off」,」On」,」On」,包裝成onNext(」On」)這樣的事件依次發給觀察者,固然,它本身補上了onComplete()事件。

偷懶模式是一種簡便的寫法,實際上也都是被觀察者把那些信息」On」,」Off」,」On」,」On」,包裝成onNext(」On」)這樣的事件依次發給觀察者,固然,它本身補上了onComplete()事件。 
偷懶模式是一種簡便的寫法,實際上也都是被觀察者把那些信息」On」,」Off」,」On」,」On」,包裝成onNext(」On」)這樣的事件依次發給觀察者,固然,它本身補上了onComplete()事件。

以上是最經常使用到的建立方式,好了,咱們就建立了一個開關類。


建立觀察者

  • 正常模式
Subscriber light=new Subscriber<String>() { @Override public void onCompleted() { //被觀察者的onCompleted()事件會走到這裏; Log.d("DDDDDD","結束觀察...\n"); } @Override public void onError(Throwable e) { //出現錯誤會調用這個方法 } @Override public void onNext(String s) { //處理傳過來的onNext事件 Log.d("DDDDD","handle this---"+s) }

這也是比較常見的寫法,建立了一個檯燈類。

  • 偷懶模式(非正式寫法)
Action1 light=new Action1<String>() { @Override public void call(String s) { Log.d("DDDDD","handle this---"+s) } }

之因此說它是非正式寫法,是由於Action1是一個單純的人畜無害的接口,和Observer沒有啥關係,只不過它能夠當作觀察者來使,專門處理onNext 事件,這是一種爲了簡便偷懶的寫法。固然還有Action0,Action2,Action3…,0,1,2,3分別表示call()這個方法能接受幾個參數。若是你還不懂,能夠暫時跳過。後面我也會盡可能使用new Subscriber方式,建立正統的觀察者,便於大家理解。

訂閱 
如今已經建立了觀察者和被觀察者,可是二者尚未聯繫起來

switcher.subscribe(light);

我猜你看到這裏應該有疑問了,爲何是開關訂閱了檯燈?應該是檯燈訂閱了開關纔對啊。臥槽,到底誰觀察誰啊!!

你們冷靜,把刀放下,有話慢慢說,

是這樣的,檯燈觀察開關,邏輯是沒錯的,並且正常來看也應該是light.subscribe(switcher)纔對,這實際上是基於保證流式API調用風格的結果

//這就是RxJava的流式API調用 Observable.just("On","Off","On","On") //在傳遞過程當中對事件進行過濾操做 .filter(new Func1<String, Boolean>() { @Override public Boolean call(String s) { return s!=null; } }) .subscribe(mSubscriber);

上面就是一個很是簡易的RxJava流式API的調用:同一個調用主體一路調用下來,一鼓作氣。

因爲被觀察者產生事件,是事件的起點,那麼開頭就是用Observable這個主體調用來建立被觀察者,產生事件,爲了保證流式API調用規則,就直接讓Observable做爲惟一的調用主體,一路調用下去。

一句話,背後的真實的邏輯依然是檯燈訂閱了開關,可是在表面上,咱們讓開關「僞裝」訂閱了檯燈,以便於保持流式API調用風格不變。

好了,如今分解動做都完成了,已經架構了一個基本的RxJava事件處理流程。

咱們再來按照觀察者模式的運做流程和流式Api的寫法複習一遍:

流程圖以下:圖片描述
結合流程圖的相應代碼實例以下:

//建立被觀察者,是事件傳遞的起點 Observable.just("On","Off","On","On") //這就是在傳遞過程當中對事件進行過濾操做 .filter(new Func1<String, Boolean>() { @Override public Boolean call(String s) { return s!=null; } }) //實現訂閱 .subscribe( //建立觀察者,做爲事件傳遞的終點處理事件 new Subscriber<String>() { @Override public void onCompleted() { Log.d("DDDDDD","結束觀察...\n"); } @Override public void onError(Throwable e) { //出現錯誤會調用這個方法 } @Override public void onNext(String s) { //處理事件 Log.d("DDDDD","handle this---"+s) } );

嗯,基本上咱們就把RxJava的骨架就講完了,總結一下:

  • 建立被觀察者,產生事件
  • 設置事件傳遞過程當中的過濾,合併,變換等加工操做。
  • 訂閱一個觀察者對象,實現事件最終的處理。

Tips: 當調用訂閱操做(即調用Observer.subscribe()方法)的時候,被觀察者才真正開始發出事件。


如今開始講異步操做?彆着急,事件的產生起點和處理的終點咱們都比較詳細的講解了,接下來咱們好好講講事件傳遞過程當中發生的那些事兒… 
RxJava的操做符

即便你已經看了我上面那段講解,Rxjava可能還打動不了你,不要緊,事件產生的起點和消費的終點其實沒那麼吸引人,真正有意思的是事件傳遞過程當中的那些鬼斧神工的操做。 
因爲篇幅的限制,我只講兩三個操做。 
變換

Map操做

好比被觀察者產生的事件中只有圖片文件路徑;,可是在觀察者這裏只想要bitmap,那麼就須要類型變換。

Observable.create(new Observable.just(getFilePath()) //使用map操做來完成類型轉換 .map(new Func1<String, Bitmap>() { @Override public Bitmap call(String s) { //顯然自定義的createBitmapFromPath(s)方法,是一個極其耗時的操做 return createBitmapFromPath(s); } }) .subscribe( //建立觀察者,做爲事件傳遞的終點處理事件 new Subscriber<Bitmap>() { @Override public void onCompleted() { Log.d("DDDDDD","結束觀察...\n"); } @Override public void onError(Throwable e) { //出現錯誤會調用這個方法 } @Override public void onNext(Bitmap s) { //處理事件 showBitmap(s) } );
  • 實際上在使用map操做時,new Func1() 就對應了類型的轉你方向,String是原類型,Bitmap是轉換後的類型。在call()方法中,輸入的是原類型,返回轉換後的類型

你認真看完上面的代碼就會以爲,何須在過程當中變換類型呢?我直接在事件傳遞的終點,在觀察者中變換就行咯。老實說,你這個想法沒毛病,但實際上,上面寫的代碼是不合理的。

我在代碼中也提到,讀取文件,建立bitmap多是一個耗時操做,那麼就應該在子線程中執行,主線程應該僅僅作展現。那麼線程切換通常就會是比較複雜的事情了。可是在Rxjava中,是很是方便的。

Observable.create(new Observable.just(getFilePath()) //指定了被觀察者執行的線程環境 .subscribeOn(Schedulers.newThread()) //將接下來執行的線程環境指定爲io線程 .observeOn(Schedulers.io()) //使用map操做來完成類型轉換 .map(new Func1<String, Bitmap>() { @Override public Bitmap call(String s) { //顯然自定義的createBitmapFromPath(s)方法,是一個極其耗時的操做 return createBitmapFromPath(s); } }) //將後面執行的線程環境切換爲主線程 .observeOn(AndroidSchedulers.mainThread()) .subscribe( //建立觀察者,做爲事件傳遞的終點處理事件 new Subscriber<Bitmap>() { @Override public void onCompleted() { Log.d("DDDDDD","結束觀察...\n"); } @Override public void onError(Throwable e) { //出現錯誤會調用這個方法 } @Override public void onNext(Bitmap s) { //處理事件 showBitmap(s) } );

由上面的代碼能夠看到,使用操做符將事件處理逐步分解,經過線程調度爲每一步設置不一樣的線程環境,徹底解決了你線程切換的煩惱。能夠說線程調度+操做符,才真正展示了RxJava無與倫比的魅力。

flatmap操做

先提出一個需求,查找一個學校每一個班級的每一個學生,並打印出來。

若是用老辦法:先讀出全部班級的數據,循環每一個班級。再循環中再讀取每一個班級中每一個學生,而後循環打印出來。

仍是得說,這種想法,沒毛病,就是嵌套得有點多。

Rxjava說:我不是針對誰…

//建立被觀察者,獲取全部班級 Observable.from(getSchoolClasses()) .flatMap(new Func1<SingleClass, Observable<Student>>() { @Override public Observable<Student> call(SingleClass singleClass) { //將每一個班級的全部學生做爲一列表包裝成一列Observable<Student>,將學生一個一個傳遞出去 return Observable.from(singleClass.getStudents()); } }) .subscribe( //建立觀察者,做爲事件傳遞的終點處理事件 new Subscriber<Student>() { @Override public void onCompleted() { Log.d("DDDDDD","結束觀察...\n"); } @Override public void onError(Throwable e) { //出現錯誤會調用這個方法 } @Override public void onNext(Student student) { //接受到每一個學生類 Log.d("DDDDDD",student.getName()) } );

好了,基本上按照RxJava的骨架搭起來就能完成需求。你說棒不棒??

其實FlatMap是比較難懂的一個操做符,做爲初學者其實會用就好,因此我推薦的對於FlatMap的解釋是:將每一個Observable產生的事件裏的信息再包裝成新的Observable傳遞出來,

那麼爲何FlatMap能夠破除嵌套難題呢?

就是由於FlatMap能夠再次包裝新的Observable,而每一個Observable均可以使用from(T[])方法來建立本身,這個方法接受一個列表,而後將列表中的數據包裝成一系列事件。 
異步(線程調度)

異步是相對於主線程來說的子線程操做,在這裏咱們不妨使用線程調度這個概念更加貼切。

首先介紹一下RxJava的線程環境有哪些選項: 
圖片描述
在講解Map操做符時,已經提到了線程調度,在這裏我用更加簡介的代碼代替:

//new Observable.just()執行在新線程 Observable.create(new Observable.just(getFilePath()) //指定在新線程中建立被觀察者 .subscribeOn(Schedulers.newThread()) //將接下來執行的線程環境指定爲io線程 .observeOn(Schedulers.io()) //map就處在io線程 .map(mMapOperater) //將後面執行的線程環境切換爲主線程, //可是這一句依然執行在io線程 .observeOn(AndroidSchedulers.mainThread()) //指定線程無效,但這句代碼自己執行在主線程 .subscribeOn(Schedulers.io()) //執行在主線程 .subscribe(mSubscriber);

實際上線程調度只有subscribeOn()和observeOn()兩個方法。對於初學者,只須要掌握兩點:

  • subscribeOn()它指示Observable在一個指定的調度器上建立(只做用於被觀察者建立階段)。只能指定一次,若是指定屢次則以第一次爲準
  • observeOn()指定在事件傳遞(加工變換)和最終被處理(觀察者)的發生在哪個調度器。可指定屢次,每次指定完都在下一步生效。 
    結尾

好了,對於RxJava整個入門文章到這裏就徹底結束了,如今再來回看RxJava,你會發現,它就是在觀察者模式的骨架下,經過豐富的操做符和便捷的異步操做來完成對於複雜業務的處理。

我相信你對於整個RxJava的骨架,以及執行流程應該有了至關的瞭解,如今就只須要多練習一下操做符的用法了。

本文沒有介紹太多的操做符,不少沒來得及介紹的操做符的用法實例都放在github上的RxJavaDemo項目上了,後期還會繼續加上更多操做符的使用,歡迎你們上去看看,對照代碼,手機運行一下。 你們多給頂幾下!順便follow一下,接下來,我也會慢慢整理出一些別的有用的項目分享給你們。

相關文章
相關標籤/搜索