web-dev-qa-db-fra.com

Comment arrêter et reprendre les ticks émetteurs Observable.interval

Cela émettra une tique toutes les 5 secondes. 

Observable.interval(5, TimeUnit.SECONDS, Schedulers.io())
            .subscribe(tick -> Log.d(TAG, "tick = "+tick));

Pour l'arrêter, vous pouvez utiliser 

Schedulers.shutdown();

Mais alors tous les planificateurs s'arrêtent et il n'est pas possible de reprendre le ticking plus tard. Comment puis-je arrêter et reprendre l'émission "gracieusement" `?

29
Jan Seevers

Voici une solution possible:

class TickHandler {

    private AtomicLong lastTick = new AtomicLong(0L);
    private Subscription subscription;

    void resume() {
        System.out.println("resumed");
        subscription = Observable.interval(5, TimeUnit.SECONDS, Schedulers.io())
                                 .map(tick -> lastTick.getAndIncrement())
                                 .subscribe(tick -> System.out.println("tick = " + tick));
    }

    void stop() {
        if (subscription != null && !subscription.isUnsubscribed()) {
            System.out.println("stopped");
            subscription.unsubscribe();
        }
    }
}
34
AndroidEx

Il y a quelque temps, je recherchais également des solutions de type "minuterie" RX, mais aucune d'entre elles ne répondait à mes attentes. Donc là vous pouvez trouver ma propre solution:

AtomicLong elapsedTime = new AtomicLong();
AtomicBoolean resumed = new AtomicBoolean();
AtomicBoolean stopped = new AtomicBoolean();

public Flowable<Long> startTimer() { //Create and starts timper
    resumed.set(true);
    stopped.set(false);
    return Flowable.interval(1, TimeUnit.SECONDS)
            .takeWhile(tick -> !stopped.get())
            .filter(tick -> resumed.get())
            .map(tick -> elapsedTime.addAndGet(1000));
}

public void pauseTimer() {
    resumed.set(false);
}

public void resumeTimer() {
    resumed.set(true);
}

public void stopTimer() {
    stopped.set(true);
}

public void addToTimer(int seconds) {
    elapsedTime.addAndGet(seconds * 1000);
}
10
Artur Szymański
val switch = new Java.util.concurrent.atomic.AtomicBoolean(true)
val tick = new Java.util.concurrent.atomic.AtomicLong(0L)

val suspendableObservable = 
  Observable.
    interval(5 seconds).
    takeWhile(_ => switch.get()).
    repeat.
    map(_ => tick.incrementAndGet())

Vous pouvez définir switch sur false pour suspendre le ticking et true pour le reprendre.

8
Sheng

Voici une autre façon de faire cela, je pense.
Lorsque vous vérifiez le code source, vous trouvez interval () using class OnSubscribeTimerPeriodically . Le code clé ci-dessous. 

@Override
public void call(final Subscriber<? super Long> child) {
    final Worker worker = scheduler.createWorker();
    child.add(worker);
    worker.schedulePeriodically(new Action0() {
        long counter;
        @Override
        public void call() {
            try {
                child.onNext(counter++);
            } catch (Throwable e) {
                try {
                    worker.unsubscribe();
                } finally {
                    Exceptions.throwOrReport(e, child);
                }
            }
        }

    }, initialDelay, period, unit);
}

Donc, vous verrez, si vous voulez canaliser la boucle, pourquoi ne pas lancer une nouvelle exception dans onNext () . Exemple de code ci-dessous.

Observable.interval(1000, TimeUnit.MILLISECONDS)
            .subscribe(new Action1<Long>() {
                @Override
                public void call(Long aLong) {
                    Log.i("abc", "onNext");
                    if (aLong == 5) throw new NullPointerException();
                }
            }, new Action1<Throwable>() {
                @Override
                public void call(Throwable throwable) {
                    Log.i("abc", "onError");
                }
            }, new Action0() {
                @Override
                public void call() {
                    Log.i("abc", "onCompleted");
                }
            });

