3、Hilt 的 @Component 与 @Module 注解详解
我刚开始用 Hilt 的时候,最头疼的就是搞不清 @Component 和 @Module 到底谁管谁。有一次项目里依赖死活注入不进去,排查了半天才发现是 @Module 忘了安装到正确的 Component 上。今天咱们就把这两个核心注解彻底拆开聊聊。
3.1 @Component 到底是什么?
@Component 是 Hilt 依赖注入的「容器」。说白了,它就是一个胶水层,负责把各个 @Module 提供的依赖组装起来,在合适的时机创建和销毁。Hilt 在编译期会自动生成一个继承自 Dagger Component 的实现类,你完全不需要手写 Dagger 代码。
我个人习惯把 @Component 想象成一个「黑盒生产线」——你把原材料(@Module)丢进去,设定好订单(@AndroidEntryPoint),编译后它就自动产出成品(依赖注入的实例)。当然这个生产线是分层的:
| Hilt 预设 Component | 生命周期 | 典型入口 |
|---|---|---|
| ApplicationComponent | Application 生命周期 | @HiltAndroidApp |
| ActivityComponent | Activity 生命周期 | @AndroidEntryPoint Activity |
| FragmentComponent | Fragment 生命周期 | @AndroidEntryPoint Fragment |
| ViewComponent | View 生命周期 | @WithFragmentBindings View |
| ServiceComponent | Service 生命周期 | @AndroidEntryPoint Service |
3.2 @Module 的正确打开方式
@Module 就是「原材料仓库」。你告诉 Hilt 哪些类是可以用来做依赖的,以及怎么做。但注意,@Module 本身不干活,它必须通过 @InstallIn 绑定到一个 Component 上才能生效。否则编译期 Hilt 会直接报错。
@Module 里最常用的两个注解:
- @Provides:用于提供你自己无法直接 @Inject 构造函数的对象,比如第三方库 Retrofit、Room 等实例。方法名无所谓,返回类型就是依赖的类型。
- @Binds:用于将一个接口绑定到它的实现类。参数必须是接口类型,返回接口类型,方法必须是抽象方法(Kotlin 中定义在 object 或 companion object 里要小心)。
// @Provides 示例
@Module
@InstallIn(ApplicationComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
}
// @Binds 示例
@Module
@InstallIn(ActivityComponent::class)
abstract class LoginModule {
@Binds
abstract fun bindLoginService(impl: LoginServiceImpl): LoginService
}
3.3 避坑指南:我踩过的两个坑
坑一:@Module 忘记加 @InstallIn
我曾经在一个新项目里写了个 @Module,自信满满地编译,结果 Hilt 报错说找不到模块。检查了十分钟才发现漏了 @InstallIn。缺了它,Hilt 不会把这个 @Module 合并到任何 Component 中,等于白写。
坑二:同一 Component 下重复提供相同类型
有一次我在同一个 ActivityComponent 下写了两个 @Module,都提供了 String 类型的依赖。编译时 Dagger 直接报警告「duplicate bindings」。解决办法是用 @Qualifier 自定义限定符区分,比如 @Named("api") 和 @Named("mock")。
3.4 理解 Component 的层次与作用域
Hilt 的 Component 是分层的:ApplicationComponent 是根,ActivityComponent 是子,FragmentComponent 是孙。子 Component 可以访问父 Component 提供的依赖,反过来不行。这和 Dagger 的 subcomponent 概念一致。
作用域注解(@Singleton, @ActivityScoped, @FragmentScoped)必须和 Component 的生命周期匹配。你如果在 @Module 里给了 @ActivityScoped,那么这个 @Module 只能安装到 ActivityComponent 或它的子 Component 中。我建议刚开始时统一用 @Singleton 简单粗暴,等理解透彻了再细化作用域。
3.5 实战建议:如何组织你的 Module
按功能模块拆分是个好习惯。比如网络相关的 Retrofit、OkHttp 放一个 NetworkModule;数据库相关的 Room 放一个 DatabaseModule。每个 Module 加上 @InstallIn(ApplicationComponent::class) 和 @Singleton,这样全局都能用。
对于 Activity 或 Fragment 特有的依赖,比如某个 Activity 需要的一个 ViewModel 工厂,就放在 @InstallIn(ActivityComponent::class) 里。不要一股脑全塞到全局 Module,否则编译期不会报错,但运行时作用域混乱会让你抓狂。
最后说个个人经验:如果你发现 Hilt 编译报错「cannot be provided without an @Provides or @Binds method」,先检查两点:① 对应类有没有 @Inject 构造函数?② 如果没有,有没有对应的 @Module 且正确安装?八成是漏了其中之一。