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 的构造函数必须接收 Context 和 WorkerParameters。这是 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 的依赖。
先看步骤:
- 在 Koin 模块中用
worker { }定义 Worker - 在 Worker 构造函数中声明你需要的依赖
- 使用
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 也支持 CoroutineWorker 和 RxWorker。用法完全一样:
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 集成的完整流程:
测试 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 之后。这个坑我替你们踩过了。
workManagerModule,跟其他模块分开。这样结构清晰,也方便测试时替换。
实际项目中,我一般把 Worker 的模块放在 di/WorkManagerModule.kt 里。跟网络模块、数据库模块平级。找起来方便。
公众号:蓝海资料掘金营,微信deep3321