线程的调度又叫线程的切换,在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");
}
});
运行结果如下:
从代码中可以看出,当不指定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()等等。