WorkManager 与 Hilt/Dagger 依赖注入
依赖注入这个话题,说实话在 Android 开发里已经不算新鲜了。但 WorkManager 和 Hilt 怎么配合,很多人一开始都会踩坑。我记得刚接触 WorkManager 时,第一反应就是:Worker 的构造函数不是我能控制的,那怎么把 Repository 传进去?
嗯,今天我们就来彻底解决这个问题。
为什么 Worker 需要依赖注入?
先看一个最朴素的场景。你有一个后台任务,需要从网络拉数据,然后存到本地数据库。如果没有依赖注入,你可能会这样写:
class SyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// 直接在 Worker 里创建依赖
val api = Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
.create(ApiService::class.java)
val dao = AppDatabase.getInstance(applicationContext).userDao()
// 业务逻辑...
return Result.success()
}
}
这样写有什么问题?我告诉你,问题大了去了。首先,测试变得极其困难——你没法 mock 掉 api 和 dao。其次,如果多个 Worker 都需要同样的依赖,你就得重复创建。说白了,这违反了依赖反转原则。
HiltWorker 注解:最简单的注入方式
Hilt 从 1.0 版本开始就提供了 @HiltWorker 注解。我个人习惯用这种方式,因为它最省事。
用法很简单,三步走:
- 给 Worker 类加上
@HiltWorker注解 - 在构造函数里用
@Inject标注需要注入的参数 - 确保 Application 类用
@HiltAndroidApp注解
@HiltWorker
class SyncWorker @Inject constructor(
context: Context,
workerParams: WorkerParameters,
private val userRepository: UserRepository
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
return try {
userRepository.syncUsers()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
你看,这样写就清爽多了。UserRepository 由 Hilt 自动提供,你完全不用操心它的生命周期。
WorkerFactory 的创建:更灵活的控制
如果你不想用 @HiltWorker,或者你需要更细粒度的控制,可以自己实现 WorkerFactory。我在项目中遇到过一种情况:同一个 Worker 类需要根据不同的参数使用不同的依赖实例。这时候 @HiltWorker 就有点力不从心了。
自己写 WorkerFactory 其实不复杂:
class HiltWorkerFactory @Inject constructor(
private val userRepository: UserRepository,
private val orderRepository: OrderRepository
) : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
return when (workerClassName) {
SyncWorker::class.java.name -> {
SyncWorker(appContext, workerParameters, userRepository)
}
OrderSyncWorker::class.java.name -> {
OrderSyncWorker(appContext, workerParameters, orderRepository)
}
else -> null
}
}
}
然后你需要在 Application 里配置这个工厂:
@HiltAndroidApp
class MyApplication : Application(), Configuration.Provider {
@Inject
lateinit var hiltWorkerFactory: HiltWorkerFactory
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(hiltWorkerFactory)
.build()
}
注入 Repository 到 Worker 中
不管用哪种方式,最终目的都是把 Repository 注入到 Worker 里。这里我建议遵循一个原则:Worker 只负责调度和生命周期管理,业务逻辑全部委托给 Repository。
举个例子,假设你有一个 UserRepository:
class UserRepository @Inject constructor(
private val apiService: ApiService,
private val userDao: UserDao
) {
suspend fun syncUsers(): List<User> {
val users = apiService.fetchUsers()
userDao.insertAll(users)
return users
}
}
那么 Worker 里的代码就变得非常薄:
@HiltWorker
class SyncWorker @Inject constructor(
context: Context,
params: WorkerParameters,
private val userRepository: UserRepository
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return userRepository.syncUsers().let {
Result.success()
}
}
}
这样做的好处很明显:测试 Worker 时,你只需要 mock 掉 UserRepository,不需要关心它内部是怎么调用 API 和数据库的。
测试注入的 Worker
测试这块,说实话很多人会忽略。但我觉得,后台任务一旦出问题,排查起来比 UI 层麻烦得多。所以测试一定要跟上。
用 Hilt 的测试框架,你可以这样写:
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class SyncWorkerTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var userRepository: UserRepository
@Before
fun setup() {
hiltRule.inject()
}
@Test
fun testSyncWorker_success() = runBlocking {
// 准备测试数据
val context = ApplicationProvider.getApplicationContext<Context>()
val workerParams = WorkerParameters(
UUID.randomUUID(),
Data.EMPTY,
emptyList(),
WorkerParameters.RuntimeExtras(),
1,
1,
context.resources,
null,
null,
null
)
// 创建 Worker 实例
val worker = SyncWorker(context, workerParams, userRepository)
// 执行任务
val result = worker.doWork()
// 验证结果
assertEquals(Result.success(), result)
}
}
我曾经踩过一个坑:测试时忘记注入依赖,结果 userRepository 一直是 null。后来我养成了一个习惯——每次写测试前,先确认 hiltRule.inject() 有没有调用。
知识体系总览
下面这张图总结了 WorkManager 与 Hilt 依赖注入的核心流程:
两种注入方式对比
| 对比维度 | @HiltWorker 注解 | 自定义 WorkerFactory |
|---|---|---|
| 代码量 | 少,只需一个注解 | 多,需要实现工厂类 |
| 灵活性 | 中等,依赖由 Hilt 自动提供 | 高,可以手动控制依赖实例 |
| 适用场景 | 大多数常规场景 | 需要根据参数选择不同依赖 |
| 测试难度 | 容易,Hilt 测试框架支持 | 中等,需要手动 mock 工厂 |
| 维护成本 | 低,Hilt 自动管理 | 中,工厂代码需要维护 |
我个人建议,除非你有特殊需求,否则优先用 @HiltWorker。它简单、可靠,而且 Hilt 团队一直在维护。我刚开始用自定义工厂时,就遇到过因为工厂里忘记处理某个 Worker 类型,导致运行时崩溃的问题。后来换成 @HiltWorker,这类问题再也没出现过。
好了,关于 WorkManager 和 Hilt 的依赖注入,核心内容就是这些。记住一点:让 Worker 保持轻量,把业务逻辑交给 Repository,用依赖注入来解耦。这样你的代码不仅好测,也好维护。