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 层直接切线程,应由数据层负责

相关推荐
晚风吹红霞1 小时前
C++ vector 深度剖析:从入门到模拟实现,避开所有坑
开发语言·c++
凯瑟琳.奥古斯特1 小时前
力扣1235完整解法详解
java·开发语言·leetcode
z落落1 小时前
C# 继承基础详解(代码实战+权限规则)
java·开发语言
techdashen1 小时前
你想在 Rust 中实现动态库热重载?
开发语言·chrome·rust
不会C语言的男孩1 小时前
C++ Primer 第5章:语句
开发语言·c++
酉鬼女又兒1 小时前
零基础入门计算机网络:从基本概念到核心交换技术
开发语言·计算机网络·考研·职场和发展·php
爱喝水的鱼丶1 小时前
SAP-ABAP:SAP 简单报表输出开发系列(共6篇)第三篇:SAP ALV 报表样式定制:字段布局与交互功能配置
服务器·开发语言·学习·交互·sap·abap
chao1898441 小时前
基于SIFT和SURF特征的图像配准(MATLAB)
开发语言·matlab
摇滚侠1 小时前
JDBC 基础到高级一套通关!基础篇 00-15
java·开发语言·数据库