Kotlin 协程与挂起函数(Coroutines & suspend)入门到实战

Kotlin 协程与挂起函数(Coroutines & suspend)入门到实战

Kotlin 协程是 Android 和后端 Kotlin 开发里最核心的异步方案之一。

很多人第一次学协程时会卡在几个地方:

  • suspend 到底是什么?
  • 协程是不是线程?
  • 为什么不会阻塞?
  • launchasyncwithContext 有什么区别?
  • Android 项目到底该怎么用?

这篇教程按**"从能看懂 → 能写 → 能实战"**的方式讲。


一、为什么需要协程?

先看传统代码:

kotlin 复制代码
fun downloadData() {
    Thread {
        Thread.sleep(3000)
        runOnUiThread {
            textView.text = "下载完成"
        }
    }.start()
}

问题:

  • 回调嵌套
  • 线程管理麻烦
  • 容易内存泄漏
  • 异步代码像"地狱"

协程的写法:

kotlin 复制代码
lifecycleScope.launch {
    delay(3000)
    textView.text = "下载完成"
}

是不是像同步代码?但它实际上是异步的

这就是协程最大的意义:

用同步代码的写法,完成异步操作。


二、什么是协程(Coroutine)

协程可以理解为:"轻量级线程"

但它不是线程

线程 vs 协程

对比 线程 协程
系统级
创建成本 极低
切换成本 很低
数量 可以很多
阻塞 容易 默认非阻塞
依赖关系 线程包含协程 协程运行在线程上

比如:

kotlin 复制代码
repeat(100000) {
    launch {
        delay(1000)
    }
}

10万个协程都没问题。但10万个线程直接炸。


三、挂起函数 suspend 到底是什么?

这是最关键的地方。

1. suspend 不是异步

很多人误解:

kotlin 复制代码
suspend fun test()

自动开线程
自动异步

suspend 的真正含义:

这个函数可以"暂停"而不阻塞线程。

2. 什么叫"挂起"?

kotlin 复制代码
suspend fun loadData() {
    delay(3000)
    println("完成")
}

这里 delay(3000) 会:

  • 暂停当前协程
  • 释放线程
  • 3秒后恢复

注意:线程没有被卡死。

3. Thread.sleep 和 delay 的区别

kotlin 复制代码
// Thread.sleep
Thread.sleep(3000)
// 特点:阻塞线程,什么都干不了

// delay
delay(3000)
// 特点:只暂停协程,不阻塞线程

直观理解

假设:

  • 线程 = 厨房
  • 协程 = 厨师
Thread.sleep delay
厨师睡觉 厨师说:"3秒后叫我"
厨房也废了 厨房还能给别人做饭

这就是协程高性能的核心。


四、协程的基本使用

先添加依赖:

kotlin 复制代码
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"

五、launch:启动协程

kotlin 复制代码
GlobalScope.launch {
    delay(1000)
    println("协程执行")
}
launch 特点 说明
启动协程 无返回值
异步执行 类似 new Thread(),但更轻量

六、runBlocking(学习阶段使用)

kotlin 复制代码
fun main() = runBlocking {
    launch {
        delay(1000)
        println("协程")
    }
    println("开始")
}

输出:

复制代码
开始
协程

runBlocking 是什么?

  • 作用: 阻塞当前线程,直到内部协程结束
  • 适合: 学习、测试
  • 不适合: Android 主线程

七、async 与 await

需要返回值时:

kotlin 复制代码
runBlocking {
    val result = async {
        delay(2000)
        "请求成功"
    }
    println(result.await())
}
async 特点 说明
有返回值 返回 Deferred
await() 等待结果

八、协程调度器 Dispatcher

协程运行在哪个线程?由 Dispatcher 决定。


九、常见 Dispatcher

1. Main

  • 主线程
  • Dispatchers.Main
  • 用于: 更新UI

2. IO

  • IO线程池
  • Dispatchers.IO
  • 用于: 网络、数据库、文件

3. Default

  • CPU密集型
  • Dispatchers.Default
  • 用于: 排序、JSON解析、大计算

十、withContext:线程切换

最常用。

kotlin 复制代码
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) {
        // 网络请求
        "服务器数据"
    }
    textView.text = result
}

执行流程:

复制代码
主线程:launch
    ↓
切换IO线程:withContext(IO)
    ↓
执行完成
    ↓
自动回主线程

十一、协程作用域 CoroutineScope

协程必须运行在作用域里。


十二、GlobalScope 为什么不推荐?

kotlin 复制代码
GlobalScope.launch { }

问题:

  • 生命周期不可控
  • 容易内存泄漏
  • Activity销毁还在运行

所以: Android开发基本不用。


十三、Android 正确写法

lifecycleScope

kotlin 复制代码
// Activity
lifecycleScope.launch { }

// Fragment
viewLifecycleOwner.lifecycleScope.launch { }

特点: 页面销毁自动取消协程


十四、ViewModel 中使用

kotlin 复制代码
class MainViewModel : ViewModel() {
    fun load() {
        viewModelScope.launch {
            val data = withContext(Dispatchers.IO) {
                api.getData()
            }
        }
    }
}

这是 Android 官方推荐方案


十五、协程取消机制

kotlin 复制代码
val job = launch {
    repeat(100) {
        delay(1000)
        println(it)
    }
}

