mcpField
K
kotlin-concurrency-expert

kotlin-concurrency-expert

github.com/new-silvermoon开发992661 次安装13 周前更新
代码仓库

关于 kotlin-concurrency-expert

kotlin-concurrency-expert 是用于审查和修复 Android 项目中 Kotlin Coroutines 问题的技能,侧重结构化并发与生命周期安全,并尽量减少行为变化。

主要功能

  1. 01诊断协程崩溃、泄漏、ANR 和竞态
  2. 02选择生命周期绑定的协程作用域
  3. 03将阻塞任务移至合适的调度器
  4. 04正确处理取消并封装 Flow 状态
  5. 05将回调转换为带清理的 callbackFlow
  6. 06注入调度器以便测试

适用场景

  1. 01排查 Android 协程导致的 ANR
  2. 02替换 GlobalScope 以避免生命周期泄漏
  3. 03让 Flow 收集遵循 UI 生命周期
  4. 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) }
}

作用域指南

作用域使用场景生命周期
viewModelScopeViewModel 操作随 ViewModel 一起清除
lifecycleScopeActivity/Fragment 中的 UI 操作生命周期所有者销毁时结束
repeatOnLifecycleUI 中的 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