如果在一个组件中启动的协程“逃逸”了其生命周期(即组件销毁了,但协程还在后台运行),将会导致严重问题:
Activity
)的引用,垃圾回收器将无法回收该组件,导致内存泄漏。IllegalStateException
或其他运行时崩溃。通过创建一个与组件生命周期绑定的 CoroutineScope
,我们可以在组件销毁时,自动取消在该 scope
内启动的所有协程,从而优雅地解决上述所有问题。
虽然现在已有更好的官方库支持,但理解如何手动实现一个 CoroutineScope
对于掌握其工作原理非常有帮助。
选择 Job
类型: 创建一个 Job
实例作为 scope
的核心任务控制器。
Job()
: 标准 Job
。其任何子协程的失败都会导致整个 scope
和所有其他子协程被取消(“一损俱损”)。SupervisorJob()
: 监督 Job
。其子协程的失败不会影响 scope
或其他兄弟协程。在 UI 组件中,这通常是更好的选择,因为一个独立的 UI 任务(如一次网络请求)的失败不应取消其他不相关的任务。创建 CoroutineScope
实例: 将 Job
和一个默认的 CoroutineDispatcher
(通常是 Dispatchers.Main
)组合起来,创建 scope
。
在组件中实现 CoroutineScope
接口: 让你的组件(如 Activity
)实现 CoroutineScope
接口,并将刚刚创建的 scope
作为其 coroutineContext
的实现。这允许你直接在组件内部调用 launch
, async
等构建器。
在销毁回调中取消 Job
: 在组件生命周期结束的回调中(如 onDestroy()
),调用 job.cancel()
来取消该 scope
内所有正在运行的协程。
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
// 步骤 3: 让 Activity 实现 CoroutineScope 接口
class MyManualScopeActivity : AppCompatActivity(), CoroutineScope {
// 步骤 1 & 2: 创建 Job 和 CoroutineContext
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job // 使用主线程调度器 + Job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = SupervisorJob() // 在 Activity 创建时初始化 Job
// 现在可以直接在 Activity 中启动协程
launch {
// ... 执行一些与 UI 相关的协程任务 ...
delay(5000)
println("协程在 Activity 存活时执行完毕")
}
}
override fun onDestroy() {
super.onDestroy()
// 步骤 4: 在 Activity 销毁时取消所有协程
job.cancel()
println("Activity 销毁,所有协程已被取消")
}
}
手动管理 Job
的生命周期是可行的,但也容易出错。幸运的是,Google 提供了 Jetpack KTX 扩展库,为我们内置了与生命周期感知的 CoroutineScope
,这是目前推荐的最佳实践。
androidx.lifecycle:lifecycle-viewmodel-ktx
ViewModel
中使用。它被绑定到 ViewModel
的生命周期,当 ViewModel
的 onCleared()
方法被调用时,viewModelScope
会自动取消。SupervisorJob() + Dispatchers.Main.immediate
internal fun createViewModelScope(): CloseableCoroutineScope {
val dispatcher =
try {
// In platforms where `Dispatchers.Main` is not available, Kotlin Multiplatform will
// throw
// an exception (the specific exception type may depend on the platform). Since there's
// no
// direct functional alternative, we use `EmptyCoroutineContext` to ensure that a
// coroutine
// launched within this scope will run in the same context as the caller.
Dispatchers.Main.immediate
} catch (_: NotImplementedError) {
// In Native environments where `Dispatchers.Main` might not exist (e.g., Linux):
EmptyCoroutineContext
} catch (_: IllegalStateException) {
// In JVM Desktop environments where `Dispatchers.Main` might not exist (e.g., Swing):
EmptyCoroutineContext
}
return CloseableCoroutineScope(coroutineContext = dispatcher + SupervisorJob())
}
示例代码:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun fetchData() {
// 直接使用 viewModelScope,无需手动创建或取消
viewModelScope.launch {
// ... 执行网络请求或数据库操作 ...
}
}
// 当 ViewModel 被销毁时,这里的协程会自动取消
}
androidx.lifecycle:lifecycle-runtime-ktx
Activity
或 Fragment
中使用。它被绑定到组件的 Lifecycle
,并在 Lifecycle
进入 DESTROYED
状态时自动取消。SupervisorJob() + Dispatchers.Main.immediate
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = internalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
val newScope =
LifecycleCoroutineScopeImpl(this, SupervisorJob() + Dispatchers.Main.immediate)
if (internalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
示例代码:
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class MyActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
// 直接使用 lifecycleScope,它知道何时该取消
lifecycleScope.launch {
// ... 执行一些与 UI 生命周期相关的任务 ...
}
}
}
方法 | 优点 | 缺点 | 推荐度 |
---|---|---|---|
手动实现 | 有助于深入理解协程作用域和 Job 的工作原理。 |
代码模板化,容易忘记取消,容易出错。 | ★★☆☆☆ (仅用于学习) |
使用 KTX 预置 Scope | 代码简洁、安全、不易出错,官方推荐。 | 隐藏了部分实现细节。 | ★★★★★ (生产环境首选) |
在现代 Android 开发中,我们应优先使用 viewModelScope
和 lifecycleScope
。它们不仅简化了代码,还通过标准化的方式确保了结构化并发的正确实现,让开发者能更专注于业务逻辑。