![](/img/trans.png)
[英]How to implement NIO Socket (client) using Kotlin coroutines in Java Code?
[英]How to implement timer with Kotlin coroutines
我想使用 Kotlin 協程實現計時器,類似於用 RxJava 實現的東西:
Flowable.interval(0, 5, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.map { LocalDateTime.now() }
.distinctUntilChanged { old, new ->
old.minute == new.minute
}
.subscribe {
setDateTime(it)
}
它將每隔一分鍾發出 LocalDateTime 。
編輯:請注意,原始答案中建議的 API 現在標記為@ObsoleteCoroutineApi
:
Ticker 通道目前未與結構化並發集成,它們的 api 將在未來發生變化。
您現在可以使用Flow
API 創建您自己的代碼流:
import kotlin.time.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun tickerFlow(period: Duration, initialDelay: Duration = Duration.ZERO) = flow {
delay(initialDelay)
while (true) {
emit(Unit)
delay(period)
}
}
您可以以與當前代碼非常相似的方式使用它:
tickerFlow(Duration.seconds(5))
.map { LocalDateTime.now() }
.distinctUntilChanged { old, new ->
old.minute == new.minute
}
.onEach {
setDateTime(it)
}
.launchIn(viewModelScope) // or lifecycleScope or other
如果您不想要實驗性Duration
API,您也可以使用Long
毫秒。
注意:使用此處編寫的代碼, tickerFlow
不考慮處理元素所花費的tickerFlow
,因此延遲可能不正常(這是元素處理之間的延遲)。 如果您希望股票行情獨立於每個元素的處理,您可能需要使用緩沖區或專用線程(例如通過flowOn
)。
我相信它仍然是實驗性的,但是您可以使用TickerChannel每 X 毫秒生成一個值:
val tickerChannel = ticker(delayMillis = 60_000, initialDelayMillis = 0)
repeat(10) {
tickerChannel.receive()
val currentTime = LocalDateTime.now()
println(currentTime)
}
如果您需要在“訂閱”為每個“滴答”做一些事情的同時繼續做您的工作,您可以launch
一個后台協程,它將從這個頻道中讀取並做您想做的事情:
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()
如果你想從循環內部停止,你可以簡單地跳出循環,然后取消通道:
val ticker = ticker(500, 0)
var count = 0
for (event in ticker) {
count++
if (count == 4) {
break
} else {
println(count)
}
}
ticker.cancel()
Kotlin Flows 的一種非常務實的方法可能是:
// 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}")
}
您可以像這樣創建倒數計時器
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!"
}
另一個可能的解決方案作為可重復使用的擴展科特林CoroutineScope
fun CoroutineScope.launchPeriodicAsync(
repeatMillis: Long,
action: () -> Unit
) = this.async {
if (repeatMillis > 0) {
while (isActive) {
action()
delay(repeatMillis)
}
} else {
action()
}
}
然后用作:
var job = CoroutineScope(Dispatchers.IO).launchPeriodicAsync(100) {
//...
}
然后打斷它:
job.cancel()
編輯:喬弗里用更好的方法編輯了他的解決方案。
老的 :
Joffrey的解決方案對我有用,但我遇到了 for 循環問題。
我必須在 for 循環中取消我的自動收報機,如下所示:
val ticker = ticker(500, 0)
for (event in ticker) {
if (...) {
ticker.cancel()
} else {
...
}
}
}
但是ticker.cancel()
拋出了一個取消ticker.cancel()
因為 for 循環在此之后繼續運行。
我不得不使用 while 循環來檢查通道是否未關閉以防止出現此異常。
val ticker = ticker(500, 0)
while (!ticker.isClosedForReceive && ticker.iterator().hasNext()) {
if (...) {
ticker.cancel()
} else {
...
}
}
}
這是使用 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()
}
}
用法
val job = CoroutineScope(Dispatchers.Main).launch {
tickFlow(125L).collect {
print(it)
}
}
...
job.cancel()
這是基於 Joffrey 回答的Observable.intervalRange(1, 5, 0, 1, TimeUnit.SECONDS)
的Flow
版本:
fun tickerFlow(start: Long,
count: Long,
initialDelayMs: Long,
periodMs: Long) = flow<Long> {
delay(initialDelayMs)
var counter = start
while (counter <= count) {
emit(counter)
counter += 1
delay(periodMs)
}
}
//...
tickerFlow(1, 5, 0, 1_000L)
制作Observable.intervalRange(0, 90, 0, 1, TimeUnit.SECONDS)
的副本Observable.intervalRange(0, 90, 0, 1, TimeUnit.SECONDS)
每 1 秒將在 90 秒內發出項目):
fun intervalRange(start: Long, count: Long, initialDelay: Long = 0, period: Long, unit: TimeUnit): Flow<Long> {
return flow<Long> {
require(count >= 0) { "count >= 0 required but it was $count" }
require(initialDelay >= 0) { "initialDelay >= 0 required but it was $initialDelay" }
require(period > 0) { "period > 0 required but it was $period" }
val end = start + (count - 1)
require(!(start > 0 && end < 0)) { "Overflow! start + count is bigger than Long.MAX_VALUE" }
if (initialDelay > 0) {
delay(unit.toMillis(initialDelay))
}
var counter = start
while (counter <= count) {
emit(counter)
counter += 1
delay(unit.toMillis(period))
}
}
}
用法:
lifecycleScope.launch {
intervalRange(0, 90, 0, 1, TimeUnit.SECONDS)
.onEach {
Log.d(TAG, "intervalRange: ${90 - it}")
}
.lastOrNull()
}
具有啟動、暫停和停止功能的定時器。
class TimerViewModel : ViewModel() {
private var job: Job? = null
private val _times = MutableStateFlow(0)
val times = _times.asStateFlow()
fun start(times: Int = 60) {
if (_times.value == 0) _times.value = times
job?.cancel()
job = viewModelScope.launch(Dispatchers.IO) {
while (isActive) {
if (_times.value <= 0) {
job?.cancel()
return@launch
}
delay(timeMillis = 1000)
_times.value -= 1
}
}
}
fun pause() {
job?.cancel()
}
fun stop() {
job?.cancel()
_times.value = 0
}
}
我在這里舉了這個例子。
enter code here
private val updateLiveShowTicker = flow {
while (true) {
emit(Unit)
delay(1000L * UPDATE_PROGRAM_INFO_INTERVAL_SECONDS)
}
}
private val updateShowProgressTicker = flow {
while (true) {
emit(Unit)
delay(1000L * UPDATE_SHOW_PROGRESS_INTERVAL_SECONDS)
}
}
private val liveShow = updateLiveShowTicker
.combine(channelId) { _, channelId -> programInfoRepository.getShow(channelId) }
.catch { emit(LiveShow(application.getString(R.string.activity_channel_detail_info_error))) }
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1)
.distinctUntilChanged()
我的解決方案,您現在可以使用 Flow API 創建自己的代碼流:
最近使用它來根據計時器和最大緩沖區大小來分塊值。
private object Tick
@Suppress("UNCHECKED_CAST")
fun <T : Any> Flow<T>.chunked(size: Int, initialDelay: Long, delay: Long): Flow<List<T>> = flow {
if (size <= 0) throw IllegalArgumentException("invalid chunk size $size - expected > 0")
val chunkedList = mutableListOf<T>()
if (delay > 0L) {
merge(this@chunked, timerFlow(initialDelay, delay, Tick))
} else {
this@chunked
}
.collect {
when (it) {
is Tick -> {
if (chunkedList.isNotEmpty()) {
emit(chunkedList.toList())
chunkedList.clear()
}
}
else -> {
chunkedList.add(it as T)
if (chunkedList.size >= size) {
emit(chunkedList.toList())
chunkedList.clear()
}
}
}
}
if (chunkedList.isNotEmpty()) {
emit(chunkedList.toList())
}
}
fun <T> timerFlow(initialDelay: Long, delay: Long, o: T) = flow {
if (delay <= 0) throw IllegalArgumentException("invalid delay $delay - expected > 0")
if (initialDelay > 0) delay(initialDelay)
while (currentCoroutineContext().isActive) {
emit(o)
delay(delay)
}
}
它沒有使用 Kotlin 協程,但是如果您的用例足夠簡單,您總是可以使用諸如fixedRateTimer
或timer
(此處為文檔)之類的東西,它們解析為 JVM 本機Timer
。
我在一個相對簡單的場景中使用了 RxJava 的interval
,當我切換到使用 Timers 時,我看到了顯着的性能和內存改進。
您還可以使用View.post()
或其多個變體在 Android 的主線程上運行您的代碼。
唯一真正的煩惱是您需要自己跟蹤舊時代的狀態,而不是依靠 RxJava 來為您完成。
但這總是要快得多(如果你正在做 UI 動畫等性能關鍵的東西,這很重要)並且不會有 RxJava 的 Flowables 的內存開銷。
這是使用fixedRateTimer
的問題代碼:
var currentTime: LocalDateTime = LocalDateTime.now()
fixedRateTimer(period = 5000L) {
val newTime = LocalDateTime.now()
if (currentTime.minute != newTime.minute) {
post { // post the below code to the UI thread to update UI stuff
setDateTime(newTime)
}
currentTime = newTime
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.