Android中kotlin Flow的资源浪费/ANR问题

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")})
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值