1.前提
一开始用Flow,主要用来替代LiveData,用来观察。
然后发现了StateFlow和SharedFlow这两个主要就是用来替代LiveData的.
两者区别:
StateFlow:
1.必须要有初始值
2.只会把最新的值重现给订阅者,与活跃的订阅者数量无关
SharedFlow:
1.没有初始值
2.可以保留历史数据
3.发射使用emit()/tryemit(),而没有setvalue()
当我在使用FLow去collect数据的时候,发现文档有这样一个提示
警告:如果需要更新界面,切勿使用 launch 或 launchIn 扩展函数从界面直接收集数据流。即使 View 不可见,这些函数也会处理事件。此行为可能会导致应用崩溃。 为避免这种情况,请使用 repeatOnLifecycle API(如上所示)。
2.情况重现
val viewmodel:MainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
viewmodel.viewModelScope.launch {
viewmodel.lazyInitStateFlow.collect {
findViewById<Button>(R.id.test_forB).text = it
Log.d(TAG, "initViewmodel: $it")
}
}
乍看,这些代码确实可以collect到Flow过来的数据,但是有一点,当我的Activity到了后台,但是Flow的数据仍然一直在setvalue,那么我们的协程会停止吗? 答案是:并不会
class MyTimerTask(val block:()->Unit) : TimerTask() {
override fun run() {
block.invoke()
}
}
val timer = Timer()
timer.schedule(MyTimerTask({viewmodel.lazyInitStateFlow.value = (x++).toString() }), 0, 1000)
如上,我们开始一个定时任务,一直setvalue(),且我们从MainActivity到
MainActivityXXX.我们可以看到collect()并没有被停止,这样可能造成严重的后果,资源浪费是一点,更有可能造成我们的程序崩溃.
3.解决方法
最土的方法:在每一个OnStop的时候对此协程进行stop.但是这样你会发现代码编程了模板,就非常的不合适.
此时Google就推荐哦我们使用repeatOnLifecycle API
repeatOnLifecycle在lifecycle-runtime-ktx:2.4.0-alpha01上才拥有
我们要先引入资源在build.gradle中加入
implementation ‘androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01’
在activity中,修改一下collect中的代码,可以看到,我们从MainActivity到MainActivtyXXX后,collect就停止了. 返回到MainActivity后,又collect仍然在工作.
这样就达到了我们的目的!
在这里解释一下repeatOnLifecycle
它会将当前协程的执行中断,直到特定事件发生。比如这里是当生命周期低于Start时被停止,再次到达Start时继续执行。
viewmodel.viewModelScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewmodel.lazyInitStateFlow.collect {
findViewById<Button>(R.id.test_forB).text = it
Log.d(TAG, "initViewmodel: $it")
}
}
}
封装
如果我们要在每一次调用的时候都要写那么多代码,那么在collect的时候代码量会非常的多。那么我们封装出一个简单的函数来简化我们的代码量。
在这个时候我就在考虑kotlin的协程作用域的问题.一般来说与调用方相同即可,那么我们一般用Viewmodel的Scope
.但是我们知道我们需要一个Lifecycle,那么我们直接用Lifecycle做为作用域即可。看代码.
inline fun <T> Flow<T>.launchAndCollectInx(
owner: Lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
crossinline action: suspend CoroutineScope.(T) -> Unit
) {
owner.coroutineScope.launch {
owner.repeatOnLifecycle(minActiveState) {
collect {
action(it)
}
}
}
}
viewmodel.lazyInitStateFlow.launchAndCollectInx(lifecycle,
action = { Log.d(TAG, "initViewmodel: $it")})