10. Hilt与WorkManager:@HiltWorker、Worker中依赖注入的实现

后台任务,说白了就是App里最容易被忽视的“隐形战场”。

WorkManager是Android官方推荐的后台任务调度库,但如果你不用依赖注入,Worker类里写一堆手动new出来的依赖,那代码很快就会变成一团乱麻。我个人习惯是:能用Hilt注入的,绝不手动创建。

10.1 为什么Worker需要依赖注入?

你想想看,Worker的生命周期是由WorkManager管理的,不是由我们控制的。每次任务执行时,系统会通过WorkerFactory来创建Worker实例。如果我们想往Worker里传Repository、ApiService这些依赖,怎么办?

传统做法是在Worker的构造方法里手动获取:

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

    private val repository = (applicationContext as MyApp).appContainer.repository
    // ...
}

嗯,这里要注意——这种写法有几个问题:

  • 强依赖Application,测试时很难替换
  • 耦合严重,换一个容器实现就得改代码
  • 可读性差,依赖关系不透明

我在项目中遇到过类似情况,当时一个同事把所有依赖都写在Worker里,结果单元测试写了三天还没跑通。后来改成Hilt注入,半小时就搞定了。

10.2 @HiltWorker 注解

Hilt从1.0版本开始就支持Worker的依赖注入。核心就是@HiltWorker这个注解。

用法很简单:

@HiltWorker
class UploadWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted workerParams: WorkerParameters,
    private val repository: UploadRepository,
    private val apiService: ApiService
) : Worker(context, workerParams) {

    override fun doWork(): Result {
        return try {
            val data = repository.getPendingUploads()
            apiService.upload(data)
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

这里有几个关键点:

  • @HiltWorker:标记这个Worker需要Hilt注入
  • @AssistedInject:因为Context和WorkerParameters是WorkManager传进来的,不是我们提供的,所以要用辅助注入
  • @Assisted:标记那些由系统提供的参数
  • 其他参数:repository、apiService这些,Hilt会自动注入

重要提醒:@HiltWorker只能用于直接继承Worker的类。如果你用了CoroutineWorker或者ListenableWorker,需要额外配置。

10.3 配置HiltWorkerFactory

光加注解还不够,你得告诉WorkManager用Hilt的工厂来创建Worker。这一步很多人会忘。

在Application类里配置:

@HiltAndroidApp
class MyApplication : Application(), Configuration.Provider {

    @Inject
    lateinit var workerFactory: HiltWorkerFactory

    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()
    }
}

为什么需要这一步?

因为WorkManager默认使用自己的WorkerFactory,它不认识Hilt的注解。我们通过Configuration.Provider接口,把HiltWorkerFactory传进去,这样WorkManager创建Worker时就会走Hilt的流程。

小技巧:如果你用了Hilt的@HiltAndroidApp,HiltWorkerFactory会自动生成,不需要自己写实现类。直接@Inject就行。

10.4 CoroutineWorker 的注入

现在很多项目都用协程,CoroutineWorker比普通Worker更灵活。注入方式略有不同:

@HiltWorker
class SyncWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted workerParams: WorkerParameters,
    private val syncRepository: SyncRepository
) : CoroutineWorker(context, workerParams) {

    override suspend fun doWork(): Result {
        return withContext(Dispatchers.IO) {
            try {
                syncRepository.syncAll()
                Result.success()
            } catch (e: Exception) {
                if (runAttemptCount < 3) {
                    Result.retry()
                } else {
                    Result.failure()
                }
            }
        }
    }
}

注意,CoroutineWorker的doWork()是挂起函数,里面可以直接用协程。依赖注入的方式和普通Worker完全一样。

10.5 多模块项目中的Worker注入

如果你的项目分了多个模块,比如:data:domain:worker,那Worker通常放在单独的模块里。这时候要注意Hilt的模块可见性。

举个例子:

// :worker 模块
@HiltWorker
class CleanupWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted workerParams: WorkerParameters,
    private val cleanupRepository: CleanupRepository  // 来自 :data 模块
) : Worker(context, workerParams) { ... }

只要CleanupRepository:data模块里用@Singleton@ViewModelScoped等注解提供了,Hilt就能跨模块注入。不需要额外配置。

我曾经踩过的坑:如果Worker模块依赖了其他模块,但那个模块的Hilt组件没有正确安装(比如忘了加@InstallIn),Hilt会在编译时报错。检查一下每个模块的Module有没有正确配置。

10.6 测试Worker的依赖注入

测试是依赖注入的一大优势。用Hilt注入的Worker,测试时只需要替换依赖即可:

@HiltAndroidTest
class UploadWorkerTest {

    @get:Rule
    val hiltRule = HiltAndroidRule(this)

    @Inject
    lateinit var workerFactory: HiltWorkerFactory

    @Mock
    lateinit var mockRepository: UploadRepository

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        // 替换真实依赖为Mock
        // 这里可以用Hilt的测试模块来替换
    }

    @Test
    fun testUploadSuccess() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        val worker = TestWorkerBuilder
            .from(context, UploadWorker::class.java, workerFactory)
            .build()

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

TestWorkerBuilder配合workerFactory,就能创建出注入好依赖的Worker实例。测试起来非常方便。

10.7 知识体系图

Hilt + WorkManager 依赖注入体系 @HiltWorker 配置 HiltWorkerFactory Configuration.Provider @AssistedInject + @Assisted Context / WorkerParameters Worker / CoroutineWorker 都支持 @HiltWorker 多模块支持 单元测试 TestWorkerBuilder 核心:让WorkManager用Hilt的工厂创建Worker 解耦依赖,方便测试,跨模块复用

10.8 常见问题与避坑

问题 原因 解决方案
编译报错:@HiltWorker只能用于Worker子类 用了CoroutineWorker但没加@HiltWorker 确认类上加了@HiltWorker注解
运行时Worker创建失败 没有配置HiltWorkerFactory 在Application中实现Configuration.Provider
依赖注入为空 @AssistedInject和@Inject混用 统一用@AssistedInject + @Assisted
多模块下找不到依赖 模块的Hilt Module没有正确安装 检查@InstallIn注解和组件范围

个人建议:如果你刚开始用Hilt+WorkManager,先从一个简单的Worker开始,跑通整个流程。确认HiltWorkerFactory配置正确后,再逐步添加复杂依赖。我曾经一次性写了五个Worker,结果排查问题花了一整天。

好了,这一章的内容就到这里。Hilt和WorkManager的结合,说白了就是让后台任务也能享受依赖注入带来的便利。配置一次,后面写Worker就轻松多了。


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