写 Kotlin 的时候,你是不是也遇到过这样的情况:页面跳转后,协程还在后台拼命干活,结果更新了一个已经销毁的 Activity 或 Fragment?或者点了个按钮启动网络请求,转头就退出页面,结果回调回来时 App 崩了?
协程不是“放养”的,得有个家
协程本身很轻量,但不代表它能随便乱跑。Kotlin 里每个协程都必须运行在某个 作用域(CoroutineScope) 下——就像人得有户口所在地,协程也得有归属。这个作用域决定了协程的生命周期、取消时机和上下文环境。
最常见的三个作用域
刚入门时,你大概率会碰到这三个:
1. viewModelScope(ViewModel 里最常用)
class MainViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
// 模拟耗时读取本地数据库
delay(800)
"用户列表"
}
// 更新 UI 状态(自动切回主线程)
_uiState.value = data
}
}
}只要 ViewModel 还活着,协程就能安心执行;一旦 ViewModel 被清除(比如 Activity 销毁),viewModelScope 会自动取消所有子协程——不用手动调用 cancel(),也不用担心内存泄漏。
2. lifecycleScope(Activity/Fragment 中直接用)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
delay(1000)
Log.d("Main", "这行日志不一定能打印出来")
}
}
}它和 Activity/Fragment 的生命周期绑定。如果 launch 后用户立刻按返回键退出 Activity,协程会自动被取消,log 就不会输出。安全又省心。
3. GlobalScope(新手慎用!)
名字听着大气,实际是“孤儿协程”:没有生命周期管理,一启动就不管死活,容易导致内存泄漏或崩溃。除非你明确需要一个长期存活、与应用同寿的后台任务(比如监听系统广播),否则别碰它。
自己造个作用域?也可以,但得懂规矩
有时候你需要更细粒度控制,比如只在某次页面可见期间运行协程,或者限制并发数量。这时候可以手动创建:
private val customScope = CoroutineScope(
Dispatchers.Main + Job()
)
fun startTask() {
customScope.launch {
// ……
}
}
fun cleanup() {
customScope.cancel() // 记得主动清理!
}注意:手动创建的作用域,必须自己负责 cancel,不然协程可能一直挂着。这也是为什么官方推荐优先用 viewModelScope 和 lifecycleScope ——它们帮你把这事干了。
一个小技巧:用 withContext 切线程,别乱开新 scope
有人看到 IO 操作慢,就想另起一个协程来跑:launch { doNetwork() } // ❌ 不必要地多开一层
其实更简洁安全的做法是:
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
doNetwork() // 在 IO 线程执行
}
updateUi(result) // 自动回到主线程
}withContext 是协程内部的线程切换,不创建新协程,也不新增作用域层级,干净利落。
协程作用域不是语法糖,它是 Kotlin 异步编程的安全带。选对 scope,等于给协程上了保险——该停的时候停,该走的时候走,不拖泥带水,也不偷偷摸摸。