rxjava操作符結合使用場景簡介

rxjava操作符結合使用場景簡介

前言

本文將通過實際的例子來介紹rx相關的操作符,如果對rxjava還不熟悉的同學請先查看rxjava相關基礎姿勢再來查看本文

準備

本文依賴rxjava版本如下

implementation 'io.reactivex.rxjava2:rxjava:2.1.15'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

目錄

目前根據實際使用場景一共總結了7個例子,後面如果還有的話我會持續更新

  1. 網絡請求嵌套(A請求成功後再進行B請求的情況)
  2. 網絡請求合併(A請求和B請求成功後合併數據在更新ui)
  3. 三級緩存,從內存/磁盤/網絡中讀取數據
  4. 組合判斷(當A輸入框和B輸入框輸入內容滿足條件的時候Button才變成可點擊的)
  5. 網絡請求出錯重試
  6. 聯想搜索優化(用戶x秒後沒有再輸入新的內容,進行搜索請求)
  7. 防止多次點擊

1. 網絡請求嵌套

先執行請求1成功後執行請求2.

這裏通過flatMap操作符實現,將一個Observable轉換成另一個Observable

網絡請求的話這裏自己模擬的代碼如下

object ExampleRepo {

    fun getRequest1() = Observable.just(1)

    fun getRequest2(i: Int) = Observable.just(2)

}

執行請求代碼如下

ExampleRepo.getRequest1()
.flatMap {
    ExampleRepo.getRequest2(it)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
    {
        Log.i("zhuliyuan", "onNext: $it")
    },
    {
        Log.i("zhuliyuan", "onError: $it")
    },
    {
        Log.i("zhuliyuan", "onComplete")
    },
    {
        Log.i("zhuliyuan", "onSubscribe")
    }
)

結果與預期並無差異

2. 網絡請求合併

將請求1和請求2成功後的數據合併供ui顯示.

這裏會用到zip操作符,合併多個被觀察者Observable發送的事件,並最終發送

模擬網絡請求代碼如下

object ExampleRepo {

    fun getRequest1() = Observable.just(1)

    fun getRequest2() = Observable.just(2)

}

執行請求我們將request1的結果1和request2的結果2相加,最後結果應該爲3

Observable.zip(
    ExampleRepo.getRequest1(),
    ExampleRepo.getRequest2(),
    BiFunction<Int, Int, Int> { t1, t2 ->
                               t1 + t2
                              })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
    {
        Log.i("zhuliyuan", "onNext: $it")
    },
    {
        Log.i("zhuliyuan", "onError: $it")
    },
    {
        Log.i("zhuliyuan", "onComplete")
    },
    {
        Log.i("zhuliyuan", "onSubscribe")
    }
)

結果和預期相同

3. 三級緩存

先從內存中獲取數據,沒獲取到的話再從磁盤獲取數據,還是沒取到的話最後通過網絡獲取數據.

這裏會用到concat操作符組合多個被觀察者一起發送數據,合併後 按發送順序串行執行

var memory: String? = null
var disk: String? = "磁盤獲取數據"
var net: String? = "網絡獲取數據"

fun getData() {
    val memoryObservable = Observable.create<String> {
        Log.i("zhuliyuan", "從內存獲取數據")
        if (!TextUtils.isEmpty(memory)) {
            it.onNext(memory!!)
        }
        it.onComplete()
    }

    val diskObservable = Observable.create<String> {
        Log.i("zhuliyuan", "從磁盤獲取數據")
        if (!TextUtils.isEmpty(disk)) {
            it.onNext(disk!!)
        }
        it.onComplete()
    }.doOnNext {
        Log.i("zhuliyuan", "磁盤獲取數據成功 把數據寫入內存")
        memory = it
    }

    val netObservable = Observable.create<String> {
        Log.i("zhuliyuan", "從網絡獲取數據")
        if (!TextUtils.isEmpty(net)) {
            it.onNext(net!!)
        }
        it.onComplete()
    }.doOnNext {
        Log.i("zhuliyuan", "網絡獲取數據成功 把數據寫入內存和磁盤")
        memory = it
        disk = it
    }

    Observable.concat(memoryObservable, diskObservable, netObservable)
    .firstElement()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        {
            Log.i("zhuliyuan", "onNext: $it")
        },
        {
            Log.i("zhuliyuan", "onError: $it")
        },
        {
            Log.i("zhuliyuan", "onComplete")
        }
    )
}

