使用rxjava Android进行同步调用

问题描述 投票:0回答:4

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

示例:用户开始输入:

“cou” - >调用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. 演示者将订阅由交互者返回的观察者:

主持人:

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<>()));

所以现在要么在'普通'搜索视图中更改自定义搜索视图然后使用RxBinding,要么我应该使用处理程序或类似的东西(但仍然在如何适应我的架构)

android rx-java observable retrofit2 rx-android
4个回答
1
投票

你很幸运有一个叫做去抖动的操作员

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

去抖动的作用是在继续之前等待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秒,然后才进行网络呼叫。如果他们开始输入新内容,则会删除第一个待处理的搜索请求。

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


1
投票

首先使您的Searchview成为Observable,以便您可以应用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()

}

您可以应用filter,条件RxJava debounce()运算符来延迟执行任何操作,直到用户暂停。

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

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

Handling searchview withRXJava


0
投票

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


0
投票

我在不使用RxBinding的情况下解决了泛滥问题,我想发布我的解决方案以防其他人需要它。因此,每当调用onTextChanged时,我首先检查大小是否> 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);
        }
    }
};
© www.soinside.com 2019 - 2024. All rights reserved.