RxJava2 学习教程(二)线程调度

线程的调度又叫线程的切换,在Rxjava是默认规则中,事件的发出(由被观察者完成)和消费(由观察者完成)是在同一个线程中,但是在实际开发中,我们经常会遇到类似的情景:在子线程中进行耗时的操作,然后回到主线程进行UI操作,在这种情况下显然需要进行线程的切换,这时我们如何来完成呢?本节将进行讲解。

文章线索:

  • Observable(被观察者)和Observer()默认的工作线程
  • 观察者和被观察者如何进行不同线程切换
  • 关于ObserverOn()与SubscribeOn(),我们了解多少?
  • Rxjava中内置了哪些线程?有什么区别?
  • 通过实战了解和学习Rxjava2中线程的调度

线程如何切换

Observable和Observer默认在主线程中执行
  Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
                emitter.onNext(4);
                Log.d(TAG, "Observable thread is : " + Thread.currentThread().getName());
                emitter.onComplete();
            }
        }).subscribe(new Observer<Integer>() {

            @Override
            public void onSubscribe(Disposable d) {
                Log.d(TAG, "onSubscribe");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "Observer thread is :" + Thread.currentThread().getName());
                Log.d(TAG, "onNext: " + integer);

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {
                Log.d(TAG, "onComplete");
            }
        });

运行结果如下:

GfW9Pg.png

从代码中可以看出,当不指定Observable和Observer所在线程时,默认他们都工作在主线程,被观察者在主线程分别发射了整型数据的1,2,3,4事件,观察者在主线程依次接收,直到事件完成。

观察者和被观察者如何在不同线程中进行切换

在上面的例子中,默认情况下观察者和被观察者都工作在主线程,即主线程发送数据和主线程接收,假如我们需要实现被观察者在子线程中发送数据,然后观察者在主线程中进行接收,那么通过RxJava 内置的线程调度器就可以很轻松的实现,实现代码如下:

   Observable.create(new ObservableOnSubscribe<Integer>() {
            @SuppressLint("LongLogTag")
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                for (int i = 0; i < 4; i++) {
                    Log.e(TAG, "上游被观察者Observable在子线程为" + Thread.currentThread().getName() + "生产一个事件: " + i );
                    emitter.onNext(i);
                    SystemClock.sleep(3000);
                }
                emitter.onComplete();
            }
        }).subscribeOn(Schedulers.newThread())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(new Observer<Integer>() {
              @Override
              public void onSubscribe(Disposable d) {

              }

              @SuppressLint("LongLogTag")
              @Override
              public void onNext(Integer integer) {
                  Log.d(TAG, "下游观察者Observer在主线程:" + Thread.currentThread().getName()+"onNext事件中接收了:"+integer);

              }

              @Override
              public void onError(Throwable e) {

              }

              @SuppressLint("LongLogTag")
              @Override
              public void onComplete() {
                  Log.d(TAG, "onComplete: 下游观察者接收完毕");
              }
          });

运行结果如下:

在上游的被观察者每隔3秒在IO线程中发送一个事件,下游的观察者在主线程中进行接收,在这个过程中主要多了以下两行代码:

.subscribeOn(Schedulers.newThread())   
.observeOn(AndroidSchedulers.mainThread())

第一行代码通过subscribeOn指定了被观察者所在的线程,第二行代码则指定了观察者所在是线程,那么问题来了,RxJava中都内置了哪些线程调度器呢?

