Und Sie können es auf eine Weise verwenden, die Ihrem aktuellen Code sehr ähnlich ist:
tickerFlow(5.seconds)
.map { LocalDateTime.now() }
.distinctUntilChanged { old, new ->
old.minute == new.minute
}
.onEach {
setDateTime(it)
}
.launchIn(viewModelScope) // or lifecycleScope or other
Hinweis: Bei dem hier geschriebenen Code wird die Zeit, die zum Verarbeiten von Elementen benötigt wird, nicht berücksichtigt tickerFlowdaher ist die Verzögerung möglicherweise nicht regelmäßig (es ist eine Verzögerung zwischen Elementverarbeitung). Wenn Sie möchten, dass der Ticker unabhängig von der Verarbeitung jedes Elements tickt, können Sie a verwenden Puffer oder einen eigenen Thread (zB via flowOn).
Ursprüngliche Antwort
Ich glaube, es ist noch experimentell, aber Sie können a verwenden TickerChannel um Werte alle X Millis zu erzeugen:
val tickerChannel = ticker(delayMillis = 60_000, initialDelayMillis = 0)
repeat(10) {
tickerChannel.receive()
val currentTime = LocalDateTime.now()
println(currentTime)
}
Wenn Sie Ihre Arbeit fortsetzen müssen, während Ihr “Abonnement” für jeden “Tick” etwas tut, können Sie das tun launch eine Hintergrund-Coroutine, die von diesem Kanal liest und das tut, was Sie wollen:
val tickerChannel = ticker(delayMillis = 60_000, initialDelayMillis = 0)
launch {
for (event in tickerChannel) {
// the 'event' variable is of type Unit, so we don't really care about it
val currentTime = LocalDateTime.now()
println(currentTime)
}
}
delay(1000)
// when you're done with the ticker and don't want more events
tickerChannel.cancel()
Wenn Sie innerhalb der Schleife anhalten möchten, können Sie einfach ausbrechen und dann den Kanal abbrechen:
val ticker = ticker(500, 0)
var count = 0
for (event in ticker) {
count++
if (count == 4) {
break
} else {
println(count)
}
}
ticker.cancel()
Gibt es eine Möglichkeit, einen Ticker zu “unstornieren”? Wie kann ich den Ticker pausieren/aufheben?
– Leben
2. Dezember 2019 um 18:45 Uhr
@Lifes Sie müssen wahrscheinlich eine Art “aktive” Zustandsvariable haben, um zu überprüfen, wann Sie ein Tick erhalten. Sie können es auf “false” setzen, wenn Sie “pausieren” möchten, und wieder auf “true”, wenn Sie “fortsetzen” möchten.
– Joffrey
2. Dezember 2019 um 23:12 Uhr
Danke für die schnelle Antwort. Angesichts meines Anwendungsfalls möchte ich nicht, dass es weiter tickt, also werde ich es nach Bedarf abbrechen und neu erstellen.
– Leben
3. Dezember 2019 um 2:43 Uhr
Ticker ist in Version “1.3.2” als “ObsoleteCoroutinesApi” gekennzeichnet, was bedeutet: “Markiert Deklarationen, die obsolet in Coroutines API, was bedeutet, dass das Design der entsprechenden Deklarationen schwerwiegende bekannte Fehler aufweist und sie in Zukunft neu gestaltet werden. Grob gesagt werden diese Deklarationen in Zukunft veraltet sein, aber es gibt noch keinen Ersatz für sie, sodass sie nicht sofort veraltet sein können.”
– aLx
13. März 2020 um 14:18 Uhr
Ein sehr pragmatischer Ansatz mit Kotlin Flows könnte sein:
// Create the timer flow
val timer = (0..Int.MAX_VALUE)
.asSequence()
.asFlow()
.onEach { delay(1_000) } // specify delay
// Consume it
timer.collect {
println("bling: ${it}")
}
Wie werde ich benachrichtigt, wenn es endet?
– Skizo-ozᴉʞS
18. November 2021 um 9:50 Uhr
Achten Sie darauf, den Flow zu importieren mit: import kotlinx.coroutines.flow.collect
– Johannes
8. März um 16:53 Uhr
Warum verwenden wir hier die Funktion asSequence()?
– Hassa
29. Juni um 7:07 Uhr
@Hassa soll die Folge von Ints sein, die faul erstellt werden. Andernfalls würden alle Ints von 0 .. Int.MAX_VALUE sofort in den Speicher geladen, was Sie wahrscheinlich nicht möchten.
– Steffen Funke
29. Juni um 10:05 Uhr
Raffael C
eine weitere mögliche Lösung als wiederverwendbare Kotlin-Erweiterung von CoroutineScope
fun CoroutineScope.launchPeriodicAsync(
repeatMillis: Long,
action: () -> Unit
) = this.async {
if (repeatMillis > 0) {
while (isActive) {
action()
delay(repeatMillis)
}
} else {
action()
}
}
und dann Verwendung als:
var job = CoroutineScope(Dispatchers.IO).launchPeriodicAsync(100) {
//...
}
und um es dann zu unterbrechen:
job.cancel()
noch eine Anmerkung: wir betrachten das hier action ist nicht blockierend und nimmt keine Zeit in Anspruch.
Dank dem spielt es hier keine Rolle delay() Anruf, aber im Allgemeinen sollten wir vermeiden while (true) in Koroutinen bevorzugen while(isActive) Stornierung richtig zu unterstützen.
– Joffrey
1. Dezember 2020 um 17:08 Uhr
@Joffrey, dies ist nur ein Beispiel, Sie können es gerne zum Besseren ändern.
– Raffael C
3. Dezember 2020 um 5:53 Uhr
Was ist der Grund für die Verwendung async() Anstatt von launch() ?
– Phileo99
5. Oktober 2021 um 23:17 Uhr
@Phileo99 Ich denke, Sie könnten es so oder so tun, aber wenn Sie Async verwenden, wird ein Deferred zurückgegeben, das Ihnen ein paar mehr Optionen als ein Start {} bietet, z. B. await(). Ich bin mir nicht sicher, ob das in diesem Fall so nützlich wäre, aber ich glaube nicht, dass es viel Overhead hinzufügt. Verzögert erweitert Job, sodass alles, was der Start asynchron ausführen kann, auch ausgeführt werden kann.
– AlexW.HB
1. Februar um 19:25 Uhr
Denken Sie daran, dass das Intervall zwischen aufeinander folgenden action() Anrufe ist nicht definiert repeatMillis Zeit, aber repeatMillis + die Zeit, dass action() dauert zur Ausführung. Diese Lösung ist also in Ordnung, solange action() dauert nicht zu lange. Durch die Verwendung von Flüssen mit buffer(), conflate()oder flowOnkönnen wir Intervalle erhalten, die ungefähr konstant sind.
– Lukas Lechner
7. Juli um 8:24 Uhr
Sie können einen Countdown-Timer wie diesen erstellen
GlobalScope.launch(Dispatchers.Main) {
val totalSeconds = TimeUnit.MINUTES.toSeconds(2)
val tickSeconds = 1
for (second in totalSeconds downTo tickSeconds) {
val time = String.format("%02d:%02d",
TimeUnit.SECONDS.toMinutes(second),
second - TimeUnit.MINUTES.toSeconds(TimeUnit.SECONDS.toMinutes(second))
)
timerTextView?.text = time
delay(1000)
}
timerTextView?.text = "Done!"
}
Benjamin Ledet
Bearbeiten: Joffrey hat seine Lösung mit einem besseren Ansatz bearbeitet.
Alt :
Joffreys Lösung funktioniert für mich, aber ich bin auf ein Problem mit der for-Schleife gestoßen.
Ich muss meinen Ticker in der for-Schleife wie folgt löschen:
val ticker = ticker(500, 0)
for (event in ticker) {
if (...) {
ticker.cancel()
} else {
...
}
}
}
Aber ticker.cancel() hat eine CancellationException ausgelöst, weil die for-Schleife danach weiterging.
Ich musste eine While-Schleife verwenden, um zu überprüfen, ob der Kanal nicht geschlossen wurde, um diese Ausnahme nicht zu erhalten.
val ticker = ticker(500, 0)
while (!ticker.isClosedForReceive && ticker.iterator().hasNext()) {
if (...) {
ticker.cancel()
} else {
...
}
}
}
Warum gehst du nicht einfach break aus der Schleife, wenn Sie wissen, dass Sie wollen, dass es aufhört? Sie können den Ticker dann außerhalb der Schleife abbrechen, das hat bei mir gut funktioniert. Außerdem erstellen Sie mit diesem Ansatz bei jeder Schleifenumdrehung einen neuen Iterator. Dies ist möglicherweise nicht das, was Sie tun möchten.
– Joffrey
3. Dezember 2019 um 16:55 Uhr
Manchmal denken wir nicht an die einfachsten Lösungen … Sie haben vollkommen recht, danke!
– Benjamin Ledet
4. Dezember 2019 um 8:41 Uhr
Kein Problem 🙂 Davon abgesehen, hatte ich nicht erwartet cancel() zu scheitern, wenn es innerhalb der Schleife aufgerufen wird, also haben Sie mir etwas dazu beigebracht. Um dem auf den Grund zu gehen, muss ich weiter recherchieren.
– Joffrey
4. Dezember 2019 um 10:38 Uhr
Nun, mit der Version 1.2.2 der Coroutinen ist es nicht fehlgeschlagen! Aber ich habe auf die Version 1.3.2 aktualisiert und jetzt funktioniert es. Vielleicht sollte es mit 1.2.2 fehlschlagen und sie haben es behoben oder es ist ein Fehler, der eingeführt wurde …
– Benjamin Ledet
4. Dezember 2019 um 11:27 Uhr
Dario Pellegrini
Hier ist eine mögliche Lösung mit Kotlin Flow
fun tickFlow(millis: Long) = callbackFlow<Int> {
val timer = Timer()
var time = 0
timer.scheduleAtFixedRate(
object : TimerTask() {
override fun run() {
try { offer(time) } catch (e: Exception) {}
time += 1
}
},
0,
millis)
awaitClose {
timer.cancel()
}
}
Warum gehst du nicht einfach break aus der Schleife, wenn Sie wissen, dass Sie wollen, dass es aufhört? Sie können den Ticker dann außerhalb der Schleife abbrechen, das hat bei mir gut funktioniert. Außerdem erstellen Sie mit diesem Ansatz bei jeder Schleifenumdrehung einen neuen Iterator. Dies ist möglicherweise nicht das, was Sie tun möchten.
– Joffrey
3. Dezember 2019 um 16:55 Uhr
Manchmal denken wir nicht an die einfachsten Lösungen … Sie haben vollkommen recht, danke!
– Benjamin Ledet
4. Dezember 2019 um 8:41 Uhr
Kein Problem 🙂 Davon abgesehen, hatte ich nicht erwartet cancel() zu scheitern, wenn es innerhalb der Schleife aufgerufen wird, also haben Sie mir etwas dazu beigebracht. Um dem auf den Grund zu gehen, muss ich weiter recherchieren.
– Joffrey
4. Dezember 2019 um 10:38 Uhr
Nun, mit der Version 1.2.2 der Coroutinen ist es nicht fehlgeschlagen! Aber ich habe auf die Version 1.3.2 aktualisiert und jetzt funktioniert es. Vielleicht sollte es mit 1.2.2 fehlschlagen und sie haben es behoben oder es ist ein Fehler, der eingeführt wurde …
– Benjamin Ledet
4. Dezember 2019 um 11:27 Uhr
Timer mit START-, PAUSE- und STOP-Funktion.
Verwendungszweck:
val timer = Timer(millisInFuture = 10_000L, runAtStart = false)
timer.start()
Timer Klasse:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
enum class PlayerMode {
PLAYING,
PAUSED,
STOPPED
}
class Timer(
val millisInFuture: Long,
val countDownInterval: Long = 1000L,
runAtStart: Boolean = false,
val onFinish: (() -> Unit)? = null,
val onTick: ((Long) -> Unit)? = null
) {
private var job: Job = Job()
private val _tick = MutableStateFlow(0L)
val tick = _tick.asStateFlow()
private val _playerMode = MutableStateFlow(PlayerMode.STOPPED)
val playerMode = _playerMode.asStateFlow()
private val scope = CoroutineScope(Dispatchers.Default)
init {
if (runAtStart) start()
}
fun start() {
if (_tick.value == 0L) _tick.value = millisInFuture
job.cancel()
job = scope.launch(Dispatchers.IO) {
_playerMode.value = PlayerMode.PLAYING
while (isActive) {
if (_tick.value <= 0) {
job.cancel()
onFinish?.invoke()
_playerMode.value = PlayerMode.STOPPED
[email protected]
}
delay(timeMillis = countDownInterval)
_tick.value -= countDownInterval
onTick?.invoke([email protected]._tick.value)
}
}
}
fun pause() {
job.cancel()
_playerMode.value = PlayerMode.PAUSED
}
fun stop() {
job.cancel()
_tick.value = 0
_playerMode.value = PlayerMode.STOPPED
}
}
Ich denke, Sie können Tickerkanäle verwenden: kotlinlang.org/docs/reference/coroutines/…
– Marsran
22. Februar 2019 um 12:51 Uhr
@marstran Nicht mehr, sie sind jetzt veraltet.
– Farid
23. Dezember 2021 um 17:36 Uhr