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

相关推荐
隐积29 分钟前
3.1.7.Qt容器大家族(3):QVariant——万能盒子
开发语言·qt
名字还没想好☜3 小时前
Next.js ‘use client‘ 到底加在哪:Server/Client Components 边界与常见报错
开发语言·前端·javascript·react·next.js
初级代码游戏4 小时前
iOS开发 Swift 速记1:变量和基本数据类型
开发语言·ios·swift
红尘伴蝶舞5 小时前
Android 17 局域网调试与 ACCESS_LOCAL_NETWORK 权限隔离实践
kotlin·测试·设计
酷在前行5 小时前
【R生态】PERMANOVA 进阶实战:距离选择、PERMDISP、两两比较与受限置换(保姆级教程)
开发语言·r语言
cxr8285 小时前
Graphify vs GitNexus vs CodeGraph — 三工具架构深度对比
开发语言·架构·知识图谱
废弃的小码农6 小时前
功能测试--Day07--Python编程基础
开发语言·python
fengci.7 小时前
Microweber CMS 未授权路径穿越漏洞(CVE-2026-65694)
android·开发语言·前端·学习·php
程序员zgh7 小时前
C++ 拷贝赋值运算符 详解
c语言·开发语言·c++
念何架构之路7 小时前
restartmanager-重启管理子系统
java·开发语言