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

轉載自http://www.lxweimin.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方法,就能避免內存泄漏的發生。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。