job.cancel()

为什么协程能取消? 因为 delay() 会检查取消状态。


十六、Job

每个协程都有 Job。

kotlin 复制代码
val job = launch { }

作用: canceljoin、管理生命周期


十七、join()

等待协程结束:

kotlin 复制代码
val job = launch {
    delay(2000)
}
job.join()
println("结束")

十八、异常处理

try-catch

kotlin 复制代码
launch {
    try {
        val data = api.load()
    } catch (e: Exception) {
        // 处理异常
    }
}

十九、SupervisorJob

普通情况下: 一个子协程崩了,全部取消。

SupervisorJob:

kotlin 复制代码
val scope = CoroutineScope(
    SupervisorJob() + Dispatchers.Main
)

特点: 一个失败,不影响其他协程。Android 很常用。


二十、协程常见面试题

1. suspend 和 coroutineScope 区别?

  • suspend --- 只是说明函数可挂起
  • coroutineScope --- 会创建协程作用域

2. launch 和 async 区别?

launch async
无返回值 有返回值
返回 Job 返回 Deferred

3. delay 为什么不卡线程?

因为:

  • 它会挂起协程
  • 不阻塞线程

4. 协程是不是线程?

不是。 协程运行在线程上。


二十一、Android 实战案例

场景1:网络请求

kotlin 复制代码
viewModelScope.launch {
    try {
        val data = withContext(Dispatchers.IO) {
            api.getUser()
        }
        tvName.text = data.name
    } catch (e: Exception) {
        toast("请求失败")
    }
}

场景2:并发请求

kotlin 复制代码
viewModelScope.launch {
    val userTask = async(Dispatchers.IO) {
        api.getUser()
    }
    val videoTask = async(Dispatchers.IO) {
        api.getVideo()
    }

    val user = userTask.await()
    val video = videoTask.await()
}

优势: 两个请求同时执行。

场景3:倒计时

kotlin 复制代码
lifecycleScope.launch {
    for (i in 10 downTo 0) {
        tv.text = "$i"
        delay(1000)
    }
}

二十二、Flow 与协程关系

很多人混淆。

协程 Flow
解决:一个异步任务 解决:连续的数据流

比如: 搜索输入、股票数据、聊天消息、Room数据库监听

简单例子:

kotlin 复制代码
flow {
    emit(1)
    delay(1000)
    emit(2)
}

二十三、协程学习路线(推荐)

建议顺序:

  1. launch
  2. suspend
  3. delay
  4. async / await
  5. Dispatcher
  6. withContext
  7. lifecycleScope
  8. viewModelScope
  9. Job
  10. Flow

二十四、协程核心理解(最重要)

suspend = 可以暂停协程,但不会阻塞线程

你就已经超过很多只会背API的人了。


二十五、实际开发最佳实践

1. 不要用 GlobalScope

改用:

  • viewModelScope
  • lifecycleScope

2. IO任务放 Dispatchers.IO

kotlin 复制代码
withContext(Dispatchers.IO)

3. UI更新必须 Main线程

kotlin 复制代码
Dispatchers.Main

4. Repository 不要持有 Scope

错误:

kotlin 复制代码
class Repository {
    val scope = CoroutineScope(...)
}

容易泄漏。


二十六、完整 Android MVVM 示例

ViewModel

kotlin 复制代码
class UserViewModel : ViewModel() {
    val userLiveData = MutableLiveData<User>()

    fun loadUser() {
        viewModelScope.launch {
            val user = withContext(Dispatchers.IO) {
                api.getUser()
            }
            userLiveData.value = user
        }
    }
}

Activity

kotlin 复制代码
viewModel.userLiveData.observe(this) {
    tvName.text = it.name
}

二十七、总结

核心 作用
suspend 挂起函数
launch 启动协程
async 异步返回值
delay 非阻塞等待
withContext 切线程
Dispatcher 指定线程
viewModelScope Android推荐作用域
Flow 数据流

最后一段(真正理解协程)

很多人学协程,只会背:

  • launch
  • async
  • withContext

但真正重要的是:

协程的本质不是"开线程"。

而是:用极低成本管理大量异步任务。

相关推荐
y = xⁿ1 小时前
Java并发八股学习日记
java·开发语言·学习
xifangge20252 小时前
【深度排障】从 OS 底层寻址剖析 javac 不是内部或外部命令 核心报错:变量空间隔离与自动化部署终极范式
java·开发语言·jdk·自动化
肖恩想要年薪百万2 小时前
JSP中常用JSTL标签
java·开发语言·状态模式
l1t2 小时前
在aarch64机器上安装clang来生成codonjit python模块
开发语言·python
谙弆悕博士2 小时前
快速学C语言——第19章:C语言常用开发库
c语言·开发语言·算法·业界资讯·常用函数
月落归舟2 小时前
深入解析Java基础之基础
java·开发语言
折哥的程序人生 · 物流技术专研2 小时前
《Java 100 天进阶之路》第20篇:Java初始化、构造器、对象创建的过程
java·开发语言·后端·面试
南宫萧幕2 小时前
基于 Simulink 与 Python 联合仿真的 eVTOL 强化学习全链路实战
开发语言·人工智能·python·算法·机器学习·控制
csbysj20202 小时前
Perl 运算符
开发语言