RxJava:如何重置长时间运行的热可观察链?

时间:2015-08-04 20:35:34

标签: java android system.reactive rx-java rx-android

对于我应用的搜索功能,我有一个热门的可观察链,可以执行以下操作。

  1. 接受用户输入字符串为EditTextTextChangedEvent)(mainThread
  2. 去抖动300毫秒(在computation线程上)
  3. 显示加载微调器(mainThread
  4. 使用该字符串查询SQL数据库(此查询可能需要100毫秒到2000毫秒)(在Schedulers.io()上)
  5. 向用户显示结果(mainThread
  6. 由于步骤3的长度变化很大,因此会出现竞争条件,其中最近的搜索结果显示在最近的结果上(有时)。假设用户想要输入chicken,但由于奇怪的打字速度,这个单词的第一部分会在整个术语之前发出:

    • 首先发送chick的搜索结果,然后发送chicken
    • chick执行1500ms执行chicken执行300ms
    • 这会导致chick搜索结果错误地显示在搜索字词chicken中。这是因为chicken搜索首先完成(仅花费300毫秒),然后是chick搜索(1500毫秒)。

    我该如何处理这种情况?

    • 一旦用户通过TextChangedEvent触发新搜索,我就不关心旧搜索,即使它仍在运行。有没有办法取消旧搜索?

    完整的可观察代码:

    subscription = WidgetObservable.text(searchText)
                    .debounce(300, TimeUnit.MILLISECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                            //do this on main thread because it's a UI element (cannot access a View from a background thread)
    
                            //get a String representing the new text entered in the EditText
                    .map(new Func1<OnTextChangeEvent, String>() {
                        @Override
                        public String call(OnTextChangeEvent onTextChangeEvent) {
                            return onTextChangeEvent.text().toString().trim();
                        }
                    })
                    .subscribeOn(AndroidSchedulers.mainThread())
                    .doOnNext(new Action1<String>() {
                        @Override
                        public void call(String s) {
                            presenter.handleInput(s);
                        }
                    })
                    .subscribeOn(AndroidSchedulers.mainThread())
                    .observeOn(Schedulers.io())
                    .filter(new Func1<String, Boolean>() {
                        @Override
                        public Boolean call(String s) {
                            return s != null && s.length() >= 1 && !s.equals("");
                        }
                    }).doOnNext(new Action1<String>() {
                        @Override
                        public void call(String s) {
                            Timber.d("searching for string: '%s'", s);
                        }
                    })
                            //run SQL query and get a cursor for all the possible search results with the entered search term
                    .flatMap(new Func1<String, Observable<SearchBookmarkableAdapterViewModel>>() {
                        @Override
                        public Observable<SearchBookmarkableAdapterViewModel> call(String s) {
                            return presenter.getAdapterViewModelRx(s);
                        }
                    })
                    .subscribeOn(Schedulers.io())
                            //have the subscriber (the adapter) run on the main thread
                    .observeOn(AndroidSchedulers.mainThread())
                            //subscribe the adapter, which receives a stream containing a list of my search result objects and populates the view with them
                    .subscribe(new Subscriber<SearchBookmarkableAdapterViewModel>() {
                        @Override
                        public void onCompleted() {
                            Timber.v("Completed loading results");
                        }
    
                        @Override
                        public void onError(Throwable e) {
                            Timber.e(e, "Error loading results");
                            presenter.onNoResults();
                            //resubscribe so the observable keeps working.
                            subscribeSearchText();
                        }
    
                        @Override
                        public void onNext(SearchBookmarkableAdapterViewModel searchBookmarkableAdapterViewModel) {
                            Timber.v("Loading data with size: %d into adapter", searchBookmarkableAdapterViewModel.getSize());
                            adapter.loadDataIntoAdapter(searchBookmarkableAdapterViewModel);
                            final int resultCount = searchBookmarkableAdapterViewModel.getSize();
                            if (resultCount == 0)
                                presenter.onNoResults();
                            else
                                presenter.onResults();
                        }
                    });
    

1 个答案:

答案 0 :(得分:2)

使用switchMap代替flatMap。这将导致它在您开始新查询时丢弃*上一个查询。

*这是如何运作的:

每当外部源observable产生一个新值时,switchMap会调用您的选择器返回一个新的内部可观察对象(在这种情况下为presenter.getAdapterViewModelRx(s))。 switchMap然后取消订阅来自它正在侦听的上一个内部可观察对象,订阅到新的。

取消订阅前一个内部观察点有两个效果:

  1. observable产生的任何通知(值,完成,错误等)将被默默地忽略并丢弃。

  2. 将通知观察者其观察者已取消订阅,并可选择采取措施取消其所代表的任何异步过程。

  3. 您的遗弃查询是否实际取消完全取决于presenter.getAdapterViewModelRx()的实施情况。理想情况下,它们会被取消,以避免不必要地浪费服务器资源。但即使它们继续运行,上面的#1也会阻止你的输入类型代码看到陈旧的结果。