RxJava3 实战知识梳理(1) - 后台执行耗时操作,实时通知 UI 更新

转载自https://www.jianshu.com/p/c935d0860186

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,我们只需要将其将入到该集合当中,在ActivityonDestroy方法中,调用它的clear方法,就能避免内存泄漏的发生。