22. Koin与WorkManager:workManager DSL、Worker中依赖注入

WorkManager 是 Android 官方推荐的异步任务调度库。它处理那些「即使 App 退出也要执行」的任务,比如上传日志、同步数据、清理缓存。

但有个问题——WorkManager 的 Worker 是由系统直接实例化的。你没法像 Activity 或 Fragment 那样,通过构造函数注入依赖。这就尴尬了。

嗯,Koin 专门为这个场景提供了 workManager DSL。说白了,就是一套让 Worker 也能享受依赖注入的语法糖。

为什么 Worker 的注入是个难题?

先看看原生 WorkManager 的写法:

class UploadWorker(
    context: Context,
    params: WorkerParameters
) : Worker(context, params) {

    override fun doWork(): Result {
        // 这里想用 Repository?没法直接注入
        return Result.success()
    }
}

Worker 的构造函数必须接收 ContextWorkerParameters。这是 WorkManager 框架强制的。你不能自己加参数。

那怎么办?

我早期做项目时,用过一种很蠢的办法——在 Worker 内部手动调用 Koin 的 get()

class UploadWorker(
    context: Context,
    params: WorkerParameters
) : Worker(context, params) {

    private val repository: UploadRepository by inject()

    override fun doWork(): Result {
        repository.upload()
        return Result.success()
    }
}

这能跑,但不好。为什么呢?

  • 测试困难——你没法 mock 注入的依赖
  • 依赖不透明——看构造函数不知道它需要什么
  • 违背了「显式依赖」的原则

Koin 的 workManager DSL 就是为了解决这个问题。

workManager DSL 的核心用法

Koin 提供了一个专门的 DSL 函数——worker。用它来声明 Worker 的依赖。

先看步骤:

  1. 在 Koin 模块中用 worker { } 定义 Worker
  2. 在 Worker 构造函数中声明你需要的依赖
  3. 使用 WorkManagerInjection.setup() 让 Koin 接管 Worker 的创建

代码示例:

// 1. 定义 Worker,构造函数注入依赖
class UploadWorker(
    context: Context,
    params: WorkerParameters,
    private val repository: UploadRepository
) : Worker(context, params) {

    override fun doWork(): Result {
        return try {
            repository.upload()
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

// 2. 在 Koin 模块中声明
val workManagerModule = module {
    worker { UploadWorker(get(), get(), get()) }
}

// 3. 在 Application 中设置
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApp)
            modules(workManagerModule)
        }
        WorkManagerInjection.setup(this)
    }
}

注意看 worker { } 里面的 get()。第一个 get() 是 Context,第二个是 WorkerParameters,第三个才是我们自定义的 UploadRepository。

Koin 会自动把前两个参数从 WorkManager 框架中取出来,第三个从容器中注入。

避坑指南:我曾经踩过的坑

我曾经在项目里直接用 worker { UploadWorker(get(), get(), get()) },结果运行时崩溃了。报错说找不到 Context。

排查了半天,发现是 WorkManagerInjection.setup() 必须在 startKoin 之后调用。顺序错了,Koin 还没初始化完,WorkManager 就拿不到容器。

注意: WorkManagerInjection.setup() 必须在 startKoin() 之后调用。否则会抛出 IllegalStateException

另一个坑是:如果你用了 WorkManager.initialize() 手动初始化,那 Koin 的 setup 会失效。两者只能选一个。

ListenableWorker 的支持

Koin 也支持 CoroutineWorkerRxWorker。用法完全一样:

class SyncWorker(
    context: Context,
    params: WorkerParameters,
    private val syncManager: SyncManager
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        syncManager.sync()
        return Result.success()
    }
}

// 模块声明
val module = module {
    worker { SyncWorker(get(), get(), get()) }
}

我个人习惯用 CoroutineWorker。因为 doWork() 是挂起函数,天然支持协程。配合 Koin 注入,写起来很舒服。

整体架构图

下面这张图展示了 Koin 与 WorkManager 集成的完整流程:

Koin + WorkManager 集成流程 Application startKoin { modules(workManagerModule) } WorkManagerInjection.setup(this) Koin 自动注入 Context + WorkerParameters + 依赖 doWork() 执行任务 关键点 • setup 必须在 startKoin 之后 • worker {} 中前两个参数 由框架自动提供 • 支持 CoroutineWorker

测试 Worker 的依赖注入

用 Koin 的 workManager DSL 后,测试变得简单了。你可以直接构造 Worker 实例,传入 mock 的依赖:

class UploadWorkerTest {

    private val mockRepository: UploadRepository = mock()

    @Test
    fun `upload worker succeeds`() {
        val worker = UploadWorker(
            context = ApplicationProvider.getApplicationContext(),
            params = WorkerParameters(
                id = UUID.randomUUID(),
                inputData = Data.EMPTY,
                tags = emptyList(),
                runtimeExtras = WorkerParameters.RuntimeExtras(),
                runAttemptCount = 1,
                generation = 0,
                backoffDelayDuration = 0
            ),
            repository = mockRepository
        )

        val result = worker.doWork()
        assertEquals(Result.success(), result)
    }
}

你想想看,如果不用注入,你只能在 Worker 内部硬编码依赖。测试时要么改代码,要么用反射。多麻烦。

总结

Koin 的 workManager DSL 解决了 Worker 依赖注入的痛点。核心就三步:

  • worker { } 声明 Worker
  • 在构造函数中声明依赖
  • 调用 WorkManagerInjection.setup()

嗯,就这么简单。但顺序别搞反了,setup 必须在 startKoin 之后。这个坑我替你们踩过了。

小技巧: 如果你有多个 Worker,建议单独建一个 workManagerModule,跟其他模块分开。这样结构清晰,也方便测试时替换。

实际项目中,我一般把 Worker 的模块放在 di/WorkManagerModule.kt 里。跟网络模块、数据库模块平级。找起来方便。


公众号:蓝海资料掘金营,微信deep3321