写Android App时,你是不是也遇到过这样的情况:点个按钮开始加载数据,转着圈圈等半天,结果切到别的页面再回来,发现数据还在那儿拼命回调——甚至崩溃报错说‘Activity has been destroyed’?问题往往不在网络,而在协程没管好‘地盘’。
协程也有‘户口本’
协程不是想开就开、想停就停的野马。它得有个‘归属’,这个归属就是作用域(CoroutineScope)。就像公司员工得挂靠在某个部门,协程也得绑定在一个有生命周期的上下文里,否则它干完活找不到主家,主家关了它还傻跑,迟早出事。
常见作用域怎么选?
在Android开发中,最常用的是这几个:
lifecycleScope:绑定到Activity或Fragment的生命周期。页面一销毁,里面所有协程自动取消。适合做UI相关操作,比如更新列表、弹Toast。
lifecycleScope.launch {
val data = withContext(Dispatchers.IO) { fetchDataFromNetwork() }
textView.text = data
}
viewModelScope:绑定到ViewModel。页面重建(比如横竖屏切换)它不丢,但ViewModel被清除时协程才停。适合放业务逻辑,比如保存用户输入、处理分页请求。
viewModelScope.launch {
userRepo.updateProfile(profile)
.collect { result -> handleResult(result) }
}
GlobalScope:慎用!它不绑定任何生命周期,相当于‘裸奔’协程。App退后台它还在跑,容易内存泄漏、空指针、甚至发重复请求。新手常踩这个坑,能不用就别用。
一个小实验帮你理解
假设你在Activity里这样写:
GlobalScope.launch {
delay(5000)
textView.text = "5秒后才显示" // 此时Activity可能已finish
}
点返回键退出页面,5秒后App大概率直接闪退——因为textView已经null了。换成lifecycleScope,这段代码根本不会执行到最后那句。
自己定义作用域?也可以,但得守规矩
真要手动创建作用域(比如在工具类里),记得加上Job和CoroutineName,方便追踪和取消:
private val customScope = CoroutineScope(
Dispatchers.IO + Job() + CoroutineName("MyWorker")
)
而且必须在合适的时机主动取消,比如在onDestroy里调用customScope.cancel(),不然照样泄漏。
说白了,协程作用域不是语法糖,是责任划分。你给它一个家,它才肯听话干活;你撒手不管,它就自己乱撞——电脑办公开发里,稳比快更重要。