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

相关推荐
通玄31 分钟前
Jetpack Compose 入门系列(六):Navigation 3 页面导航
android
rocpp3 小时前
Android 多语言切换实战:从 Context 到 Android 13 应用语言适配
android·kotlin
释然小师弟4 小时前
Android开发十年:反思与回顾
android·后端·嵌入式
黄林晴6 小时前
用了这么久 Koin Scope,原来一直都用错了?
android·kotlin
爱勇宝19 小时前
我做了一个只用来搜歌词的小 App
android·前端·后端
众少成多积小致巨1 天前
JNI (Java Native Interface) 技术手册中文参考指南
android·java·c++
唐青枫1 天前
Kotlin Context Parameters 详解:别再把 Logger、事务和配置层层往下传
kotlin
Coffeeee1 天前
如何使用Glide和Coil加载WebP动图
android·kotlin·glide
Kapaseker1 天前
5 分钟搞懂 Kotlin DSL
android·kotlin
恋猫de小郭1 天前
AI Agent 开发究竟是啥?如何用 AI 开发 Agent ?深入浅出给你一套概念
android·前端·ai编程