网络连接失败的处理
看过最前面那篇文章的应该很清楚retryWhen()是什么了。
我再来总结一下,retryWhen()的直面意思就是:发生错误了,接下来该做什么。
retryWhen()是RxJava的一种错误处理机制,当遇到错误时,将错误传递给另一个Observable来决定是否要重新给订阅这个Observable
延迟重试
来想象一个场景:用户用的2G网络或者WiFi信号不稳定,导致网络经常连接失败,其实这个时候只要多努力一下就可以连接成功了,如果此时弹出错误提示,体验肯定不好,所以这里就要用到重试机制
判断错误类型
再模拟一个场景:用户不是手机信号不好,而是根本就没打开网络,此时还傻傻的重试只是浪费电量而已,所以我们可以加一个判断,打开了网络才重试,没有打开网络就继续发送失败的消息。
加入重试超时
继续想,重试也不可能永远进行,一般都会设置一个重试超时的机制。
想一下,没有哪个APP只有一个接口地址吧 - -#,如果你用的Retrofit那么每一个接口返回的Observable都要手动加上上面的重试代码,如果是我,我肯定报警了……所以我们必须把刚刚写的重试代码封装成一个类:
public class TryWhenTransaction implements Func1<Observable extends Throwable>, Observable>> {
private static final String TAG = "TokenAdvancedFragment";
/***
* 重试间隔时间
*/
private long mInterval;
public TryWhenTransaction(long interval) {
mInterval = interval;
}
private int retryCount = 0;
@Override
public Observable> call(final Observable extends Throwable> observable) {
return observable.flatMap(new Func1>>() {
@Override
public Observable> call(Throwable throwable) {
if (throwable instanceof UnknownHostException) {
//若没打开网络则停止重试
return Observable.error(throwable);
} else if (throwable instanceof NullPointerException)
Log.i(TAG, "call: Time:" + new Date(System.currentTimeMillis()) + " thread:" + Thread.currentThread().getName());
//重试三次
if (++retryCount < 3)
return Observable.timer(5, TimeUnit.SECONDS);
else
return Observable.error(new IllegalArgumentException("超过最大次数"));//超过最大次数终止
}
});
}
}
调用代码:
public void testMethodA() {
Observable.just(token).flatMap(new Func1>() {
@Override
public Observable call(Integer o) {
//若请求数据得不到响应 则返回一个Error 会激活retry
Log.i(TAG, "call: first token invalide is:" + o + " thread:" + Thread.currentThread().getName());
return Observable.error(new NullPointerException("请求数据失败"));
}
}).retryWhen(
new TryWhenTransaction(5))
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber() {
@Override
public void onCompleted() {
Log.i(TAG, "onCompleted: ");
}
@Override
public void onError(Throwable e) {
//这边可以根据错误类型来分别处理
if (e instanceof IllegalArgumentException) {
Log.e(TAG, "onError: ", e);
}
}
@Override
public void onNext(FakeThing fakeThing) {
}
});
}
token失效解决方案
用户长时间不访问app可能导致token的失效,这时如果继续访问app可能会导致用户重新登录降低体验,所以token失效自动重新获取可以增强用户体验。
上代码:
public void testMethodB() {
// 使用token进行网络请求
Observable.just(token).flatMap(new Func1>() {
@Override
public Observable call(Integer o) {
//若token是1000则为无效token
if (o == 1000) {
Log.i(TAG, "call: first token invalide is:" + o);
return Observable.error(new RuntimeException("TokenError"));
}
Log.i(TAG, "call: after retry request token:" + token + " original o is still:" + o);
return Observable.just(new FakeThing());
}
}).retryWhen(
new Func1, Observable>>() {
@Override
public Observable> call(final Observable extends Throwable> observable) {
return observable.flatMap(new Func1>() {
@Override
public Observable> call(Throwable throwable) {
if (throwable instanceof RuntimeException) {//如果是token错误就开启重试
//重新请求Token 获取到token为2000
token = 2000;
Log.i(TAG, "call: retry execute token changed is:" + token);
return observable.just(null).delay(1, TimeUnit.SECONDS);
}
return Observable.error(throwable);//其他错误就不开启重试
}
});
}
})
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber() {
@Override
public void onCompleted() {
Log.i(TAG, "onCompleted: ");
}
@Override
public void onError(Throwable e) {
try {
throw e;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void onNext(FakeThing fakeThing) {
}
});
}
上运行截图:
有木有发现新获取的Token本来应该是2000再重订阅的时候显示却是1000!其实,之所以造成这个的原因是因为对Obserable的工作机制没有理解透彻导致的。在创建一个Obserable的时候,参数的内容,固定的步骤就已经决定好了。 即使在过程中参数发生了改变,retry的时候,还是使用原来的值去请求的。可以使用HttpLoggingInterceptor验证这个。
那总是原始值怎么办?看下面
我们需要每次重试的时候,产生一个新的Obserable。这时候我们可以借助defer操作符来实现,每次调用都是一个新的Obserable。
把请求接口的Obserable用Obserable.defer包裹一下即可。
public void testMethodC() {
//使用defer重新包裹请求 可以在retry后使用新(重新获取的)token进行请求
// 如果不包裹则会使用第一次传进去的token进行请求
Observable.defer(new Func0>() {
@Override
public Observable call() {
//这里写请求 由于token过期或者为null传回错误码-1
return Observable.just(token);
}
}).flatMap(new Func1>() {
@Override
public Observable call(Integer o) {
if (o == 1000) {
Log.i(TAG, "call: first token invalide is:" + o);
return Observable.error(new RuntimeException("TokenError"));
}
Log.i(TAG, "call: after retry request token:" + token + " original o is still:" + o);
return Observable.just(new FakeThing());
}
}).retryWhen(
new Func1, Observable>>() {
@Override
public Observable> call(final Observable extends Throwable> observable) {
return observable.flatMap(new Func1>() {
@Override
public Observable> call(Throwable throwable) {
if (throwable instanceof RuntimeException) {
//重新请求Token 获取到token为2000
token = 2000;
Log.i(TAG, "call: retry execute token changed is:" + token);
return observable.just(null);
}
return Observable.error(throwable);
}
});
}
})
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber() {
@Override
public void onCompleted() {
Log.i(TAG, "onCompleted: ");
}
@Override
public void onError(Throwable e) {
try {
throw e;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void onNext(FakeThing fakeThing) {
}
});
}
至于次数限制就自己加仿造本片第一段代码。