我的应用程序有一个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<>()));
}
调用非常简单,每当我收到错误时,我都不想显示任何内容。
有办法解决这个问题吗?或者也许这不是使用反应式编程的情况?
编辑:为了更清楚,流程如下:
主持人:
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,要么我应该使用处理程序或类似的东西(但仍然在如何适应我的架构)
你很幸运有一个叫做去抖动的操作员
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小部件
首先使您的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()可确保用户可以搜索两次相同的内容,但不会立即背靠背
在这种情况下,过滤器运算符用于过滤不需要的字符串,如空字符串,以避免不必要的网络调用。
扩展@MikeN所说的,如果你只想使用LAST输入的结果,你应该使用switchMap()
(在其他一些Rx实现中是flatMapLatest()
)。
我在不使用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);
}
}
};