Kotlin协程序使用技巧和应用场景

目录

一、使用技巧与最佳实践

[1. 作用域选择(避免内存泄漏)](#1. 作用域选择(避免内存泄漏))

[2. 调度器 Dispatchers 正确选择](#2. 调度器 Dispatchers 正确选择)

[3. 线程切换用 withContext,并发用 async](#3. 线程切换用 withContext,并发用 async)

[4. 异常处理](#4. 异常处理)

[5. 超时与取消](#5. 超时与取消)

[6. Repository 层设计原则](#6. Repository 层设计原则)

二、常见应用场景

三、常见坑提醒


一、使用技巧与最佳实践

1. 作用域选择(避免内存泄漏)

永远使用生命周期感知的作用域 ,禁止在业务代码中使用 GlobalScope(其生命周期与进程一致,不会自动取消)。

作用域 绑定对象 取消时机 适用场景
viewModelScope ViewModel onCleared() ViewModel 中发起网络/数据库请求
lifecycleScope Activity/Fragment onDestroy() UI 相关异步(倒计时、动画、Toast)
rememberCoroutineScope() Compose 组合 离开 Composition Compose 点击事件内启动协程

Kotlin 协程(Coroutines)是 Android 异步编程的首选方案

2. 调度器 Dispatchers 正确选择

  • Dispatchers.Main​ --- UI 线程,更新 View、LiveData/StateFlow

  • Dispatchers.IO​ --- 网络请求(Retrofit)、Room 数据库、文件读写

  • Dispatchers.Default​ --- CPU 密集型(JSON 解析、排序、加解密)

  • Dispatchers.Main.immediate​ --- 已在主线程时避免多余消息队列投递

⚠️ Room 和 Retrofit 的 suspend函数内部已自动切到 IO,Repository 层用 withContext(Dispatchers.IO)包裹即可,调用方无需再切。

3. 线程切换用 withContext,并发用 async

Kotlin 复制代码
// 顺序切换(最常用)
viewModelScope.launch {
    val data = withContext(Dispatchers.IO) { api.fetch() }  // IO
    _state.value = data                                   // 自动回 Main
}

// 并行请求(加速页面加载)
viewModelScope.launch {
    val userDeferred = async { api.getUser() }
    val feedDeferred = async { api.getFeed() }
    show(userDeferred.await(), feedDeferred.await())
}
  • launch→ 不返回结果(fire-and-forget)
  • async→ 返回 Deferred<T>,通过 await()取结果
  • 优先用 withContext而非 async{...}.await()做单纯线程切换

4. 异常处理

Kotlin 复制代码
// 方式一:try-catch(推荐用于业务逻辑)
viewModelScope.launch {
    try {
        _state.value = withContext(Dispatchers.IO) { api.fetch() }
    } catch (e: IOException) {
        _state.value = UiState.Error(e.message)
    }
}

// 方式二:CoroutineExceptionHandler(顶层兜底)
val handler = CoroutineExceptionHandler { _, e -> Log.e("TAG", e.toString()) }
viewModelScope.launch(handler) { /* ... */ }

supervisorScope可让子协程异常互不传播,适合多个独立任务并行。

5. 超时与取消

Kotlin 复制代码
viewModelScope.launch {
    try {
        withTimeout(5000) {  // 5秒超时自动取消
            val data = api.fetchSlow()
            _state.value = data
        }
    } catch (e: TimeoutCancellationException) {
        _state.value = UiState.Timeout
    }
}

协程取消是协作式 的,长时间计算循环中用 ensureActive()或 isActive检查取消。

6. Repository 层设计原则

suspend函数应主线程安全------内部自己切 Dispatchers.IO,ViewModel 直接调用无需关心线程:

Kotlin 复制代码
suspend fun getNews(): List<News> = withContext(Dispatchers.IO) {
    api.fetchNews().also { db.newsDao().insert(it) }
}

二、常见应用场景

场景 做法
网络请求 + 刷新 UI​ viewModelScope.launch + withContext(IO)
Room 数据库增删改查​ DAO 声明 suspend,调用时 withContext(IO)或直接 collect Flow
**并行接口(用户信息+列表)**​ async { ... } + await()两个请求并发执行
倒计时/轮询/动画​ lifecycleScope.launch { while(isActive) { delay(1000); tick() } }
Flow 数据观察​ flow.collectAsStateWithLifecycle()在 Compose,flow.onEach{}.launchIn(viewModelScope)在传统 View
退出页面仍需完成的任务(如日志上报、文件上传) 使用 Application 级自定义 CoroutineScope(SupervisorJob()+IO),不依赖 ViewModel
防重复点击​ 配合 debounce(Flow)或在点击时用 isActive/job?.isActive判断

三、常见坑提醒

  • 不要用 GlobalScope.launch做 UI 相关业务

  • 不要在 Dispatchers.Default做阻塞 IO(占满 CPU 线程池)

  • 不要在协程里用 Thread.sleep(),改用 delay()

  • 不要把 suspend函数写在 UI 层直接切线程,应由数据层负责

相关推荐
2601_966949652 小时前
如何根据研究的起止时间截取分钟级行情?Python 实战与 QuantDash 数据处理指南
开发语言·python·pandas·量化交易·1024程序员节·股票数据·quantdash
晨曦中的暮雨4 小时前
限流算法——固定窗口、滑动窗口、漏桶、令牌桶
java·开发语言·算法
喵了几个咪5 小时前
GoWind CMS 拆解:在 Go 里给 TipTap 搭一套 Yjs 协同后端
开发语言·后端·golang
程序员清风5 小时前
数据处理脚本工程化:日志、配置、命令行与测试
服务器·开发语言·数据库
2601_962885727 小时前
如何用 Python 把 K 线重采样成任意周期?
开发语言·python
独隅7 小时前
Claude API 实战教程:从入门到完整项目
android·java·数据库·claude·claude code
Wang's Blog7 小时前
Java 项目实战: 交友项目 - 圈子功能实现:总结
java·开发语言·交友
下一站春风里8 小时前
位运算完整学习笔记:按位与(&)+ 异或(^)
开发语言·c++·算法
天外飞雨道沧桑9 小时前
我在浏览器里练了一个 792 参数的大模型,它学会了补全 JavaScript
开发语言·javascript·ai·ecmascript·ai编程
hhhjhl9 小时前
Yup 跨端三方库仓颉适配全流程
java·开发语言