关于 kotlin-concurrency-expert
kotlin-concurrency-expert 是用于审查和修复 Android 项目中 Kotlin Coroutines 问题的技能,侧重结构化并发与生命周期安全,并尽量减少行为变化。
主要功能
- 01诊断协程崩溃、泄漏、ANR 和竞态
- 02选择生命周期绑定的协程作用域
- 03将阻塞任务移至合适的调度器
- 04正确处理取消并封装 Flow 状态
- 05将回调转换为带清理的 callbackFlow
- 06注入调度器以便测试
适用场景
- 01排查 Android 协程导致的 ANR
- 02替换 GlobalScope 以避免生命周期泄漏
- 03让 Flow 收集遵循 UI 生命周期
- 04改善协程取消处理与可测试性
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ new-silvermoon/ awesome-android-agent-skills --skill kotlin-concurrency-expert安装后在 Claude Code 中输入/kotlin-concurrency-expert确认已加载。
详细介绍
Kotlin 并发专家
概述
通过应用结构化并发、生命周期安全、适当的作用域和现代最佳实践,以尽量少改变行为的方式,检查并修复 Android 代码库中的 Kotlin Coroutines 问题。
工作流
1. 梳理问题
- 记录确切的错误、崩溃或症状(ANR、内存泄漏、竞态条件、状态错误)。
- 检查项目的 Coroutines 配置:
kotlinx-coroutines-android版本、lifecycle-runtime-ktx版本。 - 确认当前的作用域上下文(
viewModelScope、lifecycleScope、自定义作用域或无)。 - 确认代码是否与 UI 绑定(
Dispatchers.Main),或是否应在主线程之外运行(Dispatchers.IO、Dispatchers.Default)。 - 检查 Dispatcher 注入模式是否便于测试。
2. 应用最小且安全的修复
优先选择既能保留现有行为,又能满足结构化并发和生命周期安全要求的修改。
常见修复:
- ANR / 主线程阻塞:将耗时操作移至
withContext(Dispatchers.IO)或Dispatchers.Default;确保挂起函数不会阻塞主线程。 - 内存泄漏 / 孤儿 Coroutines:将
GlobalScope替换为与生命周期绑定的作用域(viewModelScope、lifecycleScope或注入的applicationScope)。 - 生命周期收集问题:将已弃用的
launchWhenStarted替换为repeatOnLifecycle(Lifecycle.State.STARTED)。 - 状态暴露:封装
MutableStateFlow/MutableSharedFlow;暴露只读的StateFlow或Flow。 - 吞掉 CancellationException:确保通用的
catch (e: Exception)块重新抛出CancellationException。 - 不配合的取消:在紧密循环中添加
ensureActive()或yield(),以支持协作式取消。 - 回调 API:将监听器转换为
callbackFlow,并通过awaitClose正确清理。 - 硬编码的 Dispatchers:通过构造函数注入
CoroutineDispatcher,以便测试。
关键规则
Dispatcher 注入(可测试性)
// CORRECT: Inject dispatcher
class UserRepository(
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend fun fetchUser() = withContext(ioDispatcher) { ... }
}
// INCORRECT: Hardcoded dispatcher
class UserRepository {
suspend fun fetchUser() = withContext(Dispatchers.IO) { ... }
}
生命周期感知的收集
// CORRECT: Use repeatOnLifecycle
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state -> updateUI(state) }
}
}
// INCORRECT: Direct collection (unsafe, deprecated)
lifecycleScope.launchWhenStarted {
viewModel.uiState.collect { state -> updateUI(state) }
}
状态封装
// CORRECT: Expose read-only StateFlow
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}
// INCORRECT: Exposed mutable state
class MyViewModel : ViewModel() {
val uiState = MutableStateFlow(UiState()) // Leaks mutability
}
异常处理
// CORRECT: Rethrow CancellationException
try {
doSuspendWork()
} catch (e: CancellationException) {
throw e // Must rethrow!
} catch (e: Exception) {
handleError(e)
}
// INCORRECT: Swallows cancellation
try {
doSuspendWork()
} catch (e: Exception) {
handleError(e) // CancellationException swallowed!
}
协作式取消
// CORRECT: Check for cancellation in tight loops
suspend fun processLargeList(items: List<Item>) {
items.forEach { item ->
ensureActive() // Check cancellation
processItem(item)
}
}
// INCORRECT: Non-cooperative (ignores cancellation)
suspend fun processLargeList(items: List<Item>) {
items.forEach { item ->
processItem(item) // Never checks cancellation
}
}
回调转换
// CORRECT: callbackFlow with awaitClose
fun locationUpdates(): Flow<Location> = callbackFlow {
val listener = LocationListener { location ->
trySend(location)
}
locationManager.requestLocationUpdates(listener)
awaitClose { locationManager.removeUpdates(listener) }
}
作用域指南
| 作用域 | 使用场景 | 生命周期 |
|---|---|---|
viewModelScope | ViewModel 操作 | 随 ViewModel 一起清除 |
lifecycleScope | Activity/Fragment 中的 UI 操作 | 生命周期所有者销毁时结束 |
repeatOnLifecycle | UI 中的 Flow 收集 | 随生命周期状态启动/停止 |
applicationScope(注入) | 应用级后台工作 | 应用程序的生命周期 |
GlobalScope | 切勿使用 | 破坏结构化并发 |
测试模式
@Test
fun `loading data updates state`() = runTest {
val testDispatcher = StandardTestDispatcher(testScheduler)
val repository = FakeRepository()
val viewModel = MyViewModel(repository, testDispatcher)
viewModel.loadData()
advanceUntilIdle()
assertEquals(UiState.Success(data), viewModel.uiState.value)
}
参考资料
评论
支持 Markdown