问题
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