拒绝重复造轮子:我写了一个生产级的 Kotlin 协程与 Flow 工具库(CoroutineKit)

0 阅读3分钟

拒绝重复造轮子:我写了一个生产级的 Kotlin 协程与 Flow 工具库(CoroutineKit)

在日常的 Android 和 JVM 开发中,我们几乎每天都在和 Kotlin 协程(Coroutines)和 Flow 打交道。

虽然 Kotlin 协程官方已经提供了非常强大的基础能力,但在实际业务开发中,我们依然会频繁遇到一些“痛点”:

  • 防抖节流:UI 上的连续点击需要做 throttleFirst,官方 Flow 并没有直接提供便捷的操作符。
  • 异常吞噬:使用 runCatching 时不小心把 CancellationException 给捕获并吞掉了,导致协程取消机制失效。
  • 重试策略:网络请求失败后的重试(指数退避、固定延迟、特定异常过滤)每次都要写一堆重复的 try-catch 和循环。
  • 生命周期管理:在普通的 Repository 或后台服务中,需要一个带有 SupervisorJob 且支持 Closeable 的安全 CoroutineScope
  • Java 互操作:需要把挂起函数转成 CompletableFuture,或者把传统的监听器/回调(Callback)安全地转成 Flow

为了在各个项目中复用这些沉淀下来的最佳实践,我开源了一个轻量级、生产级的 Kotlin 协程工具库 —— CoroutineKit

今天这篇文章,就带大家快速盘点一下 CoroutineKit 的核心功能以及它是如何帮我们优雅解决这些日常开发痛点的。


📦 快速引入

目前项目已发布 0.1.0 版本,支持通过 Maven Central 或 JitPack 引入:

dependencies {
    implementation("com.yangcyzhang:coroutinekit:0.1.0")
}

🚀 核心功能亮点与使用指南

1. 安全的 suspendRunCatching —— 告别协程取消失效

在标准库中,runCatching 会捕获 Throwable。如果不小心在协程中包裹了它,CancellationException(协程取消的信号)也会被捕获,导致协程无法正常取消。

CoroutineKit 提供的 suspendRunCatching 内部严格放行了 CancellationException

val result: Result<User> = suspendRunCatching {
    userRepository.fetchUser(id)
}

result.onSuccess { user -> render(user) }
      .onFailure { e -> showError(e) }

2. 带超时兜底的 withTimeoutOrDefault

在进行某些非核心数据的网络请求时,如果超时我们希望直接返回一个默认值而不是抛出异常阻断流程:

val data = withTimeoutOrDefault(timeMillis = 3_000, defaultValue = emptyList()) {
    networkService.fetchFeed()
}

3. UI 按钮防抖利器 —— throttleFirst

针对频繁点击、重复提交等场景,Flow 扩展了 throttleFirst

buttonClickFlow
    .throttleFirst(windowDuration = 500) // 500ms 内只响应第一次点击
    .onEach { handleClick() }
    .launchIn(viewModelScope)

4. 强大的带延迟重试 —— retryWithDelay

支持指数退避(Exponential Backoff)策略的 Flow 重试操作符:

apiFlow
    .retryWithDelay(
        times = 3, 
        initialDelay = 200, 
        factor = 2.0,
        predicate = { it is IOException } // 只在 IO 异常时重试
    )
    .collect { result -> process(result) }

5. 灵活的挂起函数重试策略 —— RetryPolicy

不仅 Flow 有重试,针对普通的挂起函数,CoroutineKit 提供了统一的 RetryPolicy(支持 Fixed 固定延迟、ExponentialBackoff 指数退避、Immediate 立即重试):

val result = withRetry(
    policy = RetryPolicy.ExponentialBackoff(times = 4, initialDelayMs = 100),
    predicate = { it is NetworkException }
) { attempt ->
    println("Attempt $attempt")
    apiCall()
}

6. 生命周期安全的 ManagedScope

如果你需要在普通类(如 Repository、Manager)中管理后台协程,可以使用 ManagedScope。它集成了 SupervisorJob 并实现了 Closeable 接口:

class MyRepository : Closeable {
    private val scope = ManagedScope(Dispatchers.IO)

    fn startSync() = scope.launch {
        while (isActive) {
            sync()
            delay(30_000)
        }
    }

    override fun close() = scope.close() // 一键安全取消所有子协程
}

7. Java 互操作桥梁 (JavaInterop)

  • 挂起函数转 CompletableFuture:方便 Java 代码调用 Kotlin 协程。
  • Callback 转 Flow:通用的 callbackFlow 封装,优雅接入各种传统监听器。

🧪 质量保障

作为一个生产级的小型工具库,项目配套了完整的单元测试(基于 kotlinx-coroutines-test 和 JUnit 5),并且集成了 GitHub Actions CI,每次提交都会自动运行测试,确保代码的健壮性。

# 运行单元测试
./gradlew :coroutinekit:test

欢迎交流与共建

如果你也经常在项目中遇到类似的协程样板代码,欢迎访问 GitHub 仓库了解详情、提出建议或贡献代码: