常见关键字/函数是这些:
launch
async
withContext
Dispatchers.Main
Dispatchers.IO
Dispatchers.Default
Dispatchers.Unconfined
1. 决定协程运行线程的关键字:Dispatchers.xxx
常见调度器:
| 调度器 | 运行线程 | 用途 |
|---|---|---|
Dispatchers.Main |
主线程/UI 线程 | 更新 UI |
Dispatchers.IO |
IO 线程池 | 网络、数据库、文件读写 |
Dispatchers.Default |
CPU 计算线程池 | 排序、加密、图片处理、大量计算 |
Dispatchers.Unconfined |
不固定线程 | 特殊场景,少用 |
2. 主线程运行的协程
在 Android 里,下面这些通常默认就是主线程。
1. viewModelScope.launch {}
viewModelScope.launch {
// 默认在主线程 Main
textView.text = "hello" // 可以更新 UI,当然 ViewModel 里一般不直接操作 View
}
viewModelScope 默认使用:
Dispatchers.Main.immediate
所以它默认是在主线程。
2. lifecycleScope.launch {}
lifecycleScope.launch {
// 默认在主线程 Main
binding.textView.text = "加载中"
}
lifecycleScope 默认也是主线程。
3. 显式指定 Dispatchers.Main
CoroutineScope(Dispatchers.Main).launch {
// 主线程
}
或者:
launch(Dispatchers.Main) {
// 主线程
}
3. 后台线程运行的协程
如果你要做耗时任务,一般要指定调度器。
1. IO 任务:Dispatchers.IO
适合:
网络请求
数据库操作
文件读写
SharedPreferences 读写
ContentProvider 查询
例子:
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
// 这里运行在 IO 线程
api.getData()
}
// 这里又回到 Main 线程
_uiState.value = result
}
也可以直接:
viewModelScope.launch(Dispatchers.IO) {
// 整个协程默认运行在 IO 线程
val result = api.getData()
}
但是如果后面要更新 UI,需要切回主线程:
viewModelScope.launch(Dispatchers.IO) {
val result = api.getData()
withContext(Dispatchers.Main) {
// 回到主线程
binding.textView.text = result.name
}
}
2. CPU 计算:Dispatchers.Default
适合:
大量计算
排序
JSON 大量解析
图片压缩
加密解密
复杂算法
例子:
viewModelScope.launch {
val result = withContext(Dispatchers.Default) {
heavyCalculate()
}
// 回到主线程
showResult(result)
}
4. launch、async、withContext 分别是什么?
1. launch
启动一个协程,不返回结果。
viewModelScope.launch {
// 默认 Main
}
指定线程:
viewModelScope.launch(Dispatchers.IO) {
// IO 线程
}
适合做:
发起任务
更新状态
不需要直接返回值的任务
2. async
启动一个协程,会返回结果。
val deferred = viewModelScope.async(Dispatchers.IO) {
api.getUser()
}
val user = deferred.await()
常用于并发请求:
viewModelScope.launch {
val userDeferred = async(Dispatchers.IO) {
api.getUser()
}
val orderDeferred = async(Dispatchers.IO) {
api.getOrder()
}
val user = userDeferred.await()
val order = orderDeferred.await()
showData(user, order)
}
注意:
async 返回 Deferred
await() 获取结果
3. withContext
切换协程运行的线程,并且等待结果返回。
最常用。
val result = withContext(Dispatchers.IO) {
api.getData()
}
它的特点:
切到指定线程执行
执行完再回到原来的协程上下文
可以返回结果
比如:
viewModelScope.launch {
// Main 线程
val data = withContext(Dispatchers.IO) {
// IO 线程
repository.loadData()
}
// 自动回到 Main 线程
showData(data)
}
5. 最常见 Android 写法
推荐写法
viewModelScope.launch {
// 主线程,可以控制 UI 状态
_loading.value = true
val data = withContext(Dispatchers.IO) {
// 后台线程做耗时任务
repository.getData()
}
// 回到主线程更新 UI 状态
_loading.value = false
_data.value = data
}
这个模式非常常见:
launch 默认 Main
withContext(IO) 做耗时任务
结束后回 Main 更新 UI
6. 对应关系总结
| 你想做什么 | 用什么 |
|---|---|
| 启动一个协程 | launch {} |
| 启动一个有返回值的协程 | async {} |
| 切换线程执行一段代码 | withContext(...) |
| 主线程执行 | Dispatchers.Main |
| IO 任务 | Dispatchers.IO |
| CPU 计算 | Dispatchers.Default |
| 获取 async 结果 | await() |
| 挂起等待但不阻塞线程 | delay() |
7. 默认在哪个线程,要看 Scope
这个非常重要。
viewModelScope.launch {}
viewModelScope.launch {
// 默认 Main
}
lifecycleScope.launch {}
lifecycleScope.launch {
// 默认 Main
}
CoroutineScope(Dispatchers.IO).launch {}
CoroutineScope(Dispatchers.IO).launch {
// 默认 IO
}
runBlocking {}
runBlocking {
// 阻塞当前线程
}
如果你在 main 函数里:
fun main() = runBlocking {
println(Thread.currentThread().name)
}
它就在当前 main 线程。
如果你在 Android 主线程调用 runBlocking,会阻塞主线程,所以 Android 里一般不要用。
8. suspend 是不是代表切线程?
不是。
这个特别容易混。
suspend fun getData(): String {
return "hello"
}
suspend 只表示:
这个函数可以挂起
不代表:
它一定在后台线程
也不代表:
它一定切线程
真正决定线程的是:
Dispatchers.IO
Dispatchers.Main
Dispatchers.Default
比如:
viewModelScope.launch {
getData()
}
如果 launch 在 Main 线程,那么 getData() 默认也在 Main 线程执行,除非它内部自己切了线程。
9. 举个完整例子
viewModelScope.launch {
println("1: ${Thread.currentThread().name}")
// Main
val user = withContext(Dispatchers.IO) {
println("2: ${Thread.currentThread().name}")
// IO 线程
repository.getUser()
}
println("3: ${Thread.currentThread().name}")
// 回到 Main
_user.value = user
}
执行逻辑:
1. viewModelScope.launch 默认在 Main
2. withContext(Dispatchers.IO) 切到 IO 线程
3. IO 执行完,自动回到 Main
4. 更新 UI 状态
10. 最后总结
你可以这样记:
launch:启动协程
async:启动协程并返回结果
withContext:切换调度器/线程
Dispatchers.Main:主线程
Dispatchers.IO:IO 线程池
Dispatchers.Default:计算线程池
suspend:可挂起,不代表切线程
最常用写法:
viewModelScope.launch {
// Main 线程
val result = withContext(Dispatchers.IO) {
// IO 线程做耗时任务
repository.loadData()
}
// 回到 Main 线程更新 UI
updateUi(result)
}
一句话:
协程默认跑在哪,要看启动它的 Scope;要切线程,就用
withContext(Dispatchers.xxx)或在launch/async里传入Dispatchers.xxx。