使用RxJava来改进用户体验
来源:互联网 发布:淘宝有信用卡套现的吗 编辑:程序博客网 时间:2024/05/21 11:23
一个完美的移动世界永远不会失去连接,而服务端也永远不会返回错误。
构建一个很棒的app对于用户来说是幸福的事而对于开发者来说则是痛苦的事。用户点击一个按钮就阻塞了所有操作的时代已经过去了,那是要死人的。
让我们来创建一个更好的文本框搜索功能并关注以下需求
尽可能少的请求
对用户尽可能少的错误信息
RX 的逻辑相当简单,重点在完善细微的细节上。
让我们从简单的逻辑开始:
当用户输入内容的时候我们发出了一个网络请求然后获得结果:
RxTextView.textChanges(searchEditText)
.flatMap(Api::searchItems)
.subscribe(
this
::updateList, t->showError());
减少网络请求
以上存在两个问题:
每输入一个字母(对的这很坑)比如:用户快速输入了一个“a”,然后“ab”然后“abc”然后又纠正为“ab”并最终想搜索“abe”。这样你就做了5次网络请求。想象一在网速很慢的时候是个什么情况。
你还面临一个线程赛跑的问题。比如:用户输入了“a”,然后是“ab”。“ab”的网络调用发生在前而”a“的调用发生在后。那样的话updateList() 将根据 “a”的请求结果来执行。
解决:
添加调节行为:
你需要的是debounce() 。根据我的经验,取值在100–150毫秒效果最好。如果你的服务器需要额外的300毫秒那么你可以在0.5秒之内做UI更新。
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.flatMap(Api::searchItems)
.subscribe(
this
::updateList, t->showError());
杀死前面的请求:
引入 switchMap来替代flatMap。它会停止前面发出的items。所以如果在0+150ms时你搜索“ab”,在 0+300ms时搜索“abcd”,但是“ab”的网络调用需要 150ms以上的时间才能完成,那么到了开始“abcd”调用的时候前面的那个会被取消。这样你总是能得到最近的请求数据。
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.switchMap(Api::searchItems)
.subscribe(
this
::updateList, t->showError());
2. No error functionality / no network functionality
如果所有的网络调用都失败,那么你将不能再次观察到text的改变。
这可以通过添加 error catching functionality来解决。
因此你可以用:
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.switchMap(Api::searchItems)
.onErrorResumeNext(t-> empty())
.subscribe(
this
::updateList);
Don’t do that. Let’s make it smarter. What if the searchItems() api call above calls because of connectivity? Or even more “UX-depressingly” brief connectivity that the user didn’t notice?
别这么做。让我们让它更智能些。要是 searchItems() api调用因为网络连接的问题发生在其它调用之前呢?
你需要这样的一个重试机制:
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.switchMap(Api::searchItems)
.retryWhen(
new
RetryWithConnectivity())
.subscribe(
this
::updateList, t->showError());
如何进一步改进呢?添加一个超时(timeout)。就如我们的用户体验设计师 Leander Lenzing 所说的:“1秒对于用户来说是一个很长的时间”。所以上面的代码应该这样:
RxTextView.textChanges(searchEditText)
.debounce(150, MILLISECONDS)
.switchMap(Api::searchItems)
.retryWhen(
new
RetryWithConnectivityIncremental(context, 5, 15, SECONDS))
.subscribe(
this
::updateList, t->showErrorToUser());
那么RetryWithConnectivityIncremental 和RetryWithConnectivity 会做些什么呢?它将等待5秒让手机网络畅通,如果超过则会抛出一个异常。如果用户重试它则会等待更长的超时时间(比如15秒)。
这里是代码:
BroadcastObservable.java hosted with ❤ by GitHub
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Looper;
import rx.Observable;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;
public class BroadcastObservable implements Observable.OnSubscribe<Boolean> {
private final Context context;
public static Observable<Boolean> fromConnectivityManager(Context context) {
return
Observable.create(
new
BroadcastObservable(context))
.share();
}
public BroadcastObservable(Context context) {
this
.context = context;
}
@Override
public void call(Subscriber<?
super
Boolean> subscriber) {
BroadcastReceiver receiver =
new
BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
subscriber.onNext(isConnectedToInternet());
}
};
context.registerReceiver(receiver,
new
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
subscriber.add(unsubscribeInUiThread(() -> context.unregisterReceiver(receiver)));
}
private boolean isConnectedToInternet() {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
return
networkInfo !=
null
&& networkInfo.isConnected();
}
private static Subscription unsubscribeInUiThread(final Action0 unsubscribe) {
return
Subscriptions.create(() -> {
if
(Looper.getMainLooper() == Looper.myLooper()) {
unsubscribe.call();
}
else
{
final Scheduler.Worker inner = AndroidSchedulers.mainThread().createWorker();
inner.schedule(() -> {
unsubscribe.call();
inner.unsubscribe();
});
}
});
}
}
RetryWithConnectivityIncremental.java hosted with ❤ by GitHub
import android.content.Context;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import rx.Observable;
import rx.functions.Func1;
public class RetryWithConnectivityIncremental implements Func1<Observable<? extends Throwable>, Observable<?>> {
private final int maxTimeout;
private final TimeUnit timeUnit;
private final Observable<Boolean> isConnected;
private final int startTimeOut;
private int timeout;
public RetryWithConnectivityIncremental(Context context, int startTimeOut, int maxTimeout, TimeUnit timeUnit) {
this
.startTimeOut = startTimeOut;
this
.maxTimeout = maxTimeout;
this
.timeUnit = timeUnit;
this
.timeout = startTimeOut;
isConnected = getConnectedObservable(context);
}
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return
observable.flatMap((Throwable throwable) -> {
if
(throwable
instanceof
RetrofitError && ((RetrofitError) throwable).getKind() == RetrofitError.Kind.NETWORK) {
return
isConnected;
}
else
{
return
Observable.error(throwable);
}
}).compose(attachIncementalTimeout());
}
private Observable.Transformer<Boolean, Boolean> attachIncementalTimeout() {
return
observable -> observable.timeout(timeout, timeUnit)
.doOnError(throwable -> {
if
(throwable
instanceof
TimeoutException) {
timeout = timeout > maxTimeout ? maxTimeout : timeout + startTimeOut;
}
});
}
private Observable<Boolean> getConnectedObservable(Context context) {
return
BroadcastObservable.fromConnectivityManager(context)
.distinctUntilChanged()
.filter(isConnected -> isConnected);
}
}
以上。你节制了你的请求,你总是能得到最近的请求结果,你有重试连接的智能超时处理机制。
英文原文:Improving UX with RxJava
注:
还可以参考Hanks 在简书上的译文:http://www.jianshu.com/p/33c548bce571 以及译文作者根据文章制作的一个demo:https://github.com/hanks-zyh/RxSerach
- 使用RxJava来改进用户体验
- 使用RxJava来改进用户体验
- 使用RxJava来改进用户体验
- 使用RxJava 提升用户体验
- Web中使用多线程来增强用户体验
- Web中使用多线程来增强用户体验
- Web中使用多线程来增强用户体验
- Web中使用多线程来增强用户体验
- Web中使用多线程来增强用户体验
- 使用渐进式 JPEG 来提升用户体验
- 使用渐进式JPEG来提升用户体验
- 使用渐进式 JPEG 来提升用户体验
- 使用渐进式JPEG来提升用户体验
- 【转载】使用渐进式JPEG来提升用户体验
- iOS开发——使用MBProgressHUD来增加用户体验
- 使用angularjs的键盘事件来增强用户体验
- 使用渐进式 JPEG 来提升用户体验
- 使用渐进式JPEG来提升用户体验
- 《您的设计模式》(CBF4LIFE)之“代理模式”【整理】
- 大型门户网站架构分析
- mysql数据库导出导入
- CS231n 卷积神经网络与计算机视觉 8 手把手实现神经网络分类
- sigma.js框架初探
- 使用RxJava来改进用户体验
- 利用freemarker 静态化网页 多代码
- BZOJ 1202 带权并查集
- 关于画布反转问题
- 【Linux系列教程】01.在虚拟机中安装Centos7.0
- 《您的设计模式》(CBF4LIFE)之“单例模式”【整理】
- hdu-1317-XYZZY-Bellman-Ford判环、Floyd算法
- 工厂模式三部曲-工厂方法模式
- bzoj3289 Mato的文件管理