關於RxJava最友好的文章

拉丁吳發表於2016-10-16

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出

RxJava到底是什麼?讓我們直接跳過官方那種晦澀的追求精確的定義,其實初學RxJava只要把握兩點:觀察者模式非同步,就基本可以熟練使用RxJava了。

非同步在這裡並不需要做太多的解釋,因為在概念和使用上,並沒有太多高深的東西。大概就是你腦子裡想能到的那些多執行緒,執行緒切換這些東西。我會在後面會講解它的用法。

我們先把觀察者模式說清楚

關於RxJava最友好的文章

“按下開關,檯燈燈亮”

在這個事件中,檯燈作為觀察者,開關作為被觀察者,檯燈透過電線來觀察開關的狀態來並做出相應的處理

關於RxJava最友好的文章

觀察上圖,其實已經很明瞭了,不過需要指出一下幾點(對於下面理解RxJava很重要):

  • 開關(被觀察者)作為事件的產生方(生產“開”和“關”這兩個事件),是主動的,是整個開燈事理流程的起點
  • 檯燈(觀察者)作為事件的處理方(處理“燈亮”和“燈滅”這兩個事件),是被動的,是整個開燈事件流程的終點
  • 在起點和終點之間,即事件傳遞的過程中是可以被加工,過濾,轉換,合併等等方式處理的(上圖沒有體現,後面對會講到)。

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

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

RxJava也是基於觀察者模式來組建自己的程式邏輯的,就是構建被觀察者(Observable),觀察者(Observer/Subscriber),然後建立二者的訂閱關係(就像那根電線,連線起檯燈和開關)實現觀察,在事件傳遞過程中還可以對事件做各種處理

Tips: Observer是觀察者的介面, Subscriber是實現這個介面的抽象類,因此兩個類都可以被當做觀察者,由於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()事件。

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


建立觀察者

  • 正常模式
 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呼叫風格

啥是流式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的寫法複習一遍:

流程圖如下:

關於RxJava最友好的文章

結合流程圖的相應程式碼例項如下:

//建立被觀察者,是事件傳遞的起點
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: 當呼叫訂閱操作(即呼叫Observable.subscribe()方法)的時候,被觀察者才真正開始發出事件。


現在開始講非同步操作?彆著急,事件的產生起點和處理的終點我們都比較詳細的講解了,接下來我們好好講講事件傳遞過程中發生的那些事兒...

RxJava的操作符

即使你已經看了我上面那段講解,Rxjava可能還打動不了你,沒關係,事件產生的起點和消費的終點其實沒那麼吸引人,真正有意思的是事件傳遞過程中的那些鬼斧神工的操作。

由於篇幅的限制,我只講兩三個操作,其他的操作請看我的RxJava操作的Demo記得在github給我點star或者follow一下,不然我就坐在地上不起來,哼

變換

Map操作

比如被觀察者產生的事件中只有圖片檔案路徑;,但是在觀察者這裡只想要bitmap,那麼就需要型別變換

  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.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的執行緒環境有哪些選項:

關於RxJava最友好的文章

在講解Map操作符時,已經提到了執行緒排程,在這裡我用更加簡介的程式碼代替:

            //new Observable.just()執行在新執行緒
  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專案上了,後期還會繼續加上更多操作符的使用,歡迎大家上去看看,對照程式碼,手機執行一下。

大家多給點star!!順便follow一下,接下來,我也會慢慢整理出一些別的有用的專案分享給大家。

勘誤

暫無

後記

如果你還有不太理解,或者覺得文章在某些地方還有提升的空間,歡迎在下方留言,幫助我一起把這篇文章改得更加簡單,生動,透徹,實用。這也是我寫Blog一直追求的目標。

而且,我希望這篇文章能成為RxJava入門上手最好的文章。

Rxjava2.0的文章也已經出來了啦,歡迎大家前去閱讀。

歡迎轉載,但是請註明出處。

附原始碼

文章涉及和未涉及的程式碼範例

個人Github主頁

star ~~

相關文章