LifecycleCoroutine
LifecycleCoroutine
图解
sequenceDiagram
LifecycleCoroutineScopeImpl->>LifecycleCoroutineScopeImpl: Lifecycle.coroutineScope.get(),register
activate LifecycleCoroutineScopeImpl
Note right of LifecycleCoroutineScopeImpl: lunch启动协程并lifecycle.addObserver
deactivate LifecycleCoroutineScopeImpl
LifecycleCoroutineScopeImpl->>LifecycleCoroutineScopeImpl: onStateChanged回调时State.DESTROYED自动取消协程
LifecycleCoroutineScopeImpl->>LifecycleCoroutineScopeImpl: launchWhenXxx
activate LifecycleCoroutineScopeImpl
LifecycleCoroutineScopeImpl->>LifecycleController: new LifecycleController
LifecycleController->>LifecycleController: lifecycle.addObserver
LifecycleController->>DispatchQueue: dispatch lifecycle to
LifecycleCoroutineScopeImpl->>PausingDispatcher: withContext(PausingDispatcher, block)
deactivate LifecycleCoroutineScopeImpl
PausingDispatcher->>PausingDispatcher: dispatchQueue.runOrEnqueue(block)
activate PausingDispatcher
PausingDispatcher->>DispatchQueue: dispatch block to
deactivate PausingDispatcher
LifecycleCoroutineScopeImpl
/**
* [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
internal class LifecycleCoroutineScopeImpl(
override val lifecycle: Lifecycle,
override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
init {
// in case we are initialized on a non-main thread, make a best effort check before
// we return the scope. This is not sync but if developer is launching on a non-main
// dispatcher, they cannot be 100% sure anyways.
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
coroutineContext.cancel()
}
}
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel()
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel()
}
}
}
LifecycleCoroutineScope
abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
internal abstract val lifecycle: Lifecycle
fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenCreated(block)
}
fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenStarted(block)
}
fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenResumed(block)
}
}
suspend fun <T> Lifecycle.whenCreated(block: suspend CoroutineScope.() -> T): T {
return whenStateAtLeast(Lifecycle.State.CREATED, block)
}
/**
* Runs the given [block] on a [CoroutineDispatcher] that executes the [block] on the main thread
* and suspends the execution unless the [Lifecycle]'s state is at least [minState].
*
* If the [Lifecycle] moves to a lesser state while the [block] is running, the [block] will
* be suspended until the [Lifecycle] reaches to a state greater or equal to [minState].
*
* Note that this won't effect any sub coroutine if they use a different [CoroutineDispatcher].
* However, the [block] will not resume execution when the sub coroutine finishes unless the
* [Lifecycle] is at least in [minState].
*
* If the [Lifecycle] is destroyed while the [block] is suspended, the [block] will be cancelled
* which will also cancel any child coroutine launched inside the [block].
*
* If you have a `try finally` block in your code, the `finally` might run after the [Lifecycle]
* moves outside the desired state. It is recommended to check the [Lifecycle.getCurrentState]
* before accessing the UI. Similarly, if you have a `catch` statement that might catch
* `CancellationException`, you should check the [Lifecycle.getCurrentState] before accessing the
* UI. See the sample below for more details.
......
*/
suspend fun <T> Lifecycle.whenStateAtLeast(
minState: Lifecycle.State,
block: suspend CoroutineScope.() -> T
) = withContext(Dispatchers.Main.immediate) {
val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
val dispatcher = PausingDispatcher()
val controller =
LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
try {
withContext(dispatcher, block)
} finally {
controller.finish()
}
}
PausingDispatcher dispatch block to dispatchQueue
/**
* A [CoroutineDispatcher] implementation that maintains a dispatch queue to be able to pause
* execution of coroutines.
*
* @see [DispatchQueue] and [Lifecycle.whenStateAtLeast] for details.
*/
internal class PausingDispatcher : CoroutineDispatcher() {
/**
* helper class to maintain state and enqueued continuations.
*/
@JvmField
internal val dispatchQueue = DispatchQueue()
@ExperimentalCoroutinesApi
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatchQueue.runOrEnqueue(block)
}
}
LifecycleController dispatch lifecycle to dispatchQueue
internal class LifecycleController(
private val lifecycle: Lifecycle,
private val minState: Lifecycle.State,
private val dispatchQueue: DispatchQueue,
parentJob: Job
) {
private val observer = LifecycleEventObserver { source, _ ->
if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
// cancel job before resuming remaining coroutines so that they run in cancelled
// state
handleDestroy(parentJob)
} else if (source.lifecycle.currentState < minState) {
dispatchQueue.pause()
} else {
dispatchQueue.resume()
}
}
init {
// If Lifecycle is already destroyed (e.g. developer leaked the lifecycle), we won't get
// an event callback so we need to check for it before registering
// see: b/128749497 for details.
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
handleDestroy(parentJob)
} else {
lifecycle.addObserver(observer)
}
}
@Suppress("NOTHING_TO_INLINE") // avoid unnecessary method
private inline fun handleDestroy(parentJob: Job) {
parentJob.cancel()
finish()
}
/**
* Removes the observer and also marks the [DispatchQueue] as finished so that any remaining
* runnables can be executed.
*/
@MainThread
fun finish() {
lifecycle.removeObserver(observer)
dispatchQueue.finish()
}
}