冷流才是正统!!!!!!!!
1. 【有】生命周期感知,倒计时
1.离开页面,倒计时停止,回到页面倒计时继续,例如广告
- 可于 Activity / Fragment 中直接使用,没必要封装
// (属性) 用于取消,确保不会重复收集流
private var mTickJob: Job? = null
// (函数),开始倒计时
private fun countDown() {
mTickJob?.cancel()
var duration = 10
mTickJob = lifecycleScope.launch {
// 离开目标页面的时候,暂停收集流
repeatOnLifecycle(Lifecycle.State.STARTED) {
flow {
while (true) {
kotlinx.coroutines.delay(1000)
emit(null)
Log.d("BMO-BMO", "emit...")
}
}.onStart {
// 开始,显示倒计时 UI
Log.d("BMO-BMO", "开始倒计时 $duration s")
}.collect {
duration -= 1
// 更新倒计时 UI
Log.d("BMO-BMO", "倒计时 $duration s")
if (duration <= 0) {
mTickJob?.cancel()
// 结束,隐藏倒计时 UI
Log.d("BMO-BMO", "倒计时 结束!!")
}
}
}
}
}
1. 【无】生命周期感知,倒计时
可用于发送验证码之类的倒计时
xxx.kt 封装倒计时函数
/**
* 于协程中实现倒计时
* @param duration 倒计时时长
* @param interval 倒计时步长
* @param scope 协程作用域
* @param onTick 倒计时变更,回调
* @param onStart 倒计时开始,回调
* @param onEnd 倒计时结束,回调
*
* @return [Job] 可用于在需要时,取消倒计时
*/
fun countDownCoroutine(
duration: Int,
interval: Int = 1,
scope: CoroutineScope,
onTick: (Int) -> Unit,
onStart: ((Int) -> Unit)? = null,
onEnd: (() -> Unit)? = null,
): Job {
if (duration <= 0 || interval <= 0) {
throw IllegalArgumentException("duration or interval can not less than zero")
}
return flow {
for (i in duration downTo 0 step interval) {
delay((interval * 1000).toLong())
emit(i)
}
}
.onEach { onTick.invoke(it) }
.onStart { onStart?.invoke(duration) }
.onCompletion {
// 正常结束,才能回调。
if (it == null) {
onEnd?.invoke()
}
}
// 确保上游回调,是在主线程回调
.flowOn(Dispatchers.Main)
.launchIn(scope)
}
2. 目标页面调用
我们倒计时,建议使用 lifecycleScope, 便不需要 onDestroy 中调用 mTickJob?.cancel()
// (属性) 用于取消,确保不会重复收集流
private var mCountDownJob: Job? = null
// (函数),开始倒计时
private fun countDown() {
mCountDownJob?.cancel()
mCountDownJob = countDownCoroutine(duration = 10, scope = lifecycleScope, onTick = {
Log.d("BMO-BMO", "更新 UI ,剩余时间 $it s")
}, onStart = {
Log.d("BMO-BMO", "倒计时开始 ,剩余时间 $it s")
}, onEnd = {
Log.d("BMO-BMO", "倒计时结束 !!")
mCountDownJob?.cancel()
})
}
关于协程和 Kotlin Flow 的知识点,诸位自行查阅,嗯。
3. 【有】生命周期感知,TickFlow 秒触发器
如果页面需要显示手机本地时间,或者例如每 1 s 就干点什么事情,可以试试
repeatOnLifecycle 使得离开页面,不会收集流,job 直接被取消掉
函数
/**
*
* @param lifecycleOwner 直接传 Activity / Fragment
* @param onTick 每 [interval] 毫秒,滴答一下
*/
fun tickFlow(
interval: Long = 1000,
lifecycleOwner: LifecycleOwner,
onTick: () -> Unit,
): Job {
return lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
flow {
while (true) {
delay(interval)
emit(null)
}
}.collect {
onTick.invoke()
}
}
}
}
Activity / Fragment 中调用
// (属性) 用于取消,确保不会重复收集流
private var mTickJob: Job? = null
// 懒加载,复用对象
private val mTickDate by lazy { Date() }
// (函数),开始滴答
private fun startTick() {
mTickJob?.cancel()
mTickJob = tickFlow(lifecycleOwner = this) {
mTickDate.time = System.currentTimeMillis()
Log.d(
"BMO-BMO",
"当前时间: ${TimeDateHelper.dateToString(mTickDate, TimeDateHelper.longerPattern)}"
)
}
}
/**
* Date 转 字符串
*/
fun dateToString(date:Date, pattern: String = shortPattern):String {
val simpleDateFormat = SimpleDateFormat(pattern)
return simpleDateFormat.format(date)
}