這裏我模擬了三級緩存獲取數據的過程,內存中沒有數據所以會從磁盤獲取數據,磁盤有數據,獲取成功後再把數據寫入內存緩存.

打印結果如下

4.組合判斷

比如填寫表單數據的時候,當姓名和手機號都正確填寫後提交按鈕纔可點擊.

這裏會用到combineLatest操作符把多個Observable組合在一起,將其他Observable的最新數據和最後一個沒有發送數據的Observable第一次發送的數據結合在一起.

舉個實際點的例子,有兩個Observable A和B ,A發送了三個數據分別書1,2,3 然後B發送了一個數據10 那麼最後就是A發送的3和B發送的10會在combineLatest 操作符中的BiFunction 對象的參數中給你,然後你來處理最後應該返回什麼.

接下來我們模擬當用戶姓名和手機號都正確填寫後提交按鈕纔可點擊的情況.

ui如下

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="rocketly.rxjava2demo.MainActivity">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:text="姓名"
        android:textSize="18sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/et_name"
        android:layout_width="200dp"
        android:layout_height="32dp"
        android:layout_marginLeft="10dp"
        android:padding="0dp"
        app:layout_constraintBottom_toBottomOf="@id/tv_name"
        app:layout_constraintLeft_toRightOf="@id/tv_name"
        app:layout_constraintTop_toTopOf="@id/tv_name" />

    <TextView
        android:id="@+id/tv_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:text="電話"
        android:textSize="18sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_name" />

    <EditText
        android:id="@+id/et_phone"
        android:layout_width="200dp"
        android:layout_height="32dp"
        android:layout_marginLeft="10dp"
        android:padding="0dp"
        app:layout_constraintBottom_toBottomOf="@id/tv_phone"
        app:layout_constraintLeft_toRightOf="@id/tv_phone"
        app:layout_constraintTop_toTopOf="@id/tv_phone" />

    <Button
        android:id="@+id/bt_commit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/gray"
        android:text="提交"
        app:layout_constraintTop_toBottomOf="@id/et_phone" />

</android.support.constraint.ConstraintLayout>

然後我們用kt的擴展方法處理下EditText讓他能返回給我們需要的Observable

fun EditText.addTextChangedListener(): Observable<String> {
    return Observable.create<String> {
        this.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {

            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                it.onNext(s.toString())
            }

        })
    }
}

最後我們在用combineLatest操作符處理下這兩個editText的Observable,當用戶輸入name不爲空並且手機號輸入位數滿足11位,提交按鈕變成可點擊的綠色

Observable.combineLatest(
                et_name.addTextChangedListener(),
                et_phone.addTextChangedListener(),
                BiFunction<String, String, Boolean> { t1, t2 ->
                    !TextUtils.isEmpty(t1) && t2.length == 11
                })
                .subscribe {
                    if (it) {
                        bt_commit.isEnabled = true
                        bt_commit.setBackgroundColor(resources.getColor(R.color.green))
                    } else {
                        bt_commit.isEnabled = false
                        bt_commit.setBackgroundColor(resources.getColor(R.color.gray))
                    }
                }

然後我們看下效果,只有滿足我們條件的時候按鈕纔可點擊

5. 網絡請求錯誤重試

請求錯誤的時候重試機制.

這裏用到retryWhen操作符,當遇到錯誤時,將發生的錯誤傳遞給一個新的被觀察者,並決定是否需要重新訂閱原始被觀察者 & 發送事件

我們模擬網絡請求,判斷如果是io連接異常並且重試次數少於3次的話進行重試

var retryCount = 0

