Appearance
06_协程应用场景
目录
1. 协程基础回顾
1.1 什么是协程
协程(Coroutine)是轻量级的线程,由应用代码控制调度而非操作系统。协程可以在单个线程中实现高并发,避免了线程切换的开销。
核心概念:
- 挂起(suspend):协程执行到挂起函数时会暂停,但不阻塞线程
- 恢复:挂起完成后自动恢复执行
- 上下文(Context):协程的执行环境,包括调度器、作业、异常处理器等
1.2 基本语法
kotlin
import kotlinx.coroutines.*
// 协程构建器
lifecycleScope.launch {
// 协程代码
val result = withContext(Dispatchers.IO) {
// 后台任务
doHeavyWork()
}
// 自动切换回主线程
textView.text = result
}1.3 协程三要素
┌────────┬────────┬────────┐
│ 上下文 │ 启动模式│ 异常处理器│
│ Context│ Start │ Handler │
└────────┴────────┴────────┘kotlin
// 完整的协程启动
lifecycleScope.launch(
context = Dispatchers.Main + SupervisorJob(),
start = CoroutineStart.DEFAULT,
block = {
// 协程代码
}
)2. 协程 vs 传统线程
2.1 实现原理对比
| 特性 | 传统线程 | Kotlin 协程 |
|---|---|---|
| 实现层 | 操作系统内核 | 语言/编译器 |
| 调度方式 | 操作系统调度 | 应用代码调度 |
| 内存占用 | ~1MB/线程 | ~几 KB/协程 |
| 创建开销 | 高 | 极低 |
| 切换开销 | 高(上下文切换) | 低(仅保存状态) |
| 并发能力 | 几百个 | 几十万到百万 |
| 阻塞影响 | 阻塞整个线程 | 仅挂起当前协程 |
2.2 内存对比
线程:
┌─────────────────┐
│ 栈空间 1MB │ ← 每个线程独立
│ 本地变量 │
│ 参数 │
│ 返回地址 │
└─────────────────┘
协程:
┌───────────┐
│ 帧 几百字节│ ← 多个协程共享线程
│ 局部变量 │
│ 挂起点 │
└───────────┘2.3 性能对比
kotlin
// 线程方案
fun threadsTest() {
val threads = (1..10000).map { i ->
Thread {
doWork(i)
}.apply { start() }
}
threads.forEach { it.join() }
}
// 协程方案
suspend fun coroutinesTest() {
coroutineScope {
(1..10000).forEach { i ->
launch {
doWork(i)
}
}
}
}
// 结果对比:
// 线程:10000 个线程 ≈ 10GB 内存,创建和切换开销大
// 协程:10000 个协程 ≈ 几 MB 内存,几乎无切换开销2.4 代码对比
java
// 传统线程 + 回调
new Thread(new Runnable() {
public void run() {
try {
String data = fetchData();
handler.post(new Runnable() {
public void run() {
updateUI(data);
}
});
} catch (Exception e) {
handler.post(new Runnable() {
public void run() {
showError(e);
}
});
}
}
}).start();kotlin
// 协程
lifecycleScope.launch {
try {
val data = withContext(Dispatchers.IO) {
fetchData()
}
updateUI(data)
} catch (e: Exception) {
showError(e)
}
}3. 协程的核心优势
3.1 非阻塞异步
传统线程问题:
java
// 线程阻塞等待,浪费线程资源
String data = httpClient.get(url); // 阻塞
updateUI(data);协程解决方案:
kotlin
// 挂起等待,释放线程
suspend fun fetchData(): String = withContext(Dispatchers.IO) {
httpClient.get(url) // 挂起,不阻塞
}
lifecycleScope.launch {
val data = fetchData() // 等待但不阻塞
updateUI(data)
}3.2 结构化并发
kotlin
// 父协程管理子协程
lifecycleScope.launch {
val job1 = launch { task1() }
val job2 = launch { task2() }
val job3 = launch { task3() }
// 等待所有子协程完成
job1.join()
job2.join()
job3.join()
// 或者
coroutineScope {
launch { task1() }
launch { task2() }
launch { task3() }
// 自动等待
}
// 父协程结束,所有子协程取消
}3.3 异常处理
kotlin
// 1. try-catch 处理单个异常
lifecycleScope.launch {
try {
val result = withContext(Dispatchers.IO) {
riskyOperation()
}
} catch (e: Exception) {
handleError(e)
}
}
// 2. SupervisorJob 隔离子协程异常
lifecycleScope.launch(SupervisorJob()) {
launch {
throw RuntimeException("任务 1 失败")
}
launch {
// 不受影响,继续执行
doWork()
}
}
// 3. 全局异常处理器
class MyExceptionHandler : CoroutineExceptionHandler {
override fun handleException(context: CoroutineContext, exception: Throwable) {
Log.e("Coroutine", "异常:", exception)
}
}
// 使用
lifecycleScope.launch(MyExceptionHandler()) {
// 未捕获异常会被处理
}3.4 线程安全
kotlin
// MutableStateFlow 线程安全
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
// 从任何线程更新
lifecycleScope.launch {
_count.value++ // 线程安全
}
// 主线程观察
count.observe { value ->
textView.text = value.toString()
}3.5 生命周期感知
kotlin
// ViewModel 自动管理协程生命周期
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
// Fragment 销毁时自动取消
val data = repository.getData()
_data.value = data
}
}
}
// Activity 生命周期感知
class MyActivity : AppCompatActivity() {
fun loadData() {
lifecycleScope.launch {
// Activity 销毁时自动取消
val data = repository.getData()
updateUI(data)
}
}
}4. Android 开发中的使用场景
4.1 场景分类
┌─────────────┬─────────────┬─────────────┐
│ 网络请求 │ 数据库操作 │ 文件读写 │
├─────────────┼─────────────┼─────────────┤
│ 并发控制 │ 定时任务 │ UI 动画 │
├─────────────┼─────────────┼─────────────┤
│ 事件流 │ 状态管理 │ 依赖注入 │
└─────────────┴─────────────┴─────────────┘4.2 调度器选择
kotlin
// Dispatchers.Main - 主线程
lifecycleScope.launch(Dispatchers.Main) {
// UI 操作
textView.text = "Hello"
}
// Dispatchers.IO - IO 密集型
lifecycleScope.launch(Dispatchers.IO) {
// 网络请求
// 数据库操作
// 文件读写
val data = repository.getData()
}
// Dispatchers.Default - CPU 密集型
lifecycleScope.launch(Dispatchers.Default) {
// 复杂计算
// 图片处理
// 加密解密
val result = heavyCalculation()
}
// Dispatchers.Unconfined - 不受约束
lifecycleScope.launch(Dispatchers.Unconfined) {
// 不会阻塞,在哪个线程挂起就在哪个线程恢复
// 通常不推荐使用
}5. 网络请求场景
5.1 单请求
kotlin
// Retrofit + 协程
interface ApiService {
@GET("user/{id}")
suspend fun getUser(@Path("id") userId: Long): User
}
// ViewModel
class UserViewModel : ViewModel() {
private val apiService = RetrofitClient.api
fun loadUser(userId: Long) {
viewModelScope.launch {
try {
_loading.value = true
val user = withContext(Dispatchers.IO) {
apiService.getUser(userId)
}
_user.value = user
} catch (e: Exception) {
_error.value = e.message
} finally {
_loading.value = false
}
}
}
}5.2 多请求并发
kotlin
// 并发请求多个接口
suspend fun fetchAllData(): List<Any> {
return withContext(Dispatchers.IO) {
coroutineScope {
val userDeferred = async { apiService.getUser(1) }
val postDeferred = async { apiService.getPosts(1) }
val commentDeferred = async { apiService.getComments(1) }
listOf(
userDeferred.await(),
postDeferred.await(),
commentDeferred.await()
)
}
}
}
// 使用
viewModelScope.launch {
val data = fetchAllData()
_data.value = data
}5.3 限流并发
kotlin
// 限制并发数量
suspend fun downloadFilesLimited(urls: List<String>, limit: Int = 3) {
val semaphore = Semaphore(limit)
coroutineScope {
urls.forEach { url ->
async {
semaphore.acquire()
try {
downloadFile(url)
} finally {
semaphore.release()
}
}
}
}
}5.4 重试机制
kotlin
// 带重试的网络请求
suspend fun <T> retryableRequest(
times: Int = 3,
delay: Long = 1000,
block: suspend () -> T
): T {
var lastException: Exception? = null
repeat(times) { attempt ->
try {
return block()
} catch (e: Exception) {
lastException = e
if (attempt < times - 1) {
delay(delay * (attempt + 1)) // 指数退避
}
}
}
throw lastException!!
}
// 使用
viewModelScope.launch {
try {
val user = retryableRequest {
apiService.getUser(userId)
}
_user.value = user
} catch (e: Exception) {
_error.value = "请求失败"
}
}5.5 取消请求
kotlin
// 可取消的网络请求
class MyViewModel : ViewModel() {
private var job: Job? = null
fun loadUser(userId: Long) {
// 取消之前的请求
job?.cancel()
job = viewModelScope.launch {
try {
val user = withContext(Dispatchers.IO) {
apiService.getUser(userId)
}
_user.value = user
} catch (e: CancellationException) {
// 请求被取消
} catch (e: Exception) {
_error.value = e.message
}
}
}
override fun onCleared() {
super.onCleared()
job?.cancel()
}
}6. 数据库操作场景
6.1 Room + 协程
kotlin
// DAO 接口
@Dao
interface UserDao {
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUserById(id: Long): User
@Insert
suspend fun insertUser(user: User)
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
}
// Repository
class UserRepository(private val userDao: UserDao) {
suspend fun getAllUsers(): List<User> = userDao.getAllUsers()
suspend fun getUserById(id: Long): User = userDao.getUserById(id)
suspend fun insertUser(user: User): Long = userDao.insertUser(user)
suspend fun updateUser(user: User): Int = userDao.updateUser(user)
suspend fun deleteUser(user: User): Int = userDao.deleteUser(user)
}
// ViewModel
class UserViewModel : ViewModel() {
private val repository = UserRepository(userDao)
fun loadUsers() {
viewModelScope.launch {
_users.value = repository.getAllUsers()
}
}
}6.2 观察数据库变化
kotlin
// Flow 观察数据库变化
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun observeAllUsers(): Flow<List<User>>
}
// ViewModel
class UserViewModel : ViewModel() {
private val userDao = database.userDao()
val users: StateFlow<List<User>> = viewModelScope.viewModelScope
.repeatOnLifecycle(Lifecycle.State.STARTED) {
userDao.observeAllUsers()
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
}6.3 事务处理
kotlin
// 数据库事务
@Dao
interface UserDao {
@Transaction
suspend fun updateUserProfile(userId: Long, name: String, email: String) {
val user = getUserById(userId)
user.name = name
user.email = email
updateUser(user)
// 关联操作
updateUserHistory(userId, "更新个人信息")
}
}
// 使用
viewModelScope.launch {
try {
withContext(Dispatchers.IO) {
userDao.updateUserProfile(1, "新名字", "新邮箱")
}
_success.value = true
} catch (e: Exception) {
_error.value = e.message
}
}7. 文件读写场景
7.1 读取文件
kotlin
suspend fun readFile(filePath: String): String {
return withContext(Dispatchers.IO) {
File(filePath).readText()
}
}
// 使用
viewModelScope.launch {
try {
val content = readFile("/path/to/file.txt")
_content.value = content
} catch (e: Exception) {
_error.value = e.message
}
}7.2 写入文件
kotlin
suspend fun writeFile(filePath: String, content: String) {
withContext(Dispatchers.IO) {
File(filePath).writeText(content)
}
}
// 带进度
suspend fun writeFileWithProgress(
filePath: String,
data: ByteArray,
progress: (Int) -> Unit
) {
withContext(Dispatchers.IO) {
val file = File(filePath)
file.outputStream().use { output ->
var written = 0L
val buffer = ByteArray(8192)
var count: Int
while (data.copyOfRange(written.toInt(), minOf(written.toInt() + 8192, data.size))
.also { count = it.size }.copyInto(buffer) != 0) {
output.write(buffer, 0, count)
written += count
progress((written * 100 / data.size).toInt())
}
}
}
}7.3 文件下载
kotlin
suspend fun downloadFile(url: String, filePath: String): Long {
return withContext(Dispatchers.IO) {
val urlConnection = URL(url).openConnection() as HttpURLConnection
urlConnection.inputStream.use { input ->
File(filePath).outputStream().use { output ->
val buffer = ByteArray(8192)
var count: Int
var total = 0L
while (input.read(buffer).also { count = it } != -1) {
output.write(buffer, 0, count)
total += count
}
total
}
}
}
}
// 带进度
suspend fun downloadFileWithProgress(
url: String,
filePath: String,
progress: (Int) -> Unit
): Long {
return withContext(Dispatchers.IO) {
val connection = URL(url).openConnection() as HttpURLConnection
val totalSize = connection.contentLength
connection.inputStream.use { input ->
File(filePath).outputStream().use { output ->
val buffer = ByteArray(8192)
var count: Int
var downloaded = 0L
while (input.read(buffer).also { count = it } != -1) {
output.write(buffer, 0, count)
downloaded += count
progress((downloaded * 100 / totalSize).toInt())
}
downloaded
}
}
}
}8. 并发控制场景
8.1 并行执行
kotlin
// 并行执行多个任务
suspend fun parallelTasks() {
val results = coroutineScope {
listOf(
async { task1() },
async { task2() },
async { task3() }
).awaitAll()
}
}
// 使用场景:同时加载多个数据源
suspend fun loadDashboardData() {
val (users, posts, comments) = coroutineScope {
triple(
async { repository.getUsers() },
async { repository.getPosts() },
async { repository.getComments() }
).await()
}
_dashboardData.value = DashboardData(users, posts, comments)
}8.2 串行执行
kotlin
// 串行执行(按顺序)
suspend fun serialTasks() {
val result1 = task1()
val result2 = task2(result1)
val result3 = task3(result2)
}
// 使用场景:依赖链式操作
suspend fun processOrder(orderId: Long) {
val order = repository.getOrder(orderId)
val inventory = checkInventory(order)
val payment = processPayment(order)
val shipment = createShipment(order, inventory, payment)
}8.3 限流
kotlin
// 使用 Semaphore 限流
val semaphore = Semaphore(5)
suspend fun limitedTask(taskId: Int) {
semaphore.acquire()
try {
doWork(taskId)
} finally {
semaphore.release()
}
}
// 批量执行限流任务
suspend fun executeLimited(tasks: List<Int>) {
coroutineScope {
tasks.forEach { taskId ->
launch {
limitedTask(taskId)
}
}
}
}8.4 竞争与屏障
kotlin
// 第一个完成的协程胜出
suspend fun raceTasks(): Result {
return withContext(Dispatchers.Default) {
coroutineScope {
val job1 = async {
delay(1000)
Result("任务 1 完成")
}
val job2 = async {
delay(500)
Result("任务 2 完成")
}
// 等待第一个完成
awaitFirst(job1, job2)
}
}
}
// 所有线程在屏障处等待
suspend fun cyclicBarrierTasks() {
val barrier = CyclicBarrier(3) {
// 所有线程到达后执行
Log.d("Barrier", "所有线程到达")
}
coroutineScope {
repeat(3) { i ->
launch {
delay(i * 100)
Log.d("Task", "任务 $i 到达")
barrier.await()
Log.d("Task", "任务 $i 继续")
}
}
}
}9. 定时任务场景
9.1 周期性任务
kotlin
// 使用 flow 实现周期性任务
fun periodicTask(interval: Long = 1000): Flow<Unit> = flow {
while (true) {
emit(Unit)
delay(interval)
}
}.shareIn(
scope = lifecycleScope,
started = SharingStarted.WhileSubscribed(5000)
)
// 使用
lifecycleScope.launch {
periodicTask(1000).collect {
refreshData()
}
}9.2 延迟执行
kotlin
// 延迟执行
lifecycleScope.launch {
delay(3000)
doSomething()
}
// 可取消的延迟
lifecycleScope.launch {
try {
delay(3000)
doSomething()
} catch (e: CancellationException) {
// 被取消
}
}9.3 定时调度
kotlin
// 模拟 ScheduledExecutorService
fun periodicWork(initialDelay: Long, period: Long, block: suspend () -> Unit) {
viewModelScope.launch {
delay(initialDelay)
while (isActive) {
block()
delay(period)
}
}
}
// 使用
periodicWork(1000, 5000) {
refreshData()
}10. 对比分析与选型
10.1 场景选型表
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单异步 | 协程 | 代码简洁 |
| 复杂数据流 | RxJava/Flow | 强大的转换能力 |
| UI 动画 | 协程 + flow | 易控制 |
| 网络请求 | 协程 + Retrofit | 集成好 |
| 数据库 | 协程 + Room | 官方支持 |
| 定时任务 | 协程 | 易取消 |
| 并发控制 | 协程 + Semaphore | 灵活 |
10.2 协程 vs 协程工具
kotlin
// 1. launch - 无返回值
lifecycleScope.launch {
// 执行任务
doWork()
}
// 2. async - 有返回值
val deferred = lifecycleScope.async {
return@async doWork()
}
val result = deferred.await()
// 3. flow - 流式数据
val flow = flow {
emit(1)
emit(2)
emit(3)
}
// 4. channel - 通道
val channel = Channel<Int>()10.3 StateFlow vs SharedFlow vs Flow
kotlin
// Flow - 冷流
val flow = flow {
emit(1)
emit(2)
}
// StateFlow - 有状态的冷流
val stateFlow = MutableStateFlow(0)
// SharedFlow - 热流
val sharedFlow = MutableSharedFlow<Int>()10.4 选型建议
┌─────────────────┬──────────────────┐
│ 场景 │ 选型 │
├─────────────────┼──────────────────┤
│ 单次异步操作 │ launch │
│ 需要返回值 │ async │
│ 状态管理 │ StateFlow │
│ 事件广播 │ SharedFlow │
│ 数据处理 │ Flow │
│ 通道通信 │ Channel │
└─────────────────┴──────────────────┘11. 面试高频考点
考点一:协程原理
Q: 协程是怎么实现的?
A:
- 使用状态机 + 挂起/恢复
- 编译器转换(suspend 函数)
- 基于 Continuation 实现
考点二:调度器
Q: Dispatchers 有哪些?区别?
A:
- Main:主线程,UI 操作
- IO:IO 密集型,数据库/网络
- Default:CPU 密集型,默认线程池
- Unconfined:不受约束,不推荐
考点三:结构化并发
Q: 什么是结构化并发?
A:
- 父协程管理子协程
- 子协程异常传播到父协程
- 父协程取消,子协程一起取消
考点四:协程取消
Q: 协程如何取消?
A:
- job.cancel()
- 抛出 CancellationException
- 检查 isActive
考点五:异常处理
Q: 协程异常如何处理?
A:
- try-catch
- SupervisorJob
- CoroutineExceptionHandler
考点六:挂起函数
Q: 什么是挂起函数?
A:
- 标记为 suspend 的函数
- 可以挂起而不阻塞线程
- 只能在协程或挂起函数中调用
考点七:Flow 使用
Q: Flow 是什么?怎么用?
A:
- 冷流,按需执行
- 支持操作符转换
- collect 收集数据
考点八:状态管理
Q: StateFlow vs LiveData?
A:
- StateFlow 有初始值
- StateFlow 可热订阅
- LiveData 生命周期感知
- StateFlow 更现代
考点九:并发控制
Q: 如何控制协程并发数?
A:
- Semaphore
- Dispatchers
- coroutineScope
考点十:内存泄漏
Q: 协程如何避免内存泄漏?
A:
- 使用 lifecycleScope
- 使用 viewModelScope
- 正确取消协程
最佳实践总结
1. 选择合适的 Scope
kotlin
// ViewModel
viewModelScope.launch { }
// Activity/Fragment
lifecycleScope.launch { }2. 正确选择调度器
kotlin
// IO 任务
withContext(Dispatchers.IO) { }
// CPU 任务
withContext(Dispatchers.Default) { }3. 异常处理
kotlin
try {
doWork()
} catch (e: Exception) {
handleError(e)
}4. 取消管理
kotlin
job?.cancel()
viewModelScope.coroutineContext[Job]?.cancelChildren()5. 使用状态流
kotlin
private val _state = MutableStateFlow(State.Initial)
val state: StateFlow<State> = _state.asStateFlow()总结: 协程是现代 Android 开发的核心技术。掌握协程的各种应用场景和最佳实践,能够编写更高效、更安全的代码。在实际项目中,应根据具体场景选择合适的协程构建器和工具。