viewModelScope not cancelled

拜拜、爱过 提交于 2021-01-27 05:56:20

问题


After watching Sean's explanation on Android (Google I/O'19) I've tried the same:

init{
    viewModelScope.launch {
        Timber.i("coroutine awake")
        while (true){
            delay(2_000)
            Timber.i("another round trip")
        }
    }
}

Unfortunately onCleared it's called when the activity is killed but not when it's put in background ("when we move away from the Activity...", background is "moving away" imho ^^).
And I get the following output:

> ---- Activity in Foreground
> 12:41:10.195  TEST: coroutine awake
> 12:41:12.215  TEST: another round trip
> 12:41:14.231  TEST: another round trip
> 12:41:16.245  TEST: another round trip
> 12:41:18.259  TEST: another round trip
> 12:41:20.270  TEST: another round trip
> ----- Activity in Background (on onCleared not fired)
> 12:41:22.283  TEST: another round trip
> 12:41:24.303  TEST: another round trip
> 12:41:26.320  TEST: another round trip
> 12:41:28.353  TEST: another round trip
> 12:41:30.361  TEST: another round trip
> ----- Activity in Foreground
> 12:41:30.369  TEST: coroutine awake

How can I solve this?

1 - Move the code from init to a suspend fun start() called by the activity inside a lifecycleScope.launchWhenStarted?

I get the same result. I thought lifecycleScope would cancel its child coroutines when it went to background, but I get the same Timber output with this approach.

2 - Change my ViewModel code to:

private lateinit var job: Job

suspend fun startEmitting() {
    job = viewModelScope.launch {
        Timber.i("coroutine awake")
        while (true){
            delay(2_000)
            Timber.i("another round trip")
        }
    }
}
fun cancelJob(){
    if(job.isActive){
        job.cancel()
    }
}

And, in my Activity:

override fun onResume() {
    super.onResume()
    lifecycleScope.launch {
        viewModel.startEmitting()
    }
}
override fun onPause() {
    super.onPause()
    viewModel.cancelJob()
}

Well it works but isn't the viewModelScope purpose to manage the CoroutineScope for me? I hate this cancelJob logic.

What's the best approach to deal with this?


回答1:


Kotlin can't cancel an infinite operation for you. You need to call isActive somewhere. For example: while(isActive).




回答2:


You can write your own LiveData class instead of using MutableLiveData.

When you do that you can override methods that explicitly notify you if there are any active listeners.

class MyViewModel : ViewModel(){
    val liveData = CustomLiveData(viewModelScope)

    class CustomLiveData(val scope) : LiveData<Int>(){
        private counter = 0
        private lateinit var job: Job

        override fun onActive() {
            job = scope.launch {
                Timber.i("coroutine awake")
                while (true){
                    delay(2_000)
                    Timber.i("another round trip")
                    postValue(counter++)
                }
        }

        override fun onInactive() {
            job.cancel()
        }
    }
}

Then inside your Activity you no longer need to explicitly call any start/pause as LiveData will automatically begin and cancel coroutine depending on observers state.



来源:https://stackoverflow.com/questions/58099407/viewmodelscope-not-cancelled

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!