RxJava2 實戰知識梳理(1) 後臺執行耗時操作,實時通知 UI 更新

澤毛發表於2017-12-21

一、前言

接觸RxJava2已經很久了,也看了網上的很多文章,發現基本都是在對RxJava的基本思想介紹之後,再去對各個操作符進行分析,但是看了之後感覺過了不久就忘了。

偶然的機會看到了開源專案 RxJava-Android-Samples,這裡一共介紹了十六種RxJava2的使用場景,它從實際的應用場景出發介紹RxJava2的使用,特別適合對於RxJava2已經有初步瞭解的開發者進一步地去學習如何將其應用到實際開發當中。

因此,我打算跟著這個專案的思路編寫一系列實戰的介紹並完成示例程式碼編寫,並對該例項中用到的知識進行介紹,做到學以致用。下面,就開始第一個例子的學習,原始碼的倉庫為:RxSample

二、示例

2.1 應用場景

當我們需要進行一些耗時操作,例如下載、訪問資料庫等,為了不阻塞主執行緒,往往會將其放在後臺進行處理,同時在處理的過程中、處理完成後通知主執行緒更新UI,這裡就涉及到了後臺執行緒和主執行緒之間的切換。首先回憶一下,在以前我們一般會用以下兩種方式來實現這一效果:

  • 建立一個新的子執行緒,在其run()方法中執行耗時的操作,並通過一個和主執行緒Looper關聯的Handler傳送訊息給主執行緒更新進度顯示、處理結果。
  • 使用AsyncTask,在其doInBackground方法中執行耗時的操作,呼叫publishProgress方法通知主執行緒,然後在onProgressUpdate中更新進度顯示,在onPostExecute中顯示最終結果。

那麼,讓我們看一些在RxJava中如何完成這一需求。

2.2 示例程式碼

我們的介面上有一個按鈕mTvDownload,點選之後會發起一個耗時的任務,這裡我們用Thread.sleep來模擬耗時的操作,每隔500ms我們會將當前的進度通知主執行緒,在mTvDownloadResult中顯示當前處理的進度。

public class BackgroundActivity extends AppCompatActivity {

    private TextView mTvDownload;
    private TextView mTvDownloadResult;
    private CompositeDisposable mCompositeDisposable = new CompositeDisposable();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_background);
        mTvDownload = (TextView) findViewById(R.id.tv_download);
        mTvDownloadResult = (TextView) findViewById(R.id.tv_download_result);
        mTvDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startDownload();
            }
        });
    }

    private void startDownload() {
        final Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {

            @Override
            public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                for (int i = 0; i < 100; i++) {
                    if (i % 20 == 0) {
                        try {
                            Thread.sleep(500); //模擬下載的操作。
                        } catch (InterruptedException exception) {
                            if (!e.isDisposed()) {
                                e.onError(exception);
                            }
                        }
                        e.onNext(i);
                    }
                }
                e.onComplete();
            }

        });
        DisposableObserver<Integer> disposableObserver = new DisposableObserver<Integer>() {

            @Override
            public void onNext(Integer value) {
                Log.d("BackgroundActivity", "onNext=" + value);
                mTvDownloadResult.setText("Current Progress=" + value);
            }

            @Override
            public void onError(Throwable e) {
                Log.d("BackgroundActivity", "onError=" + e);
                mTvDownloadResult.setText("Download Error");
            }

            @Override
            public void onComplete() {
                Log.d("BackgroundActivity", "onComplete");
                mTvDownloadResult.setText("Download onComplete");
            }
        };
        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
        mCompositeDisposable.add(disposableObserver);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mCompositeDisposable.clear();
    }
}
複製程式碼

實際的執行結果如下:

RxJava2 實戰知識梳理(1)   後臺執行耗時操作,實時通知 UI 更新

三、示例解析

3.1 執行緒切換

在上面的例子中,涉及到了兩種型別的操作:

  • 需要在後臺執行的耗時操作,對應於subscribe(ObservableEmitter<Integer> e)中的程式碼。
  • 需要在主執行緒進行UI更新的操作,對應於DisposableObserver的所有回撥,具體的是在onNext中進行進度的更新;在onCompleteonError中展示最終的處理結果。

那麼,這兩種型別操作所執行的執行緒是在哪裡指定的呢,關鍵是下面這句:

observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
複製程式碼
  • subscribeOn(Schedulers.io()):指定observablesubscribe方法執行在後臺執行緒。
  • observeOn(AndroidSchedulers.mainThread()):指定observer的回撥方法執行在主執行緒。

這兩個函式剛開始的時候很有可能弄混,我是這麼記的,subscribeOns開頭,可以理解為“上游”開頭的諧音,也就是上游執行的執行緒。

關於這兩個函式,還有一點說明:多次呼叫subscribeOn,會以第一次的為準;而多次呼叫observeOn則會以最後一次的為準,不過一般我們都不會這麼幹,就不舉例子了。

3.2 執行緒的型別

subscribeOn/observeOn都要求傳入一個Schedulers的子類,它就代表了執行執行緒型別,下面我們來看一下都有哪些選擇:

  • Schedulers.computation():用於計算任務,預設執行緒數等於處理器的數量。
  • Schedulers.from(Executor executor):使用Executor作為排程器,關於Executor框架可以參考這篇文章:多執行緒知識梳理(5) - 執行緒池四部曲之 Executor 框架
  • Schedulers.immediate( ):在當前執行緒執行任務
  • Schedulers.io( ):用於IO密集型任務,例如訪問網路、資料庫操作等,也是我們最常使用的。
  • Schedulers.newThread( ):為每一個任務建立一個新的執行緒。
  • Schedulers.trampoline( ):當其它排隊的任務完成後,在當前執行緒排隊開始執行。
  • Schedulers.single():所有任務共用一個後臺執行緒。

以上是在io.reactivex.schedulers包中,提供的Schedulers,而如果我們匯入了下面的依賴,那麼在io.reactivex.android.schedulers下,還有額外的兩個Schedulers可選:

compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
複製程式碼
  • AndroidSchedulers.mainThread():執行在應用程式的主執行緒。
  • AndroidSchedulers.from(Looper looper):執行在該looper對應的執行緒當中。

3.3 使用 CompositeDisposable 對下游進行管理

如果Activity要被銷燬時,我們的後臺任務沒有執行完,那麼就會導致Activity不能正常回收,而對於每一個Observer,都會有一個Disposable物件用於管理,而RxJava提供了一個CompositeDisposable類用於管理這些Disposable,我們只需要將其將入到該集合當中,在ActivityonDestroy方法中,呼叫它的clear方法,就能避免記憶體洩漏的發生。

四、小結

這個系列的第一篇文章,我們介紹瞭如何使用subscribeOn/observeOn來實現後臺執行耗時任務,並通知主執行緒更新進度。


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章