3、Hilt 的 @Component 与 @Module 注解详解

我刚开始用 Hilt 的时候,最头疼的就是搞不清 @Component 和 @Module 到底谁管谁。有一次项目里依赖死活注入不进去,排查了半天才发现是 @Module 忘了安装到正确的 Component 上。今天咱们就把这两个核心注解彻底拆开聊聊。

@Component 与 @Module 协作关系 Component 层 @HiltAndroidApp (ApplicationComponent) @AndroidEntryPoint (Activity / Fragment / View / Service) Module 层 @Module (安装到指定 Component) @Provides (实例提供) @Binds (接口绑定) @InstallIn 依赖注入 📌 每个 @Module 必须通过 @InstallIn 指定归属的 Component。 Hilt 在编译期自动生成 Dagger Component

3.1 @Component 到底是什么?

@Component 是 Hilt 依赖注入的「容器」。说白了,它就是一个胶水层,负责把各个 @Module 提供的依赖组装起来,在合适的时机创建和销毁。Hilt 在编译期会自动生成一个继承自 Dagger Component 的实现类,你完全不需要手写 Dagger 代码。

我个人习惯把 @Component 想象成一个「黑盒生产线」——你把原材料(@Module)丢进去,设定好订单(@AndroidEntryPoint),编译后它就自动产出成品(依赖注入的实例)。当然这个生产线是分层的:

Hilt 预设 Component生命周期典型入口
ApplicationComponentApplication 生命周期@HiltAndroidApp
ActivityComponentActivity 生命周期@AndroidEntryPoint Activity
FragmentComponentFragment 生命周期@AndroidEntryPoint Fragment
ViewComponentView 生命周期@WithFragmentBindings View
ServiceComponentService 生命周期@AndroidEntryPoint Service
💡 千万不要在 ApplicationComponent 里注入 Activity 作用域的依赖——运行时就会抛异常。我曾经有个同事在全局的 @Singleton 里注入了 Activity 的 Context,结果一启动就闪退,排查了整整一下午。

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
}
🔑 关键区别:@Provides 可以创建任何对象(包括带参数的),而 @Binds 只做接口与实现的绑定,不创建实例。你用 @Binds 时,实现类必须有自己的 @Inject 构造函数。

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 简单粗暴,等理解透彻了再细化作用域。

⚠️ 注意:@Provides 方法每次被调用都会执行一遍方法体,除非你加了作用域(如 @Singleton)让 Hilt 缓存实例。如果你的 @Provides 方法里创建了一个重量级对象(如 OkHttpClient),不加作用域会导致每次注入都新建,内存爆炸可不是开玩笑的。

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 且正确安装?八成是漏了其中之一。