Jetpack Compose 中 Kotlin 协程的使用
这篇文章系统介绍了Jetpack Compose中协程的使用方法和最佳实践。主要内容包括:协程在Compose中的必要性、作用域体系、专属API全景图、典型业务场景实战、生命周期管理、性能优化技巧和常见陷阱解决方案。文章通过对比不同作用域的生命周期,详细讲解了ViewModel层和UI层的协程使用,并提供了数据加载、Flow订阅、副作用处理等实用代码示例。最后强调了不可变数据、单一数据源等优化原则
本文将带你系统性地理解 Jetpack Compose 中协程的使用场景、API 设计理念、生命周期对齐、性能优化与常见误区,助你写出既响应迅速又稳定可靠的现代 Android UI 代码。
1. 为什么在 Compose 中离不开协程
传统 View 系统就离不开异步:网络请求、数据库 I/O、动画……
进入 声明式 UI 时代,异步需求不仅没减少,反而更复杂——状态驱动让数据变化更加频繁。协程凭借以下优势成为 Compose 首选:
- 语法简洁:顺序式写法避免回调地狱。
- 可取消:可与生命周期绑定,避免内存泄漏。
- 原生支持 Flow:轻松实现事件流、实时数据。
- 跨平台:与 KMP、后台服务等自然衔接。
2. 协程作用域体系:从全局到 Composable
|
作用域 |
生命周期 |
典型用途 |
取消时机 |
|
|
进程级 |
一般不推荐 |
APP 进程被杀 |
|
|
ViewModel 存活 |
网络 / 数据库 / 计算 |
|
|
|
Activity / Fragment 存活 |
界面级别任务 |
|
|
|
Composable 存活 |
点击事件、手势动画 |
该 Composable 离开树 |
|
|
key 组合体 |
副作用、初始化 |
key 改变或离开树 |
理解这张“层级金字塔”,才能在合适的位置 launch、在正确的时机 cancel。
3. Compose 专属协程 API 全景图
┌─────────┐
┌────────────┐ │Snapshot │
│ Flow / │ → │ Flow │
│LiveData/...│ │ collect │
└────────────┘ └─────┬───┘
▼
┌─────────────────────────────────┐
│ collectAsState()/withLifecycle()│
└─────────────────────────────────┘
▼
State<T> → Recomposition
核心 API 速查:
|
API |
何时用 |
作用 |
|
|
把 Flow → State |
自动订阅/取消 |
|
|
同上,且感知生命周期 |
后台暂停收集 |
|
|
协程产出单一 State |
比 |
|
|
组合副作用 |
key 变重启协程 |
|
|
临时协程 |
点击、拖拽 |
|
|
挂起获取最新 lambda |
解决闭包捕获旧值 |
|
|
Compose State → Flow |
可用 Flow 操作符 |
4. 典型业务场景实战
4.1 数据加载与 UI 状态管理
// 1) ViewModel 层:业务 + 协程
class ArticleViewModel(private val repo: ArticleRepo) : ViewModel() {
private val _uiState = MutableStateFlow(ArticleUiState())
val uiState: StateFlow<ArticleUiState> = _uiState
fun refresh() = viewModelScope.launch {
_uiState.update { it.copy(loading = true) }
runCatching { repo.fetchArticles() }
.onSuccess { list ->
_uiState.update { it.copy(loading = false, articles = list) }
}
.onFailure { e ->
_uiState.update { it.copy(loading = false, error = e) }
}
}
}
// 2) Composable 层:声明式展示
@Composable
fun ArticleScreen(vm: ArticleViewModel = viewModel()) {
val state by vm.uiState.collectAsStateWithLifecycle()
when {
state.loading -> CircularProgressIndicator()
state.error -> ErrorView(state.error)
else -> ArticleList(state.articles)
}
}
4.2 实时 Flow 订阅
// Flow -> State,一行搞定
val stepsToday by stepCounter.stepsFlow
.collectAsStateWithLifecycle(initial = 0)
Text("今日步数:$stepsToday")
4.3 组合副作用:LaunchedEffect
@Composable
fun ToastOnSaved(isSaved: Boolean) {
val ctx = LocalContext.current
LaunchedEffect(isSaved) {
if (isSaved) {
Toast.makeText(ctx, "已保存", Toast.LENGTH_SHORT).show()
}
}
}
isSaved 从 false→true 时触发,之后值不变协程不再重启。
4.4 动画 / 定时器
@Composable
fun Countdown(seconds: Int, onFinish: () -> Unit) {
var left by remember { mutableStateOf(seconds) }
LaunchedEffect(left) {
if (left > 0) {
delay(1000)
left--
} else onFinish()
}
Text("倒计时:$left s")
}
5. 生命周期与取消:让协程不再“内存泄漏”
- ViewModel 级:
viewModelScope中协程在onCleared()统一cancel()。 - Composable 级:
-
rememberCoroutineScope跟随当前组合;远离树自动取消。LaunchedEffect在 key 改变或 Composable 消失时取消旧协程并启动新协程。
- Flow ↔ Lifecycle:
collectAsStateWithLifecycle()利用repeatOnLifecycle(Lifecycle.State.STARTED),APP 进入后台就暂停流收集,省电省资源。
6. 性能优化与最佳实践
- 单一数据源:ViewModel 只输出一个
UiState,UI 纯粹展示,避免多处 launch 写数据引发竞态。 - 不可变数据类:借助
data class copy(),让 Compose 精准比较,减少无谓重组。 derivedStateOf:将纯计算转移出 recomposition-loop,避免昂贵运算反复执行。snapshotFlow+ debounce:监听滚动位置、输入框文本等热更新,使用流操作符抑制频繁刷新。- 捕获最新 lambda:长循环/回调需
rememberUpdatedState(),否则引用旧 UI。
7. 常见陷阱与排错指南
|
症状 |
根因 |
解决方案 |
|
页面关闭后协程仍跑 |
用 |
换 |
|
同一个 Flow 死循环收集 |
每次重组都 |
用 |
|
“变量值不更新” |
协程捕获旧值 |
|
|
点击按钮触发两次事件 |
Button & IconButton 各有 click |
检查 Composable 嵌套,或用 |
|
手机锁屏耗电 |
后台仍在收集 Flow |
|
8. 参考资料与延伸阅读
- 官方文档
- 博文 / 视频
-
- Google Codelab:Advanced State and Side effects in Compose
- Gabor Varadi - “The definitive guide to launching coroutines in Compose”
- 书籍
-
- 《Jetpack Compose 关键技术解析》
- 《Kotlin 协程设计模式》
结语
Jetpack Compose 与 Kotlin 协程的结合,既让 UI 编写回归“所见即所得”的直觉,又通过可取消、可组合的异步模型保证性能与稳定。
更多推荐



所有评论(0)