Je voudrais savoir comment ignorer les exceptions et continuer le flux infini (dans mon cas, le flux d'emplacements)?
Je récupère la position actuelle de l'utilisateur (en utilisant Android-ReactiveLocation ), puis je les envoie à mon API (en utilisant Retrofit ).
Dans mon cas, lorsqu'une exception se produit pendant un appel réseau (par exemple timeout), la méthode onError
est invoquée et le flux s'arrête. Comment l'éviter?
Activité:
private RestService mRestService;
private Subscription mSubscription;
private LocationRequest mLocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(100);
...
private void start() {
mRestService = ...;
ReactiveLocationProvider reactiveLocationProvider = new ReactiveLocationProvider(this);
mSubscription = reactiveLocationProvider.getUpdatedLocation(mLocationRequest)
.buffer(50)
.flatMap(locations -> mRestService.postLocations(locations)) // can throw exception
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
}
RestService:
public interface RestService {
@POST("/.../")
Observable<Response> postLocations(@Body List<Location> locations);
}
mRestService.postLocations(locations)
émettez un élément, puis terminez. Si une erreur se produit, il émet alors l'erreur, ce qui termine le flux.
Lorsque vous appelez cette méthode dans un flatMap
, l'erreur continue dans votre flux "principal", puis votre flux s'arrête.
Ce que vous pouvez faire est de transformer votre erreur en un autre élément (comme décrit ici: https://stackoverflow.com/a/28971140/47669 ), mais pas sur votre flux principal (comme je vous présume déjà essayé) mais sur la mRestService.postLocations(locations)
.
De cette façon, cet appel émettra une erreur, qui sera transformée en un élément/un autre observable puis complétée. (sans appeler onError
).
Dans une vue grand public, mRestService.postLocations(locations)
émettra un élément, puis se terminera, comme si tout réussissait.
mSubscription = reactiveLocationProvider.getUpdatedLocation(mLocationRequest)
.buffer(50)
.flatMap(locations -> mRestService.postLocations(locations).onErrorReturn((e) -> Collections.emptyList()) // can't throw exception
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
Vous pouvez utiliser l'un des opérateurs de gestion des erreurs .
onErrorResumeNext( )
- demande à un observable d'émettre une séquence d'éléments s'il rencontre une erreuronErrorReturn( )
- demande à un observable d'émettre un élément particulier lorsqu'il rencontre une erreuronExceptionResumeNext( )
- indique à un observable de continuer à émettre des éléments après avoir rencontré une exception (mais pas une autre variété de jetables)retry( )
- si une source Observable émet une erreur, réabonnez-vous en espérant qu'elle se terminera sans erreurretryWhen( )
- si un observable source émet une erreur, transmettez cette erreur à un autre observable pour déterminer s'il doit se réinscrire à la sourceSurtout retry
et onExceptionResumeNext
semblent prometteurs dans votre cas.
Si vous voulez simplement ignorer l'erreur à l'intérieur de flatMap
sans renvoyer d'élément faites ceci:
flatMap(item ->
restService.getSomething(item).onErrorResumeNext(Observable.empty())
);
Le simple fait de coller les informations de lien de la réponse de @ MikeN au cas où il serait perdu:
import rx.Observable.Operator;
import rx.functions.Action1;
public final class OperatorSuppressError<T> implements Operator<T, T> {
final Action1<Throwable> onError;
public OperatorSuppressError(Action1<Throwable> onError) {
this.onError = onError;
}
@Override
public Subscriber<? super T> call(final Subscriber<? super T> t1) {
return new Subscriber<T>(t1) {
@Override
public void onNext(T t) {
t1.onNext(t);
}
@Override
public void onError(Throwable e) {
onError.call(e);
}
@Override
public void onCompleted() {
t1.onCompleted();
}
};
}
}
et l'utiliser près de la source observable car d'autres opérateurs peuvent se désinscrire avec impatience avant cela.
Observerable.create(connectToUnboundedStream()).lift(new OperatorSuppressError(log()).doOnNext(someStuff()).subscribe();
Notez, cependant, que cela supprime la livraison d'erreur à partir de la source. Si un onNext de la chaîne après avoir levé une exception, il est probable que la source sera désabonnée.
Essayez d'appeler le service de repos dans un appel Observable.defer. De cette façon, pour chaque appel, vous aurez la possibilité d'utiliser son propre "onErrorResumeNext" et les erreurs n'entraîneront pas la fin de votre flux principal.
reactiveLocationProvider.getUpdatedLocation(mLocationRequest)
.buffer(50)
.flatMap(locations ->
Observable.defer(() -> mRestService.postLocations(locations))
.onErrorResumeNext(<SOME_DEFAULT_TO_REACT_TO>)
)
........
Cette solution est à l'origine de ce fil -> RxJava observable et abonné pour ignorer l'exception? , mais je pense que cela fonctionnera aussi dans votre cas.
Ajoutez ma solution à ce problème:
privider
.compose(ignoreErrorsTransformer)
.subscribe()
private final Observable.Transformer<ResultType, ResultType> ignoreErrorsTransformer =
new Observable.Transformer<ResultType, ResultType>() {
@Override
public Observable<ResultType> call(Observable<ResultType> resultTypeObservable) {
return resultTypeObservable
.materialize()
.filter(new Func1<Notification<ResultType>, Boolean>() {
@Override
public Boolean call(Notification<ResultType> resultTypeNotification) {
return !resultTypeNotification.isOnError();
}
})
.dematerialize();
}
};
Une légère modification de la solution (@MikeN) pour permettre aux flux finis de se terminer:
import rx.Observable.Operator;
import rx.functions.Action1;
public final class OperatorSuppressError<T> implements Operator<T, T> {
final Action1<Throwable> onError;
public OperatorSuppressError(Action1<Throwable> onError) {
this.onError = onError;
}
@Override
public Subscriber<? super T> call(final Subscriber<? super T> t1) {
return new Subscriber<T>(t1) {
@Override
public void onNext(T t) {
t1.onNext(t);
}
@Override
public void onError(Throwable e) {
onError.call(e);
//this will allow finite streams to complete
t1.onCompleted();
}
@Override
public void onCompleted() {
t1.onCompleted();
}
};
}
}