我在Android应用中使用rxjava来异步处理网络请求。现在我想在一段时间过后才重试失败的网络请求。
有没有办法在Observable上使用retry()但只能在一定延迟后重试?
有没有办法让Observable知道当前正在重试(而不是第一次尝试)?
我看过debounce()/ throttleWithTimeout(),但他们似乎做了不同的事情。
修改
我认为我找到了一种方法,但我会对确认这是正确的方法或其他更好的方法感兴趣。
我正在做的是:在我的Observable.OnSubscribe的call()方法中,在我调用Subscribers onError()方法之前,我只是让Thread睡眠所需的时间。因此,要每1000毫秒重试一次,我会这样做:
@Override
public void call(Subscriber<? super List<ProductNode>> subscriber) {
try {
Log.d(TAG, "trying to load all products with pid: " + pid);
subscriber.onNext(productClient.getProductNodesForParentId(pid));
subscriber.onCompleted();
} catch (Exception e) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e.printStackTrace();
}
subscriber.onError(e);
}
}
由于此方法无论如何都在IO线程上运行,因此它不会阻止UI。我能看到的唯一问题是,即使是第一个错误也会报告延迟,所以即使没有重试(),也会出现延迟。如果在错误之后没有应用延迟,而是在重试之前(但显然不是在第一次尝试之前),我会更喜欢它。
答案 0 :(得分:160)
您可以使用retryWhen()
运算符将重试逻辑添加到任何Observable。
以下类包含重试逻辑:
public class RetryWithDelay implements Function<Observable<? extends Throwable>, Observable<?>> {
private final int maxRetries;
private final int retryDelayMillis;
private int retryCount;
public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
}
@Override
public Observable<?> apply(final Observable<? extends Throwable> attempts) {
return attempts
.flatMap(new Function<Throwable, Observable<?>>() {
@Override
public Observable<?> apply(final Throwable throwable) {
if (++retryCount < maxRetries) {
// When this Observable calls onNext, the original
// Observable will be retried (i.e. re-subscribed).
return Observable.timer(retryDelayMillis,
TimeUnit.MILLISECONDS);
}
// Max retries hit. Just pass the error along.
return Observable.error(throwable);
}
});
}
}
public class RetryWithDelay implements
Func1<Observable<? extends Throwable>, Observable<?>> {
private final int maxRetries;
private final int retryDelayMillis;
private int retryCount;
public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
}
@Override
public Observable<?> call(Observable<? extends Throwable> attempts) {
return attempts
.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(Throwable throwable) {
if (++retryCount < maxRetries) {
// When this Observable calls onNext, the original
// Observable will be retried (i.e. re-subscribed).
return Observable.timer(retryDelayMillis,
TimeUnit.MILLISECONDS);
}
// Max retries hit. Just pass the error along.
return Observable.error(throwable);
}
});
}
}
用法:
// Add retry logic to existing observable.
// Retry max of 3 times with a delay of 2 seconds.
observable
.retryWhen(new RetryWithDelay(3, 2000));
答案 1 :(得分:13)
受Paul's answer的启发,如果您不关心Abhijit Sarkar所述的retryWhen
问题,则使用rxJava2 untditionnaly延迟重新订阅的最简单方法是:
source.retryWhen(throwables -> throwables.delay(1, TimeUnit.SECONDS))
您可能希望在retryWhen and repeatWhen上看到更多示例和解释。
答案 2 :(得分:9)
这是基于我看到的Ben Christensen的片段RetryWhen Example和RetryWhenTestsConditional的解决方案(我必须将n.getThrowable()
更改为n
工作)。我使用evant/gradle-retrolambda使lambda符号在Android上运行,但你不必使用lambda(尽管强烈推荐)。对于延迟,我实现了指数后退,但你可以插入你想要的任何退避逻辑。为了完整性,我添加了subscribeOn
和observeOn
运算符。我使用ReactiveX/RxAndroid作为AndroidSchedulers.mainThread()
。
int ATTEMPT_COUNT = 10;
public class Tuple<X, Y> {
public final X x;
public final Y y;
public Tuple(X x, Y y) {
this.x = x;
this.y = y;
}
}
observable
.subscribeOn(Schedulers.io())
.retryWhen(
attempts -> {
return attempts.zipWith(Observable.range(1, ATTEMPT_COUNT + 1), (n, i) -> new Tuple<Throwable, Integer>(n, i))
.flatMap(
ni -> {
if (ni.y > ATTEMPT_COUNT)
return Observable.error(ni.x);
return Observable.timer((long) Math.pow(2, ni.y), TimeUnit.SECONDS);
});
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
答案 3 :(得分:8)
而不是使用MyRequestObservable.retry我使用了一个包装函数retryObservable(MyRequestObservable,retrycount,seconds),它返回一个新的Observable来处理延迟的间接,所以我可以做
retryObservable(restApi.getObservableStuff(), 3, 30)
.subscribe(new Action1<BonusIndividualList>(){
@Override
public void call(BonusIndividualList arg0)
{
//success!
}
},
new Action1<Throwable>(){
@Override
public void call(Throwable arg0) {
// failed after the 3 retries !
}});
// wrapper code
private static <T> Observable<T> retryObservable(
final Observable<T> requestObservable, final int nbRetry,
final long seconds) {
return Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(final Subscriber<? super T> subscriber) {
requestObservable.subscribe(new Action1<T>() {
@Override
public void call(T arg0) {
subscriber.onNext(arg0);
subscriber.onCompleted();
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable error) {
if (nbRetry > 0) {
Observable.just(requestObservable)
.delay(seconds, TimeUnit.SECONDS)
.observeOn(mainThread())
.subscribe(new Action1<Observable<T>>(){
@Override
public void call(Observable<T> observable){
retryObservable(observable,
nbRetry - 1, seconds)
.subscribe(subscriber);
}
});
} else {
// still fail after retries
subscriber.onError(error);
}
}
});
}
});
}
答案 4 :(得分:5)
此示例适用于jxjava 2.2.2:
立即重试:
Single.just(somePaylodData)
.map(data -> someConnection.send(data))
.retry(5)
.doOnSuccess(status -> log.info("Yay! {}", status);
重试:
Single.just(somePaylodData)
.map(data -> someConnection.send(data))
.retryWhen((Flowable<Throwable> f) -> f.take(5).delay(300, TimeUnit.MILLISECONDS))
.doOnSuccess(status -> log.info("Yay! {}", status)
.doOnError((Throwable error)
-> log.error("I tried five times with a 300ms break"
+ " delay in between. But it was in vain."));
如果someConnection.send()失败,我们的源单失败。 当发生这种情况时,retryWhen内部的可观察到的失败将发出错误。 我们将发射延迟300毫秒,然后将其发送回以发出重试信号。 take(5)保证我们的可观察信号在收到五个错误后将终止。 retryWhen看到终止并且在第五次失败后不重试。
答案 5 :(得分:3)
答案 6 :(得分:2)
retryWhen
是一个复杂的,甚至是错误的运营商。这里的官方文档和至少一个答案使用range
运算符,如果没有重试,它将失败。请与ReactiveX成员David Karnok一起查看我的discussion。
我改进了kjones&#39;通过将flatMap
更改为concatMap
并添加RetryDelayStrategy
课程来回答问题。 flatMap
在concatMap
执行时RetryDelayStrategy
不会保留排放顺序,这对于退避延迟非常重要。 setRandomJokes
,如名称所示,让用户从生成重试延迟的各种模式中进行选择,包括退避。
我的GitHub上提供了以下测试用例的代码:
请参阅string tSql = @" SELECT [Award_ID],[Award_Name],[Award_Info],[Award_Pic],[Award_Num],[Award_MoneyCost],[Award_MoneyGet],[Award_Type],[Award_AddDate],[Award_Hot],[Award_OnLineTime],[AwardProP],[PrizeSlidePic],[PrizeDetailPic],[PrizeBigSlidePic],[IsTop],[ClassID] FROM dbo.Web_Award WHERE ClassID={0} ";
DataTable data = DbSession.Default.FromSql(string.Format(tSql, Request.QueryString["classid"])).ToDataTable();
方法。
答案 7 :(得分:2)
与 kjones 相同的答案,但已更新至最新版本 对于 RxJava 2.x 版本:(&#39; io.reactivex.rxjava2:rxjava:2.1.3&#39;)
public class RetryWithDelay implements Function<Flowable<Throwable>, Publisher<?>> {
private final int maxRetries;
private final long retryDelayMillis;
private int retryCount;
public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
}
@Override
public Publisher<?> apply(Flowable<Throwable> throwableFlowable) throws Exception {
return throwableFlowable.flatMap(new Function<Throwable, Publisher<?>>() {
@Override
public Publisher<?> apply(Throwable throwable) throws Exception {
if (++retryCount < maxRetries) {
// When this Observable calls onNext, the original
// Observable will be retried (i.e. re-subscribed).
return Flowable.timer(retryDelayMillis,
TimeUnit.MILLISECONDS);
}
// Max retries hit. Just pass the error along.
return Flowable.error(throwable);
}
});
}
}
用法:
//将重试逻辑添加到现有的observable。 //重试最多3次,延迟2秒。
observable
.retryWhen(new RetryWithDelay(3, 2000));
答案 8 :(得分:1)
您可以在retryWhen Operator
中返回的Observable中添加延迟 /**
* Here we can see how onErrorResumeNext works and emit an item in case that an error occur in the pipeline and an exception is propagated
*/
@Test
public void observableOnErrorResumeNext() {
Subscription subscription = Observable.just(null)
.map(Object::toString)
.doOnError(failure -> System.out.println("Error:" + failure.getCause()))
.retryWhen(errors -> errors.doOnNext(o -> count++)
.flatMap(t -> count > 3 ? Observable.error(t) : Observable.just(null).delay(100, TimeUnit.MILLISECONDS)),
Schedulers.newThread())
.onErrorResumeNext(t -> {
System.out.println("Error after all retries:" + t.getCause());
return Observable.just("I save the world for extinction!");
})
.subscribe(s -> System.out.println(s));
new TestSubscriber((Observer) subscription).awaitTerminalEvent(500, TimeUnit.MILLISECONDS);
}
您可以在此处查看更多示例。 https://github.com/politrons/reactive
答案 9 :(得分:1)
基于kjones的答案是Kotlin版本的RxJava 2.x重试,但有延迟作为扩展。替换 as.numeric(v1)
[1] 27 15 47 44 62 22
为options(scipen = 999)
创建相同的扩展名。
Observable
然后将其用于可观察的Flowable
答案 10 :(得分:0)
对于Kotlin&amp; RxJava1版本
class RetryWithDelay(private val MAX_RETRIES: Int, private val DELAY_DURATION_IN_SECONDS: Long)
: Function1<Observable<out Throwable>, Observable<*>> {
private val START_RETRY: Int = 1
override fun invoke(observable: Observable<out Throwable>): Observable<*> {
return observable.delay(DELAY_DURATION_IN_SECONDS, TimeUnit.SECONDS)
.zipWith(Observable.range(START_RETRY, MAX_RETRIES),
object : Function2<Throwable, Int, Int> {
override fun invoke(throwable: Throwable, attempt: Int): Int {
return attempt
}
})
}
}
答案 11 :(得分:0)
(Kotlin)我通过指数退避和应用的Observable.range()的防御发射改进了一些代码:
fun testOnRetryWithDelayExponentialBackoff() {
val interval = 1
val maxCount = 3
val ai = AtomicInteger(1);
val source = Observable.create<Unit> { emitter ->
val attempt = ai.getAndIncrement()
println("Subscribe ${attempt}")
if (attempt >= maxCount) {
emitter.onNext(Unit)
emitter.onComplete()
}
emitter.onError(RuntimeException("Test $attempt"))
}
// Below implementation of "retryWhen" function, remove all "println()" for real code.
val sourceWithRetry: Observable<Unit> = source.retryWhen { throwableRx ->
throwableRx.doOnNext({ println("Error: $it") })
.zipWith(Observable.range(1, maxCount)
.concatMap { Observable.just(it).delay(0, TimeUnit.MILLISECONDS) },
BiFunction { t1: Throwable, t2: Int -> t1 to t2 }
)
.flatMap { pair ->
if (pair.second >= maxCount) {
Observable.error(pair.first)
} else {
val delay = interval * 2F.pow(pair.second)
println("retry delay: $delay")
Observable.timer(delay.toLong(), TimeUnit.SECONDS)
}
}
}
//Code to print the result in terminal.
sourceWithRetry
.doOnComplete { println("Complete") }
.doOnError({ println("Final Error: $it") })
.blockingForEach { println("$it") }
}
答案 12 :(得分:0)
如果需要打印重试计数, 您可以使用Rxjava的Wiki页面https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators
中提供的示例observable.retryWhen(errors ->
// Count and increment the number of errors.
errors.map(error -> 1).scan((i, j) -> i + j)
.doOnNext(errorCount -> System.out.println(" -> query errors #: " + errorCount))
// Limit the maximum number of retries.
.takeWhile(errorCount -> errorCount < retryCounts)
// Signal resubscribe event after some delay.
.flatMapSingle(errorCount -> Single.timer(errorCount, TimeUnit.SECONDS));
答案 13 :(得分:0)
使用 retryWhen
/**
* Retry Handler Support
* @param errors
* @param predicate filter error
* @param maxTry
* @param periodStrategy
* @param timeUnit
* @return
*/
private Flowable<?> retrySupport(Flowable<Throwable> errors, Predicate<? super Throwable> predicate , Integer maxTry , Function<Long, Long> periodStrategy , TimeUnit timeUnit )
{
LongAdder errorCount = new LongAdder();
return errors
.doOnNext(e -> {
errorCount.increment();
long currentCount = errorCount.longValue();
boolean tryContinue = predicate.test(e) && currentCount < maxTry;
Logger.i("No. of errors: %d , %s", currentCount,
tryContinue ? String.format("please wait %d %s.", periodStrategy.apply(currentCount), timeUnit.name()) : "skip and throw");
if(!tryContinue)
throw e;
} )
.flatMapSingle(e -> Single.timer( periodStrategy.apply(errorCount.longValue()), timeUnit));
}
样品
private Single<DeviceInfo> getErrorToken( String device)
{
return Single.error( new IOException( "network is disconnect!" ) );
}
//only retry when emit IOExpcetion
//delay 1s,2s,4s,8s,16s
this.getErrorToken( this.deviceCode )
.retryWhen( error -> retrySupport( error,
e-> e instanceof IOException,
5 ,
count-> (long)Math.pow(2,count-1),TimeUnit.SECONDS ) )
.subscribe( deviceInfo1 -> Logger.i( "----Get Device Info---" ) ,
e -> Logger.e( e, "On Error" ) ,
() -> Logger.i("<<<<<no more>>>>>"));
答案 14 :(得分:-1)
简单地这样做:
Observable.just("")
.delay(2, TimeUnit.SECONDS) //delay
.flatMap(new Func1<String, Observable<File>>() {
@Override
public Observable<File> call(String s) {
L.from(TAG).d("postAvatar=");
File file = PhotoPickUtil.getTempFile();
if (file.length() <= 0) {
throw new NullPointerException();
}
return Observable.just(file);
}
})
.retry(6)
.subscribe(new Action1<File>() {
@Override
public void call(File file) {
postAvatar(file);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
}
});