在 Android 开发中,Jetpack 提供的 CoroutineScope(如 viewModelScope 或 lifecycleScope)提供了便捷的生命周期管理。当 Activity、Fragment 或 Lifecycle 结束时,这些 Scope 会自动取消所有正在运行的协程。如果你手动创建 CoroutineScope,务必保存 Job 实例并在不需要时调用 cancel 方法。
然而,在某些场景下,我们需要确保某个操作能够完整执行,即使用户已离开当前界面。例如写入数据库或向服务器发起关键的网络请求。如果此时 viewModelScope 或 lifecycleScope 被销毁并取消了协程,可能会导致数据不一致或服务中断。虽然 cancel 动作本身不会立即停止协程代码的执行(需要协程内部配合),但会抛出 CancellationException 中断流程。那么,如何确保重要任务不被意外取消呢?
协程与 WorkManager 的选择
如果你的操作生命周期长于 App 进程(例如后台发送日志),应优先使用 WorkManager。WorkManager 是用于调度在未来某个时间点执行的关键操作的库,即使应用被杀死也能保证执行。
只要 App 进程存活,协程就可以持续运行。对于那些需要在当前进程生命周期内有效,但在用户杀掉 App 时可以取消的操作(例如获取新闻列表),则适合使用协程。
协程中不应取消的操作场景
假设应用中有一个 ViewModel 和一个 Repository,逻辑如下:
class MyViewModel(private val repo: Repository) : ViewModel() {
fun callRepo() {
viewModelScope.launch {
repo.doWork()
}
}
}
class Repository(private val ioDispatcher: CoroutineDispatcher) {
suspend fun doWork() {
withContext(ioDispatcher) {
doSomeOtherWork()
veryImportantOperation() // 此操作不应被取消,至关重要
}
}
}
我们不希望 veryImportantOperation() 受 viewModelScope 控制,因为它可能在任何时候被取消。我们希望该操作的生命周期比 ViewModel 更长。如何实现这一点?
解决方案:在 Application 类中创建自定义 Scope
请在 Application 类中创建自己的 Scope,并在由它启动的协程中调用这些重要的操作。其他类(如 Repository)可以直接从 Application 中获取该 Scope。
与 GlobalScope 相比,创建自定义 CoroutineScope 的好处是可以灵活配置。例如,你可以配置 CoroutineExceptionHandler,指定线程池 Dispatcher,并将所有常见配置放在 CoroutineContext 中。
建议将其命名为 applicationScope,并且必须包含 SupervisorJob(),以防止协程中的异常在层次结构中传播(参考本系列关于异常处理的讨论)。
class MyApplication : Application() {
applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
}

