RxJava 常見的錯誤用法
RxJava 用起來很爽,特別是和 retrofit 一起用了請求網絡數據。對于大部分初學者呢,都會出現這樣的用法:
service = GithubService.createGithubService(githubToken);
view.setOnClickListener( v -> service.user(name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(System.out::println));
當點擊一個按鈕的時候,去請求服務器數據然后使用返回的結果刷新 UI。(比如當前顯示用戶信息的界面上有個刷新按鈕,點擊一下就去請求數據并刷新界面)。
筆者就曾經寫過這樣的代碼。但是經過簡單的測試就發現這是有問題的!
1. 由于網絡請求是在后臺線程發生的,并且需要時間,如果網絡請求沒有完成,用戶點擊了返回按鍵退出了當前界面則會引起 Activity 泄露
2. 每次點擊刷新按鈕都會觸發一個新的網絡請求,同時網絡請求返回的順序是不確定的,可能導致收到的數據問題
3. 如果把收到的數據保存到一個集合中,多次點擊刷新按鈕會導致同樣的結果出現在數據集合中
比如輸入一個用戶名并點擊刷新按鈕查看其詳細資料,在結果沒有返回的時候,再次輸入一個新的用戶名并點擊刷新按鈕,則有可能第二次請求先返回,然后第一個請求結果才返回,這樣用戶最終看到的是第一個用戶名對應的詳細信息而不是第二個用戶的。
其中第一個問題比較好解決,使用一個 CompositeSubscription ,把每個 Subscription 都添加到這里,在 onDestroy 里面取消注冊即可(subscriptions.unsubscribe())。
subscriptions = new CompositeSubscription();
service = GithubService.createGithubService(githubToken);
view.setOnClickListener( v -> {
Subscriptionsub = service.user(name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(System.out::println);
subscriptions.add(sub);
});
但是另外兩個問題呢?
這里就要使用 Subject 來轉發 View 的onClick 事件了。例如下面使用 PublishSubject:
subject = PublishSubject.create();
subscriptions = new CompositeSubscription();
service = GithubService.createGithubService(githubToken);
view.setOnClickListener(v-> subject.onNext(v));
Subscriptionsub = subject.flatMap(v-> service.user(name))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(System.out::println);
subscriptions.add(sub);
這樣可以避免每次點擊刷新按鈕都創建一個新的 Subscription。而第三種情況,可以使用 switchMap 來解決
subject = PublishSubject.create();
subscriptions = new CompositeSubscription();
service = GithubService.createGithubService(githubToken);
view.setOnClickListener(v-> subject.onNext(v));
Subscriptionsub = subject.switchMap(v-> service.user(name))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(System.out::println);
subscriptions.add(sub);
另外為了避免用戶快速的點擊同一個按鈕,則可以使用 throttleFirst 來過濾掉后面一段時間內的點擊事件。
同時如果使用了 RxBinding 和 RxLifecycle 則代碼會更加簡潔清晰。
RxView.clicks(view)
.subscribeOn(AndroidSchedulers.mainThread())
.doOnNext(aVoid1 -> _adapter.clear())
.throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.io())
.switchMap(aVoid -> _githubService.contributors(_username.getText().toString(), _repo.getText().toString()))
.compose(bindUntilEvent(FragmentEvent.DESTROY_VIEW))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(contributors -> {
for (Contributor c : contributors) {
_adapter.add(format("%s has made %d contributions to %s",
c.login,
c.contributions,
_repo.getText().toString()));
}
});
上面的代碼根據 https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitFragment.java 中的代碼修改而來,可以把上面的代碼插入到 onCreateView 函數的 return 語句之前。同時取消掉 onListContributorsClicked 函數。
同時 RxJava-Android-Samples 這個項目有 3432 人加星 有 639 人 fork。 說明還是有不少人關注的,但是里面的示例用法還是有不少問題的。所以大家在參考示例項目學習的時候,一定要學會思考,不能直接復制粘貼代碼就拿來用了,RxJava-Android-Samples 項目只是告訴 Rxjava 初學者 RxJava 可以這么用,那是具體用的對不對就不一定了!
來自:http://blog.chengyunfeng.com/?p=1010