Rxandroid What's the difference between SubscribeOn and ObserveOn

后端 未结 5 2017
逝去的感伤
逝去的感伤 2020-12-23 09:09

I am just learning Rx-java and Rxandroid2 and I am just confused what is the major difference between in SubscribeOn and ObserveOn.

5条回答
  •  死守一世寂寞
    2020-12-23 09:47

    Summary

    • Use observeOn to set threads for callbacks "further down the stream (below it)", such as code blocks inside doOnNext or map.
    • Use subscribeOn to set threads for initializations "upstream (above it)", such as doOnSubscribe, Observable.just or Observable.create.
    • Both methods can be called multiple times, with each call overwriting previous ones. Position matters.

    Let's walk through this topic with an example: we want to find the length of the string "user1032613". This is not an easy task for computers, so it's only natural that we perform the intense calculation in a background thread, to avoid freezing the app.

    observeOn

    We can call observeOn as many times as we like, and it controls which thread all callbacks below it will run. It's easy to use, and works just as you'd expect.

    For example, we will show a progress bar on the main UI thread, then do intensive/blocking operations in another thread, then come back to the main UI thread to update the result:

        Observable.just("user1032613")
    
                .observeOn(mainThread) // set thread for operation 1
                .doOnNext {
                    /* operation 1 */
                    print("display progress bar")
                    progressBar.visibility = View.VISIBLE
                }
    
                .observeOn(backThread) // set thread for operation 2 and 3
                .map {
                    /* operation 2 */
                    print("calculating")
                    Thread.sleep(5000)
                    it.length
                }
    
                .doOnNext {
                    /* operation 3 */
                    print("finished calculating")
                }
    
                .observeOn(mainThread) // set thread for operation 4
                .doOnNext {
                    /* operation 4 */
                    print("hide progress bar and display result")
                    progressBar.visibility = View.GONE
                    resultTextView.text = "There're $it characters!"
                }
    
                .subscribe()
    

    In the above example, /* operation 1 */ is ran in the mainThread because we set it using observeOn(mainThread) on the line right above it; then we switch to backThread by calling observeOn again, so /* operation 2 */ will run there. Because we didn't change it before chaining /* operation 3 */, it will run in the back thread as well, just like /* operation 2 */; finally we call observeOn(mainThread) again, to make sure /* operation 4 */ updates the UI from the main thread.

    subscribeOn

    So we've learned observeOn sets threads for subsequent callbacks. What else are we missing? Well, the Observable itself, and its methods such as just(), create(), subscribe() and so on, are also code that needs to be executed. This is how objects are passed along the stream. We use subscribeOn to set threads for code related to Observable itself.

    If we remove all the callbacks (controlled by observeOn discussed earlier), we are left with the "skeleton code" that will, by default, run on whichever thread the code is written in (probably main thread):

        Observable.just("user1032613")
                .observeOn(mainThread)
                .doOnNext {
                }
                .observeOn(backThread)
                .map {
                }
                .doOnNext {
                }
                .observeOn(mainThread)
                .doOnNext {
                }
                .subscribe()
    

    If we aren't happy about this empty skeleton code running on main thread, we can use subscribeOn to change it. For example, maybe the first line Observable.just("user1032613") isn't as simple as creating a stream from my user name - maybe it's a string from the Internet, or perhaps you are using doOnSubscribe for some other intensive operations. In that case, you can call subscribeOn(backThread) to put some of the code in another thread.

    Where to put subscribeOn

    At the time of writing this answer, there are some misconceptions saying "only call it once", "position does not matter", and "if you call it multiple times, only the first time counts". After lots of researches and experiments, it turns out subscribeOn can be usefully called multiple times.

    Because Observable uses Builder Pattern (fancy name for "chaining methods one after another"), subscribeOn is applied in reverse order. Therefore, this method sets the thread for code above it, exactly the opposite of observeOn.

    We can experiment this using doOnSubscribe method. This method is triggered on the subscription event, and it runs on the thread set by subscribeOn:

        Observable.just("user1032613")
                .doOnSubscribe {
                    print("#3 running on main thread")
                }
                .subscribeOn(mainThread) // set thread for #3 and just()
                .doOnNext {
                }
                .map {
                }
                .doOnSubscribe {
                    print("#2 running on back thread")
                }
                .doOnNext {
                }
                .subscribeOn(backThread) // set thread for #2 above
                .doOnNext {
                }
                .doOnSubscribe {
                    print("#1 running on default thread")
                }
                .subscribe()
    

    It might be easier to follow the logic, if you read the above example from bottom to top, just like how Builder Pattern executes the code.

    In this example, the first line Observable.just("user1032613") is run in the same thread as print("#3") because there are no more subscribeOn in-between them. This creates the illusion of "only the first call matters" for people who only care about code inside just() or create(). This quickly falls apart once you start doing more.


    Footnote:

    Threads and print() functions in the examples are defined, for brevity, as follows:

    val mainThread = AndroidSchedulers.mainThread()
    val backThread = Schedulers.computation()
    private fun print(msg: String) = Log.i("", "${Thread.currentThread().name}: $msg")
    

提交回复
热议问题