线程调度器的种类
调度器种类 作用
AndroidSchedulers.mainThread( ) 主线程,UI线程,可以用于更新界面
Schedulers.newThread( ) 为每个任务创建一个新线程
Schedulers.io( ) 用于IO密集型的操作,例如读写SD卡文件,查询数据库,访问网络等,具有线程缓存机制,在此调度器接收到任务后,先检查线程缓存池中,是否有空闲的线程,如果有,则复用,如果没有则创建新的线程,并加入到线程池中,如果每次都没有空闲线程使用,可以无上限的创建新线程。
Schedulers.computation( ) 用于CPU 密集型计算任务,即不会被 I/O 等操作限制性能的耗时操作,例如xml,json文件的解析,Bitmap图片的压缩取样等,具有固定的线程池,大小为CPU的核数。不可以用于I/O操作,因为I/O操作的等待时间会浪费CPU。
Schedulers.from(executor) 使用指定的Executor作为调度器
Schedulers.immediate( ) 在当前线程立即开始执行任务(Rxjava2中已废弃),功能等同于RxJava2中的Schedulers.trampoline( )
Schedulers.trampoline( ) 在当前线程立即执行任务,如果当前线程有任务在执行,则会将其暂停,等插入进来的任务执行完之后,再将未完成的任务接着执行。(插队)
subscribeOn()和ObserveOn()
  • Rxjava中 提供了subscribeOn()方法用于每个observable对象的操作符在哪个线程上运行;ObserveOn()方法用于每个Subscriber(Observer)对象的操作符在哪个线程上运行
  • 线程切换的时候subscribeOn()只被执行一次 。如果出现多次,那么以第一次出现是用的那个线程为准。 observeOn()可以被调用多次且切换到指定的不同线程
调用两次subscribeOn()和一次ObserverOn()

当我们使用两次subscribeOn() 时,只会以第一次指定的线程为主,代码如下:

  //使用两次subscribeOn() 和 一次observeOn()的线程调度
        //通过两次设置subscribeOn()发射和处理数据在不同的线程,但是最终起作用的是第一次设置的工作线程
        //由此可以得出多次设置subscribeOn()设置被观察者Observable工作的线程最终起作用的是第一次设置的线程
        Observable.create(new ObservableOnSubscribe<Integer>() {
            @SuppressLint("LongLogTag")
            @Override
            public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                for (int i = 0; i < 4; i++) {
                    Log.d(TAG, "上游被观察者Observable在" + Thread.currentThread().getName() + "线程, 生产一个事件: " + i );
                    SystemClock.sleep(1000);
                    e.onNext(i);
                }
                e.onComplete();
            }
        }).subscribeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Function<Integer, Integer>() {
                    @SuppressLint("LongLogTag")
                    @Override
                    public Integer apply(@NonNull Integer integer) throws Exception {
                        Log.d(TAG, "map操作符在" + Thread.currentThread().getName() + "线程处理了事件: " + integer);
                        return integer * 2;
                    }
                })
                .observeOn(Schedulers.io())
                .subscribe(new Consumer<Integer>() {
                    @SuppressLint("LongLogTag")
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        Log.d(TAG, "下游观察者Observer在IO线程" + Thread.currentThread().getName() + "接收响应到了事件: " + integer);
                    }
                });

运行结果如下:

可以看到,我们在上面指定了两次被观察者所在线程,但是最终以第一次的为主,打印出了map操作符在主线程。

.subscribeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
调用一次subscribeOn()和两次observeOn()
 Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                emitter.onNext(1);
                emitter.onNext(2);
                emitter.onNext(3);
                LogUtil.d("上游被观察者所在IO线程为:"+Thread.currentThread().getName());
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .map(new Function<Integer, String>() {
                    @Override
                    public String apply(Integer integer) throws Exception {
                        LogUtil.d("下游观察者线程为"+Thread.currentThread().getName());
                        return 2*integer+"";
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<String>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(String s) {
                        LogUtil.d("onNext():下游观察者所在线程为"+Thread.currentThread().getName());
                    }

                    @Override
                    public void onError(Throwable e) {
                    }
                    @Override
                    public void onComplete() {

                    }
                });

运行代码结果如下:

可以看到,下游观察者之前在IO线程,后面切换到了主线程,其主要关键代码是:

  .observeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())

所以当调用两次observeOn()时会对下游观察者所在线程进行两次切换。

实践

需求: 在注册登录的案例中,我们需要实现当用户注册成功后进行自动登录的操作。
素材:
  • 背景:以https://wanandroid.com/网站的注册登录为例,需实现注册后可自动登录功能
  • 接口:
public interface ApiService {
    /**
    * 注册
    * */

    @FormUrlEncoded
    @POST( "user/register")
    Observable<RegisterResp> register(@Field("username")String username, @Field("password")String password, @Field("repassword")String repassword);

