使用rxjava Android进行同步调用

时间:2016-09-06 17:22:52

标签: android rx-java observable retrofit2 rx-android

我的应用有一个SearchView。当用户键入SearchView时,onQueryTextChange将查询传递给演示者,然后调用API。我正在使用Retrofit和RxJava进行调用。调用返回一个json文件,其中包含用户目前键入的内容。问题在于,如果用户快速输入字母并且网络速度慢,则有时SearchView不会根据所有键入的字母显示结果,但可能最后一次显示结果,因为最后一次调用更快结果与第二个结果相比较。

实施例: 用户开始输入:

"凑" - >拨打API(3个字母后的第一个电话) - >开始返回值

" N" - >拨打电话 - >开始返回值

" T" - >拨打电话 - >开始返回值

" R" - >打电话(连接很慢)

" Y" - >拨打电话 - >开始返回值

- > " R"最后获得结果并返回它们

public Observable<List<MyModel>> getValues(String query) {
    return Observable.defer(() -> mNetworkService.getAPI()
            .getValues(query)
            .retry(2)
            .onErrorReturn(e -> new ArrayList<>()));
}

通话非常简单,每当我收到错误时,我都不想显示任何内容。

有办法解决这个问题吗?或者也许这不是使用反应式编程的情况?

编辑: 为了更清楚,流程如下:

  1. 使用自定义搜索视图的活动(https://github.com/Mauker1/MaterialSearchView

  2. 当用户开始输入时,自定义searchview有一个监听器。用户开始输入活动后,请调用Presenter。

  3. 演示者将订阅由交互者返回的观察者:

  4. 主持人:

    addSubscription(mInteractor.getValues(query)
                .observeOn(mMainScheduler)
                .subscribeOn(mIoScheduler)
                .subscribe(data -> {
                    getMvpView().showValues(data);
                }, e -> {
                    Log.e(TAG, e.getMessage());
                }));
    

    相互作用物:

    public Observable<List<MyModel>> getValues(String query) {
        return Observable.defer(() -> mNetworkService.getAPI()
                .getValues(query)
                .debounce(2, TimeUnit.SECONDS)
                .retry(2)
                .onErrorReturn(e -> new ArrayList<>()));
    

    所以,现在要么改变正常的自定义搜索视图&#39; searchview,然后使用RxBinding或者我应该使用处理程序或类似的东西(但仍然在努力如何使其适应我的架构)

5 个答案:

答案 0 :(得分:1)

你很幸运,有一个叫做去抖动的算子

Observable.defer(() -> mNetworkService.getAPI()
            .getValues(query)
            .debounce(3, TimeUnit.SECONDS)
            .retry(2)
            .onErrorReturn(e -> new ArrayList<>()));

debounce的作用是在继续之前等待N个时间单位以获得更多结果。例如,网络需要2秒才能返回,并且您在请求后请求泛洪,debounce将等待3秒没有结果,然后返回最后的结果。可以把它想象成除了N时间之前的所有东西。

这解决了你的问题,但仍然会泛滥网络,理想情况下你会使用优秀的RxBinding库,在发出请求之前做延迟:

RxTextView.textChanges(searchView)
.debounce(3, TimeUnit.SECONDS)
.map(input->mNetworkService.getAPI().getValues(input.queryText().toString()))
.retry(2)
.onErrorReturn(e -> new ArrayList<>()))

使用当前设置,它将在用户输入内容后等待3秒,然后才进行网络呼叫。如果他们开始输入新内容,则会删除第一个待处理的搜索请求。

编辑:更改为基于OP不使用Android SearchView小部件的RxTextView.textChanges(textview)

答案 1 :(得分:1)

首先将您的Searchview设为可观察,以便您可以应用Rx运算符。 要将searchview转换为Observable

public static Observable<String> fromview(SearchView searchView) {
final PublishSubject<String> subject = PublishSubject.create();

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    @Override
    public boolean onQueryTextSubmit(String s) {
        subject.onComplete();
        searchView.clearFocus(); //if you want to close keyboard
        return false;
    }

    @Override
    public boolean onQueryTextChange(String text) {
        subject.onNext(text);
        return false;
    }
});

return subject;

}

private void observeSearchView() {

disposable = RxSearchObservable.fromview(binding.svTweet)
        .debounce(300, TimeUnit.MILLISECONDS)
        .filter(text -> !text.isEmpty() && text.length() >= 3)
        .map(text -> text.toLowerCase().trim())
        .distinctUntilChanged()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe()

}

您可以应用过滤条件 RxJava 反跳()运算符可延迟执行任何操作,直到用户短暂暂停为止。

使用 distinctUntilChanged ()可确保用户可以搜索相同的内容两次,但不能立即背对背

在这种情况下, filter 运算符用于过滤不需要的字符串(例如空字符串),以避免不必要的网络调用。

Handling searchview withRXJava

答案 2 :(得分:0)

扩展@MikeN所说的内容,如果你只想使用LAST输入的结果,你应该使用switchMap()(在其他一些Rx实现中为flatMapLatest())。

答案 3 :(得分:0)

我在不使用RxBinding的情况下解决了洪水问题,我想发布我的解决方案以防万一其他人需要它。 因此,每当调用onTextChanged时,我首先检查大小是否为&gt; 2,如果它连接到网络(由BroadcastReceiver更新布尔值)。然后我创建要发送的消息已延迟,我删除队列中的所有其他消息。这意味着我将只执行不在指定延迟范围内的查询:

@Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

        if (TextUtils.getTrimmedLength(s) > 2 && isConnected) {
            mHandler.removeMessages(QUERY_MESSAGE);
            Message message = Message.obtain(mHandler, QUERY_MESSAGE, s.toString().trim());
            mHandler.sendMessageDelayed(message, MESSAGE_DELAY_MILLIS);
        }
    }

然后是处理程序:

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        if (msg.what == QUERY_MESSAGE) {
            String query = (String)msg.obj;
            mPresenter.getValues(query);
        }
    }
};

答案 4 :(得分:0)

  1. 将rxbinding依赖项添加到gradle实现"com.jakewharton.rxbinding2:rxbinding-kotlin:2.1.1"
  2. 使用反跳和distinct来忽略频繁的按键输入和重复的输入
  3. 处置先前的API调用以仅获取最新的搜索结果
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.toolbar_menu, menu)

        // Associate searchable configuration with the SearchView
        val searchManager = requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
        searchView = menu.findItem(R.id.action_search).actionView as SearchView
        searchView.setSearchableInfo(
            searchManager.getSearchableInfo(requireActivity().componentName)
        )
        searchView.maxWidth = Integer.MAX_VALUE

        // listening to search query text change
        disposable = RxSearchView.queryTextChangeEvents(searchView)
            .debounce(750, TimeUnit.MILLISECONDS)
            .distinctUntilChanged()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                callApi(it.queryText().toString())
            }, {
                Timber.e(it)
            })

        super.onCreateOptionsMenu(menu, inflater)
    }

    private fun callApi(query: String){
        if(!apiDisposable.isDisposed){
            apiDisposable.dispose()
        }
        apiDisposable = mNetworkService.getAPI(query)
    }