非同步多執行緒
延遲觸發
迴圈定時觸發
一、前言
專案開發中一定會用到網路請求,檔案讀寫,開啟子執行緒進行耗時操作,延遲返回或關閉提示框,輪詢介面獲取處理結果,子執行緒傳送內容到主執行緒更新介面等等。碰到這些問題或需求的時候,每位程式猿都會使用自己喜歡或習慣的用法來實現或介面問題。當然結果是功能完成了或問題修復了。但是程式碼風格的差異,使用時考慮不全,使用方法不是最優,等等多多少少存在一些瑕疵。例如:
- 阿里的java程式設計規範不推薦顯式使用Thread
- 直接使用AsyncTask存在記憶體洩露或者weak用法導致空指標的問題
- 使用Handler進行更新介面的複雜操作
- 使用postDelayed()進行延遲操作不能在子執行緒中使用
- 使用TimerTask進行輪詢時複雜的更新頁面 綜上,特別想在程式碼上統一用法,並且是簡單又安全,而且效能最優。所以想到RxJava是不是可以來實現這個願望。嘗試和研究中記錄的Demo,有使用錯誤或更好的方案,請多指教。
二、 鋪墊
- 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中的使用。
-
RxJava多執行緒 使用RxJava進行多執行緒操作的原理、用法及其他,請參考在 Andoid 中如何使用 RxJava 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