    /**
     * 登录
     */
    @FormUrlEncoded
    @POST("user/login")
    Observable<LoginResponse> login(@Field("username") String name, @Field("password") String password);
}
原始的处理方式:
 //登录逻辑
 private void originLogin(){
       apiService.login(username, password)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())  //回到主线程去处理请求结果
               .subscribe(new Consumer<LoginResponse>() {
                   @Override
                   public void accept(LoginResponse loginResponse) throws Exception {
                       ToastUtil.showToast("登录成功");

                   }
               },new Consumer<Throwable>() {
                   @Override
                   public void accept(Throwable throwable) throws Exception {
                       ToastUtil.showToast("登录失败");
                   }
               });
   }
   
   // 注册后自动登录逻辑
   private void regisLogin(){
       apiService.register(username,password,repassword)
                  .subscribeOn(Schedulers.io()) //在IO线程进行网络请求
                  .observeOn(AndroidSchedulers.mainThread()) 
                  .subscribe(new Consumer<RegisterResp>() {
                      @Override
                      public void accept(RegisterResp registerResp) throws Exception {
                          LogUtil.d("registCode:"+registerResp.getErrorCode());
                          if (-1==registerResp.getErrorCode()){
                             ToastUtil.showToast("用户名已注册");
                          }else {
                              ToastUtil.showToast("注册成功。开始登录");
                              originLogin();
                          }
                      }
                  }, new Consumer<Throwable>() {
                      @Override
                      public void accept(Throwable throwable) throws Exception {
                          ToastUtil.showToast("注册失败");
                      }
                  });
   }

可以看到,没有学习线程调度之前,我们需要分别实现注册和的登录,且不考虑执行效率,单从代码的处理上就很繁琐,需要写两个方法,两个模块进行处理,那有没有什么办法,在不影响效果的前提下将上面的代码优化一下呢,且看下面的代码:

现在的处理方式(改进后)
  //使用接口请求对象创建call对象
        apiService.register(username,password,repassword)
            .subscribeOn(Schedulers.io()) ////在IO线程进行注册和登录的网络请求
             .flatMap(new Function<RegisterResp, ObservableSource<LoginResponse>>() {
            @Override
            public ObservableSource<LoginResponse> apply(RegisterResp registerResp) throws Exception {
              if (-1==registerResp.getErrorCode()){
                  return Observable.error(new Throwable("用户名已经注册"));
              }else if ( -1001==registerResp.getErrorCode()){
                  return Observable.error(new Throwable("注册失败"));
              }

                return api.login(username, password);
            }
        }).observeOn(AndroidSchedulers.mainThread()) // 回到主线程处理
         .subscribe(new Observer<LoginResponse>() {
             @Override
             public void onSubscribe(Disposable d) {

             }

             @Override
             public void onNext(LoginResponse loginResponse) {
                 Toast.makeText(MainActivity.this, "恭喜你登录成功", Toast.LENGTH_SHORT).show();
             }

             @Override
             public void onError(Throwable e) {
                 if ("注册失败".equals(e.getMessage())){
                     Toast.makeText(MainActivity.this, "注册失败", Toast.LENGTH_SHORT).show();
                 }else if (("用户名已经注册".equals(e.getMessage()))){
                     Toast.makeText(MainActivity.this, "用户名已经注册", Toast.LENGTH_SHORT).show();
                     }
                 }

             @Override
             public void onComplete() {

             }
         });

从代码中可以看到,我们在IO线程里就先处理好注册的结果,如果注册成功则直接利用现有的IO线程进行登录操作请求,注册失败则将结果返回到主线程进行处理,代码简洁,逻辑简单。

最后

Rxjava2中线程的调度我们先学习到这里,后面会继续分享相关知识,接下来下一篇将讲解 Rxjava2中的操作符,是的,没错,就是类似上面代码中create(),flatMap()等等。

发布了8 篇原创文章 · 获赞 9 · 访问量 6200

猜你喜欢

转载自blog.csdn.net/DolphKong/article/details/105688517