RxJava 入門

sunbird89629發表於2017-10-10

匯入

我相信大家肯定對ReactiveX 和 RxJava 都不陌生,因為現在只要是和技術相關的網站,部落格都會隨處見到介紹ReactiveX和RxJava的文章。

ReactiveX

  • ReactiveX是Reactive Extensions 的縮寫,即響應式程式設計的擴充套件。
  • “a library for composing asynchronous and event-based programs using observable sequences for the Java VM”(一個在 Java VM 上使用可觀測的序列來組成非同步的、基於事件的程式庫)。
  • Rx是一種程式設計模型,目標是提供一致的程式設計介面,幫助開發者更方便的處理非同步 資料流.由微軟的架構師Erik Meijer領導的團隊開發,在2012年11月開源。他在各個常用程式語言上都有實現。如Java,C#,PHP,Swift,Scala等等.社群網站是reactivex.io
  • ReactiveX不僅僅是一個程式設計介面,它是一種程式設計思想的突破,它影響了許多其它的程式庫和框架以及程式語言。

關於響應式程式設計

  • 事件匯流排(Event buses)或我們們常見的單擊事件就是一個非同步事件流,你可以觀察這個流,也可以基於這個流做一些自定義操作。響應式就是基於這種想法。你能夠建立所有事物的資料流,而不僅僅只是單擊和懸停事件資料流。 流廉價且無處不在,任何事物都可以當作一個流:變數、使用者輸入、屬性、快取、資料結構等等。比如,假設你的微博評論就是一個跟單擊事件一樣的資料流,你能夠監聽這個流,並做出響應。
  • 有一堆的函式能夠建立(create)任何流,也能將任何流進行組合(combine)和過濾(filter)。 這正是“函式式”的魔力所在。一個流能作為另一個流的輸入(input),甚至多個流也可以作為其它流的輸入。你能合併(merge)兩個流。你還能通過過濾(filter)一個流得到那些你感興趣的事件。你能將一個流中的資料對映(map)到一個新的流中。
  • 響應式程式設計的主要組成部分是observable, operator 和 observer
  • 一般響應式程式設計的資訊流:Observable -> Operator1 -> Operator2->...->OperatorN->Observer
  • Observable 是事件的生產者,Observer是事件的最終消費者。中間可以通過任意多個的Operator對事件進行處理和轉換
  • 因為Observer通常在主執行緒中執行,因此設計上要求程式碼儘可能的簡單,只對事件作出相應(不對事件或者資料進行修改,所有修改事件的工作全部由operator完成)

RxJava 和 RxAndroid

  • RxJava是ReactiveX在 Java 平臺的實現,你可以將它看作一個普通的Java類庫。
  • RxAndroid是RxJava的一個針對Android平臺的擴充套件,主要用於 Android 開發。
  • RxJava就是一個做非同步開發的框架,和Android系統提供的 Handler+Thread,AsyncTask,Context.runOnUiThread等是解決的是同樣的問題。那麼他跟系統提供的非同步程式設計方案比,有什麼好處呢。或者說他有什麼樣的優勢值得我們花時間和精力切換到RxJava呢?

總結起來可以用兩個詞來概括:非同步和簡潔

主要概念

Observable(被觀察者)

Observables 負責發出一系列的事件,這裡的事件可以是任何東西,例如網路請求的結果,複雜計算處理的結果,資料庫操作的結構,檔案操作的結果等,事件執行結束後交給Observer的回撥處理。

Observer(觀察者)

進行訂閱接受處理事件

Operator(操作符)中文文件

負責對事件進行各種變化和處理

Scheduler(排程器)

提供了各種排程器,是RxJava可以方便的實現非同步開發

事件

這裡的事件值指的是 onNext (有新資料),onComplete (所有資料處理完成),onError (事件佇列異常)

