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