1、前言
一年多一直在做Android电视开发,很少做网络编程,对于互联网常用技术都跟不上了,在这记录一下RxJava3的使用。
2、示例
2.1、应用场景
当我们需要进行一些耗时操作,例如下载、访问数据库等,为了不阻塞主线程,往往会将其放在后台进行处理,同时在处理的过程中、处理完成后通知主线程更新UI,这里就涉及到了后台线程和主线程之间的切换。首先回忆一下,在以前我们一般会用以下两种方式来实现这一效果:
- 异步消息处理机制,创建一个新的子线程,在其run()方法中执行耗时的操作,并通过一个和主线程Looper关联的Handler发送消息给主线程更新进度显示、处理结果。
- 使用AsyncTask,在其doInBackground方法中执行耗时的操作,调用publishProgress方法通知主线程,然后在onProgressUpdate中更新进度显示,在onPostExecute中显示最终结果。
那么,让我们看一些在RxJava中如何完成这一需求。
2.2、示例代码
我们的界面上有一个mTvDownload按钮,点击之后会发起一个耗时的任务,这里我们用Thread.sleep来模拟耗时的操作,每隔5000ms我们会将当前的进度通知主线程,在mTvDownloadResult中显示当前处理的进度。
首先去build.gradle中添加依赖,我用的是RxJava 3.0;
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
然后去实现代码,RxJava的使用一般分为三个步骤:
- 创建被观察者
- 创建观察者
- 观察者订阅被观察者
public class DownloadActivity extends AppCompatActivity {
private static final String TAG = "DownloadActivity";
private TextView mTvDownload;
private TextView mTvDownloadResult;
private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);
Log.d(TAG, "onCreate: start");
mTvDownload = findViewById(R.id.tv_download);
mTvDownloadResult = findViewById(R.id.tv_download_result);
mTvDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "onClick");
startDownload();
}
});
}
private void startDownload() {
Log.d(TAG, "startDownload: start");
//创建被观察者
Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(@NonNull ObservableEmitter<Integer> emitter) throws Throwable {
for (int i = 0;i < 100;i++) {
if (i % 20 == 0) {
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
emitter.onNext(i);
}
}
emitter.onComplete();
}
});
//创建观察者
DisposableObserver<Integer> disposableObserver = new DisposableObserver<Integer>() {
@Override
public void onNext(@NonNull Integer integer) {
Log.d(TAG, "onNext: integer == " + integer);
mTvDownloadResult.setText(integer + "");
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "onError: Error");
mTvDownloadResult.setText("Download Error");
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete Download Complete");
mTvDownloadResult.setText("下载完成");
}
};
//观察者订阅被观察者
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
mCompositeDisposable.add(disposableObserver);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: start");
mCompositeDisposable.clear();
}
}
3、示例解析
3.1、线程切换
在上面的例子中,涉及到了两种类型的操作:
- 需要在后台执行的耗时操作,对应于subscribe(ObservableEmitter<Integer> e)中的代码。
- 需要在主线程进行UI更新的操作,对应于DisposableObserver的所有回调,具体的是在onNext中进行进度的更新;在onComplete和onError中展示最终的处理结果。
那么,这两种类型操作所运行的线程是在哪里指定的呢,关键是下面这句:
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(disposableObserver);
- subscribeOn(Schedulers.io()):指定observable的subscribe方法运行在后台线程。
- observeOn(AndroidSchedulers.mainThread()):指定observer的回调方法运行在主线程。
这两个函数刚开始的时候很有可能弄混,我是这么记的,subscribeOn以s开头,可以理解为“上游”开头的谐音,也就是上游执行的线程。
关于这两个函数,还有一点说明:多次调用subscribeOn,会以第一次的为准;而多次调用observeOn则会以最后一次的为准,不过一般我们都不会这么干,就不举例子了。
3.2、线程的类型
subscribeOn/observeOn
都要求传入一个Schedulers
的子类,它就代表了运行线程类型,下面我们来看一下都有哪些选择:
-
Schedulers.computation()
:用于计算任务,默认线程数等于处理器的数量。 -
Schedulers.from(Executor executor)
:使用Executor
作为调度器,关于Executor
框架可以参考这篇文章:多线程知识梳理(5) - 线程池四部曲之 Executor 框架。 -
Schedulers.io( )
:用于IO
密集型任务,例如访问网络、数据库操作等,也是我们最常使用的。 -
Schedulers.newThread( )
:为每一个任务创建一个新的线程。 -
Schedulers.trampoline( )
:当其它排队的任务完成后,在当前线程排队开始执行。 -
Schedulers.single()
:所有任务共用一个后台线程。
以上是在io.reactivex.schedulers
包中,提供的Schedulers
,而如果我们导入了下面的依赖,那么在io.reactivex.android.schedulers
下,还有额外的两个Schedulers
可选:
-
AndroidSchedulers.mainThread()
:运行在应用程序的主线程。 -
AndroidSchedulers.from(Looper looper)
:运行在该looper对应的线程当中。
3.3、使用 CompositeDisposable 对下游进行管理
如果Activity
要被销毁时,我们的后台任务没有执行完,那么就会导致Activity
不能正常回收,而对于每一个Observer
,都会有一个Disposable
对象用于管理,而RxJava提供了一个CompositeDisposable
类用于管理这些Disposable
,我们只需要将其将入到该集合当中,在Activity
的onDestroy
方法中,调用它的clear
方法,就能避免内存泄漏的发生。