Ensuite, vous verrez ceci:

08-08 11:10:46.008 28146-28181/net.bingyan.test I/abc: onNext
08-08 11:10:47.008 28146-28181/net.bingyan.test I/abc: onNext
08-08 11:10:48.008 28146-28181/net.bingyan.test I/abc: onNext
08-08 11:10:49.008 28146-28181/net.bingyan.test I/abc: onNext
08-08 11:10:50.008 28146-28181/net.bingyan.test I/abc: onNext
08-08 11:10:51.008 28146-28181/net.bingyan.test I/abc: onNext
08-08 11:10:51.018 28146-28181/net.bingyan.test I/abc: onError             
2
Chris Wong

Désolé, ceci est dans RxJS au lieu de RxJava, mais le concept sera le même. J'ai adapté cela de learn-rxjs.io et ici c'est sur codepen .

L'idée est que vous démarrez avec deux flux d'événements de clic, startClick$ et stopClick$. Chaque clic sur le flux stopClick$ est mappé sur un observable vide et les clics sur startClick$ sont mappés sur le flux interval$. Les deux flux résultants forment merge- d en un observable d'observables. En d'autres termes, un nouvel observable de l'un des deux types sera émis à partir de merge à chaque clic. L'observable résultant passera par switchMap, qui commence à écouter ce nouvel observable et cesse d'écouter ce qu'il écoutait auparavant. Switchmap commencera également à fusionner les valeurs de ce nouvel observable sur son flux existant.

Après le changement, scan ne voit jamais que la valeur "incrément" émise par interval$ et ne voit aucune valeur lorsque "stop" a été cliqué. 

Et jusqu'à ce que le premier clic se produise, startWith commencera à émettre des valeurs à partir de $interval, uniquement pour faire avancer les choses:

const start = 0;
const increment = 1;
const delay = 1000;
const stopButton = document.getElementById('stop');
const startButton = document.getElementById('start');
const startClick$ = Rx.Observable.fromEvent(startButton, 'click');
const stopClick$ = Rx.Observable.fromEvent(stopButton, 'click');
const interval$ = Rx.Observable.interval(delay).mapTo(increment);
const setCounter = newValue => document.getElementById("counter").innerHTML = newValue;
setCounter(start);

const timer$ = Rx.Observable

    // a "stop" click will emit an empty observable,
    // and a "start" click will emit the interval$ observable.  
    // These two streams are merged into one observable.
    .merge(stopClick$.mapTo(Rx.Observable.empty()), 
           startClick$.mapTo(interval$))

    // until the first click occurs, merge will emit nothing, so 
    // use the interval$ to start the counter in the meantime
    .startWith(interval$)

    // whenever a new observable starts, stop listening to the previous
    // one and start emitting values from the new one
    .switchMap(val => val)

    // add the increment emitted by the interval$ stream to the accumulator
    .scan((acc, curr) => curr + acc, start)

    // start the observable and send results to the DIV
    .subscribe((x) => setCounter(x));

Et voici le HTML

<html>
<body>
  <div id="counter"></div>
  <button id="start">
    Start
  </button>
  <button id="stop">
    Stop
  </button>
</body>
</html>
1
mikebridge

Vous pouvez utiliser takeWhile et boucle jusqu'à ce que les conditions soient vraies

Observable.interval(1, TimeUnit.SECONDS)
        .takeWhile {
            Log.i(TAG, " time " + it)
            it != 30L
        }
        .subscribe(object : Observer<Long> {
            override fun onComplete() {
                Log.i(TAG, "onComplete " + format.format(System.currentTimeMillis()))
            }

            override fun onSubscribe(d: Disposable) {
                Log.i(TAG, "onSubscribe " + format.format(System.currentTimeMillis()))
            }

            override fun onNext(t: Long) {
                Log.i(TAG, "onNext " + format.format(System.currentTimeMillis()))
            }

            override fun onError(e: Throwable) {
                Log.i(TAG, "onError")
                e.printStackTrace()
            }

        });
0
Alier