Issue
I have the following code and a few questions that need to be answered:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launchWhenResumed {
delay(2000)
Log.d("LifeCycleAware", "launchWhenStarted: before calling")
val result = differentDispatcher()
Log.d("LifeCycleAware", "launchWhenStarted: after calling $result")
}
}
private suspend fun differentDispatcher(): Int =
withContext(Dispatchers.Default) {
for (i in 1..5) {
delay(2000)
Log.d("LifeCycleAware", "Inside different Dispatcher")
}
return@withContext 9
}
override fun onStart() {
super.onStart()
Log.d("LifeCycleAware", "onStart")
}
override fun onStop() {
super.onStop()
Log.d("LifeCycleAware", "onStop")
}
As far as I understand, the method (or any of them) launchWhenResumed, is called when the Lifecycle has reached the RESUMED state, and I also know that when I move the app to the background, the corrutine will stop, but it will not stop if it has child corrutines running in another Dispatcher, so far so good.
So in this code, we determine that if I, in the middle of the loop that is in the differentDispatcher method, send the app to second, it will continue to run but when it finishes, the parent corrutine launched with launchWhenResumed, will not resume until it takes this RESUMED state again.
My first doubt is... if when the corrutine is finished running, I go to the background and return to the foreground, why is it not launched again, if I have returned to the RESUMED state?
I also know about the existence of the repeatOnLifecycle method, where, if I pass the Lifecycle.State.RESUMED state as parameter, it is executed every time, moreover, I know that in this case if I go to the background, the execution of the corrutine is completely suspended and when I go back to the foreground it starts from the beginning, but, why when I run with launchWhenResumed and the corrutine finishes it does not start again, but with repeatOnLifecycle it does? What does it do differently internally?
I guess the answer is because when I switch from background to foreground, the onCreate method is not called again, and I've checked that:
override fun onResume() {
super.onResume()
lifecycleScope.launchWhenResumed {
delay(2000)
Log.d("LifeCycleAware", "launchWhenStarted: before calling")
val result = differentDispatcher()
Log.d("LifeCycleAware", "launchWhenStarted: after calling $result")
}
}
This way it does re-launch because the onResume method does call again when I switch from background to foreground but then.... what kind of magic does the repeatOnLifecycle method do?
Solution
The key to understanding launchWhenResumed
is to break it down into the two parts that is actually is: a launch
and a whenResumed
block. Looking at the source code, you'll see it is actually exactly that:
public fun launchWhenResumed(
block: suspend CoroutineScope.() -> Unit
): Job = launch { // It does a regular launch
lifecycle.whenResumed(block) // Then passes your block to whenResumed
}
A launch
is a one time operation - a launch
done in onCreate()
will only run exactly once for each call to onCreate()
. This is also why calling launch
in onResume()
will launch every time you hit onResume
.
A call to launch
finishes in one of two ways: all of the calls within that block complete normally or the CoroutineScope
is cancelled. For lifecycleScope
, that cancellation happens when the Lifecycle
reaches DESTROYED
.
So in a regular launch
, work starts immediately, runs until everything completes (or the scope is cancelled), and that's it. It never runs again or restarts at all.
Instead, the whenResumed
is an example of Suspend Lifecycle-aware coroutines:
Even though the CoroutineScope provides a proper way to cancel long-running operations automatically, you might have other cases where you want to suspend execution of a code block unless the
Lifecycle
is in a certain state.Any coroutine run inside these blocks is suspended if the
Lifecycle
isn't at least in the minimal desired state.
So what whenResumed
does is just pause the coroutine code when you fall below the resumed state, essentially meaning that if your Lifecycle
stops being RESUMED
, instead of your val result = differentDispatcher()
actually resuming execution immediately and your result being delivered back to your code, that result is simply waiting for your Lifecycle
to again reach RESUMED
.
So whenResumed
doesn't have any 'restarting' functionality - just like other coroutine code, it just runs the code you've given it and then completes normally.
You're right that repeatOnLifecycle
is a very different pattern. As per the Restartable Lifecycle-aware coroutines, repeatOnLifecycle
doesn't have any of the 'pausing' behavior at all:
Even though the lifecycleScope provides a proper way to cancel long-running operations automatically when the Lifecycle is DESTROYED, you might have other cases where you want to start the execution of a code block when the Lifecycle is in a certain state, and cancel when it is in another state.
So in the repeatOnLifecycle
call, every time your Lifecycle
reaches RESUMED
(or what Lifecycle.State
you want), the block is ran. When you fall below that state, the whole block is completely cancelled (very similar to when your LifecycleOwner
reaches DESTROYED
- that level of cancelling the whole coroutine scope).
You'll not the dire warning at the end of the page that talks about both of these APIs:
Warning: Prefer collecting flows using the
repeatOnLifecycle
API instead of collecting inside thelaunchWhenX
APIs. As the latter APIs suspend the coroutine instead of cancelling it when the Lifecycle is STOPPED, upstream flows are kept active in the background, potentially emitting new items and wasting resources.
The fact that your differentDispatcher
code continues to run in the background, despite being inside a whenResumed
block is considered a bad thing - if that code was doing more expensive operations, like checking for the user's location (keeping GPS on), it would continue to use up system resources and the user's battery the whole time, even when you aren't RESUMED
.
Answered By - ianhanniballake
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.