RxJava的好處(為什麼RxJava對於Android如此重要)

  • 輕鬆使用併發:讓非同步程式設計變得簡單簡潔.像寫同步程式碼一樣。
  • 方便的執行緒切換
  • 簡單而完善的異常處理機制:傳統的try/cache沒辦法處理非同步中子執行緒產生的異常,RxJava 提供了合適的錯誤處理機制
  • 強大的操作符支援,函式式的風格,鏈式呼叫。

舉個例子

假如現在我們有這樣一個需求:介面上有一個自定義的檢視 imageCollectorView ,它的作用是顯示多張圖片,並能使用 addImage(Bitmap) 方法來任意增加顯示的圖片。現在需要程式將一個給出的目錄陣列 File[] folders 中每個目錄下的 png 圖片都載入出來並顯示在 imageCollectorView 中。需要注意的是,由於讀取圖片的這一過程較為耗時,需要放在後臺執行,而圖片的顯示則必須在 UI 執行緒執行。

目錄陣列


File[] folders=new File[]{......};複製程式碼

執行緒方式實現(call hell)


new Thread() {
    @Override
    public void run() {
        super.run();
        try{
          for (File folder : folders) {
              File[] files = folder.listFiles();
              for (File file : files) {
                  if (file.getName().endsWith(".png")) {
                      final Bitmap bitmap = getBitmapFromFile(file);
                      getActivity().runOnUiThread(new Runnable() {
                          @Override
                          public void run() {
                              imageCollectorView.addImage(bitmap);
                          }
                      });
                  }
              }
          }
        }catch(Exception e){
          //error handling
          //只能在這裡進行異常處理
        }

    }
}.start();複製程式碼

RxJava 實現


//建立Observable
Observable observable = Observable.create(new ObservableOnSubscribe<File>() {
            @Override
            public void subscribe(ObservableEmitter<File> e) throws Exception {
                for (File file : files) {
                    e.onNext(file);
                }
                e.onComplete();
            }
        });
//建立Observer
Observer<Bitmap> observer = new Observer<Bitmap>() {
            @Override
            public void onSubscribe(Disposable disposable) {

            }

            @Override
            public void onNext(Bitmap bitmap) {
                imageCollectorView.addImage(bitmap);
            }

            @Override
            public void onError(Throwable throwable) {
              //error handling
            }

            @Override
            public void onComplete() {
                Log.i(TAG,"All images are shown");
            }
        };
//對事件集進行處理並連線消費者
observable.flatMap(new Func1<File, Observable<File>>() {//分別獲取每個資料夾下面的檔案,組合成一個Observable
              @Override
              public Observable<File> call(File file) {
                  return Observable.from(file.listFiles());
              }
          })
          .filter(new Func1<File, Boolean>() {//過濾出所有副檔名為png的檔案
              @Override
              public Boolean call(File file) {
                  return file.getName().endsWith(".png");
              }
          })
          .map(new Func1<File, Bitmap>() {//根據File物件,獲取Bitmap物件
              @Override
              public Bitmap call(File file) {
                  return getBitmapFromFile(file);
              }
          })
          .subscribeOn(Schedulers.io())//指定Observable的所有操作符的操作在io執行緒中執行
          .observeOn(AndroidSchedulers.mainThread())//指定消費者在主執行緒中執行
          .subscribe(observer);//連線觀察者複製程式碼

有的人可能說了,你這不是程式碼更多,更復雜了嗎?
不要著急,這只是最基礎的版本,稍後會對程式碼進行簡化。
但即使是這種情況下,程式碼雖然多了,但我們可以發現,他的邏輯更清晰了,也沒有那麼多的巢狀了。

簡化程式碼

  • 對於一個陣列,可用建立操作符“from”來建立Observable
  • 如果我們只對結果感興趣,不關心異常處理和事件發射完成事件,我也可以將Observer用Consumer來替換

//建立Observable
Observable observable = Observable.from(folers);
//建立Observer
Consumer<Bitmap> consumer=new Consumer<Bitmap>() {
            @Override
            public void accept(@NonNull Bitmap bitmap) throws Exception {
                imageCollectorView.addImage(bitmap);
            }
          };
