RxJava (XV) Free switching of RxJava threads

Posted by hyngvesson on Wed, 15 May 2019 04:07:12 +0200

RxJava series article directory guide:

I. Usage and Source Code Analysis of RxJava create Operator
2. Detailed usage of RxJava map operators
3. Detailed usage of RxJava flatMap operator
4. Detailed usage of RxJava concatMap operator
5. Implementation of token mechanism between app and server by RxJava onError ResumeNext operator
6. Error Retry Mechanism of RxJava retryWhen Operator
RxJava uses debounce operator to optimize app search function
8. RxJava concat operation processing multiple data sources
9. Actual usage scenarios of RxJava zip operators in Android
10. RxJava switchIfEmpty Operator Implementing Android Check Local Cache Logic Judgment
11. RxJava defer operator implements code support chain invocation
Advanced Use of combineLatest Operator
13. RxJava Causes Memory Leakage in Fragment Activity
14. interval and takeWhile Operators to Acquire Verification Codes
XV. Free switching of Rx Java threads

When Android uses RxJava, it may need to switch threads frequently, such as time-consuming operations being executed in sub-threads and rendering the interface in the main thread after execution. The following example code:

deferObservable(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //Perform time-consuming tasks
                return "task result";
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                //Render View
            }
        }, new Action1<Throwable>() {
            @Override
            public void call(Throwable throwable) {
                throwable.printStackTrace();
            }
        });

This is the simplest logic: sub-threads process time-consuming tasks and then process results.
But in actual development, it may be more complex than this. For example, there is a logic that first loads data (sub-threads) from the local, then displays the local data (main threads) in the interface, then loads data (sub-threads) from the online, and then renders it (main threads). This requires frequent thread switching.

In RxJava, threads are switched through subscribeOn and observeOn operators. subscribeOn() mainly changes the thread of subscription, that is, the thread of call() execution, and observeOn() mainly changes the thread of sending, that is, the thread of onNext() execution.

In order to realize the logic of free switching above (sub-thread - > main thread - > sub-thread - > - > main thread)

deferObservable(new Callable<String>() { //defer observable
            @Override
            public String call() throws Exception {
                Log.d("RxThreadFragment", "defer " + Thread.currentThread().getName());
                return "task result";
            }
        })
        .observeOn(AndroidSchedulers.mainThread())//Specify the following call to execute in the main thread
        .flatMap(new Func1<String, Observable<String>>() {
            @Override
            public Observable<String> call(String s) {
                Log.d("RxThreadFragment", "flatMap1 " + Thread.currentThread().getName());
                return Observable.just(s);
            }
        })
        .observeOn(Schedulers.io())//Specify that the following call is executed in a subthread
        .flatMap(new Func1<String, Observable<String>>() {
            @Override
            public Observable<String> call(String s) {
                Log.d("RxThreadFragment", "flatMap2 " + Thread.currentThread().getName());
                return Observable.just(s);
            }
        })
        .subscribeOn(Schedulers.io())//Specify that the Observable that does not specify the thread above executes in the IO thread
        .observeOn(AndroidSchedulers.mainThread())//Specify the following call to execute in the main thread
        .subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                //etc
                Log.d("RxThreadFragment", s + Thread.currentThread().getName());
            }
        }, new Action1<Throwable>() {
            @Override
            public void call(Throwable throwable) {
                throwable.printStackTrace();
            }
        });

Output results:

defer RxIoScheduler-2
flatMap2 main
flatMap3 RxIoScheduler-3
task result main

As you can see from the above code, observeOn specifies the thread in which the adjacent Observable emits data under this operator.
subscribeOn specifies all the threads on this operator that are not named as Observable s.
For example, in the previous example, there are three observable s ("defer", "flatMap1", "flatMap2") on the subscribeOn operator.
Since "flatMap1" and "flatMap2" have been schedule d by observeOn respectively, the subscribeOn is only valid for "defer".

Let's look at another example.

final Observable<String> observable1 = RxUtils.deferObservable(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Log.e("RxThreadFragment", "observable1 thread name : " + Thread.currentThread().getName());
                return "observable1 Schedulers.io()";
            }
        }).subscribeOn(Schedulers.io());//Specify the thread where the call method above resides

        final Observable<String> observable2 = RxUtils.deferObservable(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Log.e("RxThreadFragment", "observable2 thread name : " + Thread.currentThread().getName());
                return "observable2 AndroidSchedulers.mainThread()";
            }
        }).subscribeOn(Schedulers.io());//Specify the thread where the call method above resides

        final Observable<String> observable3 = RxUtils.deferObservable(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Log.e("RxThreadFragment", "observable3 thread name : " + Thread.currentThread().getName());
                return "observable3 Schedulers.io()";
            }
        }).subscribeOn(Schedulers.io());//Specify the thread where the call method above resides

        RxUtils.deferObservable(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        Log.e("RxThreadFragment", "test thread name : " + Thread.currentThread().getName());
                        return "test thread";
                    }
                })
                .subscribeOn(Schedulers.io())//Modify the thread above where Observable call is located
                .observeOn(AndroidSchedulers.mainThread())//Modify the following thread for flatMap1 call
                .flatMap(new Func1<String, Observable<String>>() {//flatMap1
                    @Override
                    public Observable<String> call(String s) {
                        Log.e("RxThreadFragment", "flatMap1 thread name : " + Thread.currentThread().getName());
                        return observable1;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())//Modify the following thread for flatMap2 call
                .flatMap(new Func1<String, Observable<String>>() {//flatMap2
                    @Override
                    public Observable<String> call(String s) {
                        Log.e("RxThreadFragment", "flatMap2 thread name : " + Thread.currentThread().getName());
                        return observable2;
                    }
                })
                .flatMap(new Func1<String, Observable<String>>() {
                    @Override
                    public Observable<String> call(String s) {
                        Log.e("RxThreadFragment", "flatMap3 thread name : " + Thread.currentThread().getName());
                        return observable3;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())//Modify the following subscribe call thread
                .subscribe(new Action1<String>() {
                    @Override
                    public void call(String s) {
                        Log.e("RxThreadFragment", "subscribe Action1 thread name : " + Thread.currentThread().getName());
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        throwable.printStackTrace();
                    }
                });

Output results:

Test thread name: RxIoScheduler-2 is manually set to background thread
 FlatMap1 thread name: main is set to main thread manually
 Observable 1 thread name: RxIoScheduler-3 is manually set to background thread
 FlatMap2 thread name: main is set to main thread manually
 Observable 2 thread name: RxIoScheduler-2 is manually set to background thread
 FlatMap3 thread name: RxIoScheduler-2 background thread
 Observable 3 thread name: RxIoScheduler-3 is manually set to background thread
 Subscribe Action1 thread name: main is set to main thread manually

As you can see from this example, flatMap3 does not set the thread it is in, and defaults to the thread mode of the previous observable, which is the thread mode of observable 2 above.
If the previous operator is not flatMap, but map (which does not return observable2), then the thread where the map call is located is used.

Through the above two examples, I believe that the Rx Java thread switching should be almost the same. It should be noted that above is basically a combination of subscribeOn and multiple observeOn s to achieve the free switching of threads.

If using multiple subscribeOns is not interesting, only the first subscribeOn is valid.

source code

Topics: Android Java Fragment