fun load(){
    Observable.error<Exception>(IOException("報錯了"))
    .retryWhen {
        it.flatMap {
            if (it is IOException && retryCount < 3) {//當連接失敗的時候重試
                retryCount++
                Log.i("zhuliyuan", "開始第${retryCount}次重試")
                return@flatMap Observable.just(1)
            } else {//其他錯誤
                return@flatMap Observable.error<Exception>(it)
            }
        }
    }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        {
            Log.i("zhuliyuan", "onNext: $it")
        },
        {
            Log.i("zhuliyuan", "onError: $it")
        },
        {
            Log.i("zhuliyuan", "onComplete")
        },
        {
            Log.i("zhuliyuan", "onSubscribe")
        }
    )
}

日誌如下

6.聯想搜索優化

在搜索的時候,用戶最後一次輸入n秒後還未輸入新的內容,這時請求接口進行聯想搜索.

這裏會用到debounce操作符,發送數據事件時,若2次發送事件的間隔<指定時間,就會丟棄前一次的數據,直到指定時間內都沒有新數據發射時纔會發送後一次的數據.

我們模擬用戶聯想搜索這個流程,當用戶最後一次輸入1.5秒後還沒輸入新的內容,我們直接把搜索內容顯示出來

ui代碼如下

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="rocketly.rxjava2demo.MainActivity">

    <EditText
        android:id="@+id/et"
        android:layout_width="200dp"
        android:layout_height="32dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:padding="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="搜索內容:"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et" />

</android.support.constraint.ConstraintLayout>

將EditText輸入監聽轉換成Observable用的kotlin的擴展方法

fun EditText.addTextChangedListener(): Observable<String> {
    return Observable.create<String> {
        this.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {

            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                it.onNext(s.toString())
            }

        })
    }
}

最後模擬邏輯如下

et.addTextChangedListener()
                .debounce(1500, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        {
                            tv.text = "搜索內容:$it"
                            Log.i("zhuliyuan", "onNext: $it")
                        },
                        {
                            Log.i("zhuliyuan", "onError: $it")
                        },
                        {
                            Log.i("zhuliyuan", "onComplete")
                        },
                        {
                            Log.i("zhuliyuan", "onSubscribe")
                        }
                )

然後我們看下效果,只要兩次輸入間隔大於1.5秒,就會把搜索內容顯示出來

7. 防止多次點擊

防止短時間快速點擊按鈕觸發多次點擊

這裏用到throttleFirst操作符,在某段時間內,只發送該段時間內第1次事件

這裏我們模擬快速點擊的情況

ui如下

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="rocketly.rxjava2demo.MainActivity">

    <Button
        android:id="@+id/bt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="快速點擊" />

</android.support.constraint.ConstraintLayout>

通過kt的擴展方法封裝button的點擊事件,在1秒內只發送該段時間內第一次點擊事件

fun Button.setOnClickListenerV2(listener: (View) -> Unit) {
    Observable
            .create<View> { emit ->
                this.setOnClickListener {
                    emit.onNext(it)
                }
            }
            .throttleFirst(1, TimeUnit.SECONDS)
            .subscribe {
                listener(it)
            }
}

然後我們設置點擊代碼如下,快速點擊的時候只會每隔1秒觸發一次點擊回調

bt.setOnClickListener {
    Log.i("zhuliyuan", "點擊了")
}

日誌如下

可以看到即使多次點擊,每次間隔也有1秒

總結

目前結合我平常使用的就是這7個例子,如果還有更多我會持續更新,最後感謝閱讀.

  1. flatMap網絡請求嵌套(A請求成功後再進行B請求的情況)
  2. zip網絡請求合併(A請求和B請求成功後合併數據在更新ui)
  3. concat三級緩存,從內存/磁盤/網絡中讀取數據
  4. combineLatest組合判斷(當A輸入框和B輸入框輸入內容滿足條件的時候Button才變成可點擊的)
  5. retryWhen網絡請求出錯重試
  6. debounce聯想搜索優化(用戶x秒後沒有再輸入新的內容,進行搜索請求)
  7. throttleFirst防止多次點擊
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章