//對事件集進行處理並連線消費者
observable.flatMap(new Func1<File, Observable<File>>() {//分別獲取每個資料夾下面的檔案,組合成一個Observable
              @Override
              public Observable<File> call(File file) {
                  return Observable.from(file.listFiles());
              }
          })
          .filter(new Func1<File, Boolean>() {//過濾出所有副檔名為png的檔案
              @Override
              public Boolean call(File file) {
                  return file.getName().endsWith(".png");
              }
          })
          .map(new Func1<File, Bitmap>() {//根據File物件,獲取Bitmap物件
              @Override
              public Bitmap call(File file) {
                  return getBitmapFromFile(file);
              }
          })
          .subscribeOn(Schedulers.io())//指定Observable的所有操作符的操作在io執行緒中執行
          .observeOn(AndroidSchedulers.mainThread())//指定消費者在主執行緒中執行
          .subscribe(consumer);//連線消費者複製程式碼

RxJava 鏈式呼叫實現


Observable.from(folders)
    .flatMap(new Func1<File, Observable<File>>() {
        @Override
        public Observable<File> call(File file) {
            return Observable.from(file.listFiles());
        }
    })
    .filter(new Func1<File, Boolean>() {
        @Override
        public Boolean call(File file) {
            return file.getName().endsWith(".png");
        }
    })
    .map(new Func1<File, Bitmap>() {
        @Override
        public Bitmap call(File file) {
            return getBitmapFromFile(file);
        }
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Consumer<Bitmap>() {
                @Override
                public void accept(@NonNull Bitmap bitmap) throws Exception {
                    imageCollectorView.addImage(bitmap);
                }
              });複製程式碼

RxJava + lambda 實現


Observable.from(folders)
    .flatMap(file -> Observable.from(file.listFiles())
    .filter(file -> file.getName().endsWith(".png"))
    .map( file -> getBitmapFromFile(file))
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(bitmap -> imageCollectorView.addImage(bitmap));//無異常處理,有異常會拋到主執行緒,不影響我們原來程式的crash處理複製程式碼

關於lambda(匿名函式,它可以包含表示式和語句)在Android中的使用:

  • 要在 Android 的較早版本中測試 Lambda 表示式、方法引用和型別註解,請前往您的 build.gradle 檔案,將 compileSdkVersion 和 targetSdkVersion 設定為 23 或更低。您仍需要啟用 Jack 工具鏈以使用這些 Java 8 功能。
  • 經測試,按照官方提供的方案配置後,雖然可以使用lambda,但編譯速度變的很慢。
  • 在執行的時候,並且我測試用的專案引用改了 Bouncy Castle(輕量級加密解密工具包) 這個包報出了記憶體溢位的異常,所以我感覺現在還不太穩定。
  • 第三方開源的實現方案:retrolambda
  • 當然我們也可以不用lambda,這樣程式碼看著比較多,但因其只有一層巢狀的鏈式呼叫,所以邏輯結構並不複雜。事實上 Android Studio 會自動幫我們把這部分程式碼摺疊成lambda的形式。

更進一步,假設我們現在需要忽略掉前5張,一共顯示10張


Observable.from(folders)
    .flatMap(file -> Observable.from(file.listFiles())
    .filter(file -> file.getName().endsWith(".png"))

    .skip(5)
    .take(10)

    .map( file -> getBitmapFromFile(file))
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(bitmap -> imageCollectorView.addImage(bitmap));//無異常處理,有異常會拋到主執行緒,不影響我們原來程式的crash處理複製程式碼

操作符簡介

建立操作

用於建立Observable的操作符

  • Create — 通過呼叫觀察者的方法從頭建立一個Observable

    create操作符是所有建立型操作符的“根”,也就是說其他建立型操作符最後都是通過create操作符來建立Observable的

  • From — 將其它的物件或資料結構轉換為Observable

  • Just — 將物件或者物件集合轉換為一個會發射這些物件的Observable
  • Defer — 在觀察者訂閱之前不建立這個Observable,為每一個觀察者建立一個新的Observable
  • Empty/Never/Throw — 建立行為受限的特殊Observable

    一般用於測試

  • Interval — 建立一個定時發射整數序列的Observable

  • Range — 建立發射指定範圍的整數序列的Observable
  • Repeat — 建立重複發射特定的資料或資料序列的Observable
  • Start — 建立發射一個函式的返回值的Observable
  • Timer — 建立在一個指定的延遲之後發射單個資料的Observable
變換操作

這些操作符可用於對Observable發射的資料進行變換

  • Map — 對映,通過對序列的每一項都應用一個函式變換Observable發射的資料,實質是對序列中的每一項執行一個函式,函式的引數就是這個資料項
  • Buffer — 快取,可以簡單的理解為快取,它定期從Observable收集資料到一個集合,然後把這些資料集合打包發射,而不是一次發射一個
  • FlatMap — 扁平對映,將Observable發射的資料變換為Observables集合,然後將這些Observable發射的資料平坦化的放進一個單獨的Observable,可以認為是一個將巢狀的資料結構展開的過程。
  • GroupBy — 分組,將原來的Observable分拆為Observable集合,將原始Observable發射的資料按Key分組,每一個Observable發射一組不同的資料
  • Scan — 掃描,對Observable發射的每一項資料應用一個函式,然後按順序依次發射這些值
  • Window — 視窗,定期將來自Observable的資料分拆成一些Observable視窗,然後發射這些視窗,而不是每次發射一項。類似於Buffer,但Buffer發射的是資料,Window發射的是Observable,每一個Observable發射原始Observable的資料的一個子集
過濾操作

這些操作符用於從Observable發射的資料中進行選擇,符合一定條件的傳送給觀察者進行處理,不符合條件的直接丟棄

  • Filter — 過濾,過濾掉沒有通過謂詞測試的資料項,只發射通過測試的
  • Skip — 跳過前面的若干項資料
  • SkipLast — 跳過後面的若干項資料
  • Take — 只保留前面的若干項資料
  • TakeLast — 只保留後面的若干項資料
  • Debounce — 只有在空閒了一段時間後才發射資料,通俗的說,就是如果一段時間沒有操作,就執行一次操作
  • Distinct — 去重,過濾掉重複資料項
  • ElementAt — 取值,取特定位置的資料項
  • First — 首項,只發射滿足條件的第一條資料
  • IgnoreElements — 忽略所有的資料,只保留/終止通知(onError或onCompleted)
  • Last — 末項,只發射最後一條資料
  • Sample — 取樣,定期發射最新的資料,等於是資料抽樣,有的實現裡叫ThrottleFirst
組合操作

組合操作符用於將多個Observable組合成一個單一的Observable

  • And/Then/When — 通過模式(And條件)和計劃(Then次序)組合兩個或多個Observable發射的資料集
  • CombineLatest — 當兩個Observables中的任何一個發射了一個資料時,通過一個指定的函式組合每個Observable發射的最新資料(一共兩個資料),然後發射這個函式的結果
  • Join — 無論何時,如果一個Observable發射了一個資料項,只要在另一個Observable發射的資料項定義的時間視窗內,就將兩個Observable發射的資料合併發射
  • Merge — 將兩個Observable發射的資料組合併成一個
  • StartWith — 在發射原來的Observable的資料序列之前,先發射一個指定的資料序列或資料項
  • Switch — 將一個發射Observable序列的Observable轉換為這樣一個Observable:它逐個發射那些Observable最近發射的資料
  • Zip — 打包,使用一個指定的函式將多個Observable發射的資料組合在一起,然後將這個函式的結果作為單項資料發射
錯誤處理

這些操作符用於從錯誤通知中恢復

  • Catch — 捕獲,繼續序列操作,將錯誤替換為正常的資料,從onError通知中恢復
  • Retry — 重試,如果Observable發射了一個錯誤通知,重新訂閱它,期待它正常終止
輔助操作

一組用於處理Observable的操作符

  • Delay — 延遲一段時間發射結果資料
  • Do — 註冊一個動作佔用一些Observable的生命週期事件,相當於Mock某個操作
  • Materialize/Dematerialize — 將發射的資料和通知都當做資料發射,或者反過來
  • ObserveOn — 指定觀察者觀察Observable的排程程式(工作執行緒)
  • SubscribeOn — 指定Observable應該在哪個排程程式上執行
  • Serialize — 強制Observable按次序發射資料並且功能是有效的
  • Subscribe — 收到Observable發射的資料和通知後執行的操作
  • TimeInterval — 將一個Observable轉換為發射兩個資料之間所耗費時間的Observable
  • Timeout — 新增超時機制,如果過了指定的一段時間沒有發射資料,就發射一個錯誤通知
  • Timestamp — 給Observable發射的每個資料項新增一個時間戳
  • Using — 建立一個只在Observable的生命週期記憶體在的一次性資源
條件和布林操作

這些操作符可用於單個或多個資料項,也可用於Observable

  • All — 判斷Observable發射的所有的資料項是否都滿足某個條件
  • Amb — 給定多個Observable,只讓第一個發射資料的Observable發射全部資料
  • Contains — 判斷Observable是否會發射一個指定的資料項
  • DefaultIfEmpty — 發射來自原始Observable的資料,如果原始Observable沒有發射資料,就發射一個預設資料
  • SequenceEqual — 判斷兩個Observable是否按相同的資料序列
  • SkipUntil — 丟棄原始Observable發射的資料,直到第二個Observable發射了一個資料,然後發射原始Observable的剩餘資料
  • SkipWhile — 丟棄原始Observable發射的資料,直到一個特定的條件為假,然後發射原始Observable剩餘的資料
  • TakeUntil — 發射來自原始Observable的資料,直到第二個Observable發射了一個資料或一個通知
  • TakeWhile — 發射原始Observable的資料,直到一個特定的條件為真,然後跳過剩餘的資料
算術和聚合操作

這些操作符可用於整個資料序列

  • Average — 計算Observable發射的資料序列的平均值,然後發射這個結果
  • Concat — 不交錯的連線多個Observable的資料
  • Count — 計算Observable發射的資料個數,然後發射這個結果
  • Max — 計算併發射資料序列的最大值
  • Min — 計算併發射資料序列的最小值
  • Reduce — 按順序對資料序列的每一個應用某個函式,然後返回這個值
  • Sum — 計算併發射資料序列的和
連線操作

一些有精確可控的訂閱行為的特殊Observable

  • Connect — 指示一個可連線的Observable開始發射資料給訂閱者

    • 可連線的Observable (connectable Observable)與普通的Observable差不多,不過它並不會在被訂閱時開始發射資料,而是直到使用了Connect操作符時才會開始。用這個方法,你可以等待所有的觀察者都訂閱了Observable之後再開始發射資料。
    • RxJava中connect是ConnectableObservable介面的一個方法,使用publish操作符可以將一個普通的Observable轉換為一個ConnectableObservable。
    • 舉例

      
      ConnectableObservable<String> connectableObservable = Observable.just("a", "c", "d").publish();
          connectableObservable.subscribe(new Consumer<String>() {
              @Override
              public void accept(@NonNull String s) throws Exception {
                  LogUtil.i(s);
              }
          });
      
          LogUtil.i("subscribe end.....");
      
          Observable.timer(3, TimeUnit.SECONDS).subscribe(new Consumer<Long>() {
              @Override
              public void accept(@NonNull Long aLong) throws Exception {
                  LogUtil.i("connect method called after 3 seconds.");
                  connectableObservable.connect();
              }
          });複製程式碼
      
      03-20 15:54:19.328 27493-27493/me.sunbird.react_native_demo I/x_log:RxJavaActivity.testConnectableObservable(L:586): subscribe end.....
      03-20 15:54:22.378 27493-27573/me.sunbird.react_native_demo I/x_log:RxJavaActivity$34.accept(L:591): connect method called after 3 seconds.
      03-20 15:54:22.419 27493-27573/me.sunbird.react_native_demo I/x_log:RxJavaActivity$33.accept(L:582): a
      03-20 15:54:22.419 27493-27573/me.sunbird.react_native_demo I/x_log:RxJavaActivity$33.accept(L:582): c
      03-20 15:54:22.420 27493-27573/me.sunbird.react_native_demo I/x_log:RxJavaActivity$33.accept(L:582): d複製程式碼
  • Publish — 將一個普通的Observable轉換為可連線的

  • RefCount — 使一個可連線的Observable表現得像一個普通的Observable
  • Replay — 確保所有的觀察者收到同樣的資料序列,即使他們在Observable開始發射資料之後才訂閱
轉換操作
  • To — 將Observable轉換為其它的物件或資料結構
  • Blocking 阻塞Observable的操作符
操作符決策樹

幾種主要的需求

  • 直接建立一個Observable(建立操作)
  • 組合多個Observable(組合操作)
  • 對Observable發射的資料執行變換操作(變換操作)
  • 從Observable發射的資料中取特定的值(過濾操作)
  • 轉發Observable的部分值(條件/布林/過濾操作)
  • 對Observable發射的資料序列求值(算術/聚合操作)

Scheduler(排程器)中文文件

本質上RxJava就是一個做非同步開發的框架,能使我們極其靈活的進行執行緒切換。
我們可以使用ObserveOn和SubscribeOn操作符,可以讓Observable在一個特定的排程器上執行,ObserveOn指示一個Observable在一個特定的排程器上呼叫觀察者的onNext, onError和onCompleted方法,SubscribeOn更進一步,它指示Observable將全部的處理過程(包括髮射資料和通知)放在特定的排程器上執行。
subscribeOn 和 observeOn 兩個操作符是極其容易混淆的,可以看下這篇部落格來徹底分清楚這兩個操作符SubscribeOn 和 ObserveOn

排程器型別 效果
Schedulers.computation( ) 用於計算任務,如事件迴圈或和回撥處理,不要用於IO操作(IO操作請使用Schedulers.io());預設執行緒數等於處理器的數量
Schedulers.from(executor) 使用指定的Executor作為排程器
Schedulers.immediate( ) 在當前執行緒立即開始執行任務
Schedulers.io( ) 用於IO密集型任務,如非同步阻塞IO操作,這個排程器的執行緒池會根據需要增長;對於普通的計算任務,請使用Schedulers.computation();Schedulers.io( )預設是一個CachedThreadScheduler,很像一個有執行緒快取的新執行緒排程器
Schedulers.newThread( ) 為每個任務建立一個新執行緒
Schedulers.trampoline( ) 當其它排隊的任務完成後,在當前執行緒排隊開始執行

Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> observableEmitter) throws Exception {
                LogUtil.w("subscribe method is running in thread:" + Thread.currentThread().getName());
                observableEmitter.onNext("a");
                observableEmitter.onComplete();
            }
        }).map(new Function<String, String>() {
            @Override
            public String apply(@NonNull String s) throws Exception {
                LogUtil.w("first map is running in thread:" + Thread.currentThread().getName());
                return s;
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(Schedulers.newThread())
                .map(new Function<String, String>() {
                    @Override
                    public String apply(String s) throws Exception {
                        LogUtil.w("second map is running in thread:" + Thread.currentThread().getName());
                        return s;
                    }
                })
                .observeOn(Schedulers.io())
                .map(new Function<String, String>() {
                    @Override
                    public String apply(String s) throws Exception {
                        LogUtil.w("third map is running in thread:" + Thread.currentThread().getName());
                        return s;
                    }
                })
                .observeOn(Schedulers.computation())
                .map(new Function<String, String>() {
                    @Override
                    public String apply(@NonNull String s) throws Exception {
                        LogUtil.w("fourth map is running in thread:" + Thread.currentThread().getName());
                        return s;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String s) throws Exception {
                        LogUtil.w("consumer accept method is running in thread:" + Thread.currentThread().getName());
                    }
                  });複製程式碼

執行後我們可以得到如下結果:


03-20 11:44:23.716 8687-8723/? W/x_log:RxJavaActivity$27.subscribe(L:528): subscribe method is running in thread:RxCachedThreadScheduler-1
03-20 11:44:23.717 8687-8723/? W/x_log:RxJavaActivity$28.apply(L:535): first map is running in thread:RxCachedThreadScheduler-1
03-20 11:44:23.721 8687-8724/? W/x_log:RxJavaActivity$29.apply(L:543): second map is running in thread:RxNewThreadScheduler-1
03-20 11:44:23.726 8687-8725/? W/x_log:RxJavaActivity$30.apply(L:551): third map is running in thread:RxCachedThreadScheduler-2
03-20 11:44:23.729 8687-8726/? W/x_log:RxJavaActivity$31.apply(L:559): fourth map is running in thread:RxComputationThreadPool-1
03-20 11:44:23.836 8687-8687/? W/x_log:RxJavaActivity$32.accept(L:567): consumer accept method is running in thread:main複製程式碼

RxJava 的使用場景舉例

複雜的資料變換

Observable.just("1", "2", "2", "3", "4", "5")//建立Observable
    .map(Integer::parseInt)//對每一項執行Integer.parseInt方法
    .filter(s -> s > 1)//過濾出所有值 >1 的物件
    .distinct() //去重,這裡也可以傳遞一個方法,來定義兩個物件是否equals的策略,非常靈活
    .take(3)//取到前3個
    .reduce((sum, item) -> sum + item) //累加
    .subscribe(System.out::println);//9 列印出最終累加的結果。複製程式碼
Retrofit結合RxJava做網路請求框架

這裡不作詳解,具體的介紹可以看扔物線的這篇文章,對RxJava的入門者有很大的啟發。其中也講到了RxJava和Retrofit如何結合來實現更簡潔的程式碼

RxJava代替EventBus進行資料傳遞
RxBus

RxBus並不是一個庫,而是一種模式,是使用了RxJava的思想來達到EventBus的資料傳遞效果。這篇文章把RxBus講的比較詳細。

square/Otto 對 RxBus 的態度

This project is deprecated in favor of RxJava and RxAndroid. These projects permit the same event-driven programming model as Otto, but they’re more capable and offer better control of threading.

為了支援 RxJava 和 RxAndroid,我們已經廢棄了這個專案。這兩個專案提供了和 Otto 一樣的基於事件驅動的程式設計模型,而且他們更強大,並提供更好的執行緒控制。

If you’re looking for guidance on migrating from Otto to Rx, this post is a good start.

如果你正在尋找從 Otto 遷移到 Rx 的教程,閱讀這篇文章將會是一個很好的開始。

一個網路請求依賴另外一個網路請求返回的結果。例如:登入之後,根據拿到的token去獲取訊息列表。
@GET("/token")
public Observable<String> getToken();

@GET("/user")
public Observable<User> getUser(@Query("token") String token, @Query("userId") String userId);

...

getToken()
    .flatMap(new Func1<String, Observable<User>>() {
        @Override
        public Observable<User> call(String token) {
            return getUser(token, userId);
        })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });複製程式碼
同一個頁面同事發起兩個以上的請求,此時對頁面中的ProgressBar進行管理,即兩個請求只能展示一個ProgressBar,並且所有的請求都結束後,ProgressBar 才能消失

@GET("/date1")
public Observable<String> getDate1();

@GET("/data2")
public Observable<String> getData2()

...

progressDialog.show()
Observable.merge(getData1(), getData2())
          .subscribeOn(Schedulers.newThread())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(new Observer() {
                    @Override
                    public void onSubscribe(Disposable disposable) {

                    }

                    @Override
                    public void onNext(String s) {
                      // 這裡進行結果處理
                    }

                    @Override
                    public void onError(Throwable throwable) {
                      //error handling
                      progressDialog.dismiss();
                    }

                    @Override
                    public void onComplete() {
                      progressDialog.dismiss();
                    }
                  });複製程式碼
使用throttleFirst(throttle:節流閥)防止按鈕重複點選

RxView.clicks(button)
    .throttleFirst(1, TimeUnit.SECONDS)
    .subscribe(new Observer<Object>() {
        @Override
        public void onCompleted() {
              log.d ("completed");
        }

        @Override
        public void onError(Throwable e) {
              log.e("error");
        }

        @Override
        public void onNext(Object o) {
             log.d("button clicked");
        }
    });複製程式碼
使用debounce(去抖動)做textSearch

用簡單的話講就是當N個結點發生的時間太靠近(即發生的時間差小於設定的值T),debounce就會自動過濾掉前N-1個結點。
比如在做百度地址聯想的時候,可以使用debounce減少頻繁的網路請求。避免每輸入(刪除)一個字就做一次聯想

RxTextView.textChangeEvents(inputEditText)
      .debounce(400, TimeUnit.MILLISECONDS)
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(new Observer<TextViewTextChangeEvent>() {
    @Override
    public void onCompleted() {
        log.d("onComplete");
    }

    @Override
    public void onError(Throwable e) {
        log.d("Error");
    }

    @Override
    public void onNext(TextViewTextChangeEvent onTextChangeEvent) {
        log.d(format("Searching for %s", onTextChangeEvent.text().toString()));
    }
});複製程式碼

RxJava 的生態

  • rx-preferences -使SharedPreferences支援RxJava

  • RxAndroid -RxJava的Android擴充

  • RxLifecycle -幫助使用了RxJava的安卓應用控制生命週期

  • RxBinding -安卓UI控制元件的RxJava繫結API

  • Android-ReactiveLocation -Google Play Service API wrapped in RxJava

  • storio -支援RxJava的資料庫

  • retrofit -支援RxJava的網路請求庫

  • sqlbrite -支援RxJava的sqlite資料庫

  • RxPermissions -RxJava實現的Android執行時許可權控制

  • reark -RxJava architecture library for Android

  • frodo -Android Library for Logging RxJava Observables and Subscribers.

RxJava 的現況

  • RxJava 最新版 2.0.7(注:不相容1.x 的版本) 大小 2.1M
  • RxAndroid 大小 10k
  • 支援Java6以上,Android2.3以上
  • github star 22511

RxJava 的未來展望

通用的資料流,強大的操作符,靈活的執行緒排程,簡單完善的異常處理機制,函數語言程式設計等等特性,奠定了RxJava的強大地位。
Android 系統中到處都是非同步,比如網路請求,檔案讀寫,資料庫查詢,系統服務或者第三方SDK服務等,這些非同步請求非常耗時,需要在非UI執行緒中執行,而對UI的修改又必須要在主執行緒中執行。如果再包含多個非同步執行巢狀的話,就會讓我們的程式碼顯得凌亂。通過RxJava提供的強大而通用的非同步處理機制,可以使我們的程式碼邏輯更清晰,便於後期的維護。並且現在RxJava的生態越來越大,個人認為,以後所有的涉及非同步操作的系統服務,第三方庫,第三方服務SDK都會以Observable或類Observable的方式提供給我們呼叫,而不是像現在這樣,讓我們傳遞一個又一個的listener。

參考資料

分享工具

zeplin(軟體演示)
  • 方便的效果圖管理
  • 新檔案,修改檔案提示
  • 自動測量
  • 標註評論
  • 支援pohtoshop和sketch
  • 第一個專案免費
charles(軟體演示)
  • 簡單已用,功能強大
  • focus 某一個域名下的請求,方便查詢
  • 對某個請求修改引數,重新請求,方便除錯
  • 各種格式良好的response解析
  • copy出cURL 格式的請求,方便傳遞個任何人進行請求模擬

相關文章