Android非同步、延遲和定時任務的簡易用法

_阿南_發表於2019-01-18

程式猿的要求不高

非同步多執行緒

延遲觸發

迴圈定時觸發

一、前言

專案開發中一定會用到網路請求,檔案讀寫,開啟子執行緒進行耗時操作,延遲返回或關閉提示框,輪詢介面獲取處理結果,子執行緒傳送內容到主執行緒更新介面等等。碰到這些問題或需求的時候,每位程式猿都會使用自己喜歡或習慣的用法來實現或介面問題。當然結果是功能完成了或問題修復了。但是程式碼風格的差異,使用時考慮不全,使用方法不是最優,等等多多少少存在一些瑕疵。例如:

  • 阿里的java程式設計規範不推薦顯式使用Thread
  • 直接使用AsyncTask存在記憶體洩露或者weak用法導致空指標的問題
  • 使用Handler進行更新介面的複雜操作
  • 使用postDelayed()進行延遲操作不能在子執行緒中使用
  • 使用TimerTask進行輪詢時複雜的更新頁面 綜上,特別想在程式碼上統一用法,並且是簡單又安全,而且效能最優。所以想到RxJava是不是可以來實現這個願望。嘗試和研究中記錄的Demo,有使用錯誤或更好的方案,請多指教。

二、 鋪墊

  1. RxJava整合 專案中先整合RxJava庫
api 'io.reactivex.rxjava2:rxandroid:2.1.0'
api 'io.reactivex.rxjava2:rxjava:2.2.5'
api 'com.squareup.retrofit2:retrofit:2.5.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
api 'com.squareup.retrofit2:converter-gson:2.5.0'
複製程式碼

關於RxJava的方法介紹或基礎使用,請參考RxJava2在Android中的使用

  1. RxJava多執行緒 使用RxJava進行多執行緒操作的原理、用法及其他,請參考在 Andoid 中如何使用 RxJava 2 進行多執行緒程式設計?.

  2. Scheduler 針對多執行緒的操作,Schedulers的引數瞭解,請參考我所理解的RxJava——上手其實很簡單(三);

三、非同步

最直接的用法就是new Thread()建立一個子執行緒,然後用EventMessage或Handler傳送Message來更新頁面。更好一點的方法是ExecutorService建立執行緒池,統一管理執行緒並且複用執行緒以及控制執行緒的總數,但是需要再花點時間維護和優化。但是不建議為每一個Activity或fragment建立一個執行緒池,從效能和執行緒複用率上沒有必要性。

private void testCreate() {
        Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                Log.e("wyn", "ObservableEmitter");

                Log.e("wyn", "ObservableEmitter thread is " + Thread.currentThread().getName());


                long a = 1;
                for (int i = 0; i < 1000000000; i++) {
                    a = a + (a + 1);
                }

                Log.e("wyn", "a is " + a);

                emitter.onNext("wang" + a);
                emitter.onNext("yinan");

                emitter.onComplete();
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this.observerString);
    }
複製程式碼

列印的結果

2019-01-18 11:03:08.326 27120-27120/? E/wyn: onSubscribe
2019-01-18 11:03:08.326 27120-27120/? E/wyn: onSubscribe thread is main
2019-01-18 11:03:08.328 27120-27138/? E/wyn: ObservableEmitter
2019-01-18 11:03:08.328 27120-27138/? E/wyn: ObservableEmitter thread is RxCachedThreadScheduler-1
2019-01-18 11:03:13.226 27120-27138/com.example.RxThread E/wyn: a is -1
2019-01-18 11:03:13.227 27120-27120/com.example.RxThread D/wyn: onNext is wang-1
2019-01-18 11:03:13.227 27120-27120/com.example.RxThread E/wyn: onNext thread is main
2019-01-18 11:03:13.229 27120-27120/com.example.RxThread D/wyn: onNext is yinan
2019-01-18 11:03:13.229 27120-27120/com.example.RxThread E/wyn: onNext thread is main
2019-01-18 11:03:13.229 27120-27120/com.example.RxThread E/wyn: onComplete
2019-01-18 11:03:13.229 27120-27120/com.example.RxThread E/wyn: onComplete thread is main
複製程式碼

重點

使用create方法簡易的實現子執行緒操作(subscribeOn設定子執行緒型別),傳送內容(onNext傳送內容)到主執行緒(observeOn設定在主執行緒操作)更新介面。

四、延遲

最直接的方案就是postDelayed()觸發一個延遲的操作。如果是在子執行緒進行postDelayed()操作,那麼不能直接使用,會崩潰。

private void testTimer() {
        Observable.timer(3, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this.observer);
    }
複製程式碼

輸出:

2019-01-18 11:31:11.820 28231-28231/com.example.RxThread E/wyn: onSubscribe
2019-01-18 11:31:11.820 28231-28231/com.example.RxThread E/wyn: onSubscribe thread is main
2019-01-18 11:31:14.832 28231-28231/com.example.RxThread D/wyn: onNext is 0
2019-01-18 11:31:14.833 28231-28231/com.example.RxThread E/wyn: onNext thread is main
2019-01-18 11:31:14.835 28231-28231/com.example.RxThread E/wyn: onComplete
2019-01-18 11:31:14.835 28231-28231/com.example.RxThread E/wyn: onComplete thread is main
複製程式碼

Timer可以在子執行緒進行延遲操作,那麼輸出結果為:

2019-01-18 11:33:14.018 28398-28419/? E/wyn: onSubscribe
2019-01-18 11:33:14.019 28398-28419/? E/wyn: onSubscribe thread is Thread-2
2019-01-18 11:33:17.026 28398-28398/com.example.RxThread D/wyn: onNext is 0
2019-01-18 11:33:17.027 28398-28398/com.example.RxThread E/wyn: onNext thread is main
2019-01-18 11:33:17.029 28398-28398/com.example.RxThread E/wyn: onComplete
2019-01-18 11:33:17.030 28398-28398/com.example.RxThread E/wyn: onComplete thread is main
複製程式碼

重點

timer設定延遲的時間,然後在主執行緒更新介面。

五、定時、輪詢、迴圈

一般輪詢介面獲取資料或倒數計時顯示內容,使用TimerTask來實現,然後採用Handler傳送Message更新介面。

private void testInterval() {
        Observable.interval(3, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this.observer);
    }
複製程式碼

輸出結果:

2019-01-18 11:41:42.108 29064-29064/? E/wyn: onSubscribe
2019-01-18 11:41:42.108 29064-29064/? E/wyn: onSubscribe thread is main
2019-01-18 11:41:45.115 29064-29064/com.example.RxThread D/wyn: onNext is 0
2019-01-18 11:41:45.115 29064-29064/com.example.RxThread E/wyn: onNext thread is main
2019-01-18 11:41:48.112 29064-29064/com.example.RxThread D/wyn: onNext is 1
2019-01-18 11:41:48.112 29064-29064/com.example.RxThread E/wyn: onNext thread is main
2019-01-18 11:41:51.113 29064-29064/com.example.RxThread D/wyn: onNext is 2
2019-01-18 11:41:51.114 29064-29064/com.example.RxThread E/wyn: onNext thread is main
2019-01-18 11:41:54.113 29064-29064/com.example.RxThread D/wyn: onNext is 3
2019-01-18 11:41:54.114 29064-29064/com.example.RxThread E/wyn: onNext thread is main
.......
複製程式碼

重點

interval間隔指定的時間,在主執行緒執行操作。

六、補充

測試程式碼中使用到的observer和observerString,

private Observer<Long> observer = new Observer<Long>() {
        Disposable disposable;

        @Override
        public void onSubscribe(Disposable d) {
            Log.e("wyn", "onSubscribe");

            Log.e("wyn", "onSubscribe thread is " + Thread.currentThread().getName());

            disposable = d;
        }

        @Override
        public void onNext(Long s) {
            Log.d("wyn", "onNext is " + s);

            Log.e("wyn", "onNext thread is " + Thread.currentThread().getName());

            tvContent.setText(s + "");

            if (s == 10) {
                disposable.dispose();
            }
        }

        @Override
        public void onError(Throwable e) {
            Log.e("wyn", "onError");

            Log.e("wyn", "onError thread is " + Thread.currentThread().getName());
        }

        @Override
        public void onComplete() {
            Log.e("wyn", "onComplete");

            Log.e("wyn", "onComplete thread is " + Thread.currentThread().getName());
        }
    };

    private Observer<String> observerString = new Observer<String>() {
        Disposable disposable;

        @Override
        public void onSubscribe(Disposable d) {
            Log.e("wyn", "onSubscribe");

            Log.e("wyn", "onSubscribe thread is " + Thread.currentThread().getName());

            disposable = d;
        }

        @Override
        public void onNext(String s) {
            Log.d("wyn", "onNext is " + s);

            Log.e("wyn", "onNext thread is " + Thread.currentThread().getName());

            tvContent.setText(s);
        }

        @Override
        public void onError(Throwable e) {
            Log.e("wyn", "onError");

            Log.e("wyn", "onError thread is " + Thread.currentThread().getName());
        }

        @Override
        public void onComplete() {
            Log.e("wyn", "onComplete");

            Log.e("wyn", "onComplete thread is " + Thread.currentThread().getName());
        }
    };
複製程式碼

七、注意

選擇子執行緒操作的時候,如果有檔案操作那麼一定要用Schedulers.io()。不然建議使用Schedulers.computation().

八、其他

從友盟統計iOS和Android的崩潰來看,Android的空指標崩潰真的是多如牛毛啊。為啥不整一個nil型別,從系統級別上,進行全域性的空指標的保護呢!少一點崩潰,多一點快樂!!!

// END

相關文章