第3章 Hilt基础模块与组件:@Module、@InstallIn、@Provides、@Binds、组件作用域
3.1 本章知识结构
Hilt 的依赖注入能力建立在模块化组件之上。本章围绕 5 个核心注解与组件作用域展开,涵盖从模块声明、实例提供到生命周期绑定的完整链路。
| 注解/组件 | 职责 | 使用位置 |
|---|---|---|
| @Module | 标记类为 Hilt 依赖提供模块 | 类级别 |
| @InstallIn | 指定模块安装到哪个 Hilt 组件 | 类级别(与 @Module 配合) |
| @Provides | 在模块内提供具体实例的方法 | 模块内部方法 |
| @Binds | 将接口抽象绑定到实现类 | 模块内部抽象方法 |
| 组件作用域 | 控制依赖实例的生命周期(如 @Singleton) | 类级别 + 模块方法 |
3.2 原理:Hilt 模块化依赖注入架构
Hilt 将依赖的提供逻辑集中到 @Module 中,每个模块通过 @InstallIn 声明自身属于哪个组件。组件(如 SingletonComponent、ActivityComponent)负责管理实例的生命周期,而 @Provides 和 @Binds 则分别解决“如何创建实例”与“如何绑定抽象”两个核心问题。组件作用域进一步将实例的存活范围与组件对齐,避免内存泄漏与状态不一致。
核心原则:模块只负责“如何提供”,组件决定“何时存活”,作用域保证“谁该共享”。三者分离,各司其职。
3.3 实现流程与代码示例
3.3.1 定义 Module 并使用 @Provides
当依赖是一个具体类(或第三方库对象)时,使用 @Provides 在模块中编写工厂方法。方法返回类型即注入目标所需类型。
// 定义一个网络客户端类(待注入)
class OkHttpClient @Inject constructor() {
fun newCall(request: Request): Call { /* ... */ }
}
// 定义 Hilt 模块
@Module
@InstallIn(SingletonComponent::class) // 安装到全局单例组件
object NetworkModule {
@Provides
@Singleton // 作用域:全局单例
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient()
}
}
最佳实践:@Provides 方法名建议以 provide 开头,返回类型明确,方法体保持轻量。如果创建实例开销较大,配合 @Singleton 缓存结果。
3.3.2 使用 @Binds 绑定接口与实现
当依赖是一个接口,需要将抽象与具体实现解耦时,使用 @Binds。与 @Provides 不同,@Binds 必须声明为抽象方法,且参数是接口的实现类实例。
// 定义接口
interface ApiService {
fun fetchData(): String
}
// 实现类
class ApiServiceImpl @Inject constructor() : ApiService {
override fun fetchData(): String = "Data from API"
}
// 模块中使用 @Binds 绑定
@Module
@InstallIn(ActivityComponent::class) // 安装到 Activity 组件
abstract class ApiModule {
@Binds
abstract fun bindApiService(impl: ApiServiceImpl): ApiService
}
注意:@Binds 方法必须是 abstract 的,且只能有一个参数。方法体不写任何逻辑,Hilt 自动生成实现代码。如果需要配置或创建参数,改用 @Provides。
3.3.3 @InstallIn 与组件选择
@InstallIn 决定了模块中的依赖可以被哪些组件使用,也间接控制了依赖的可见性与生命周期。常用组件对照表如下:
| 组件 | 生命周期 | 适用于 |
|---|---|---|
SingletonComponent | 整个 Application | 全局单例依赖(数据库、网络客户端) |
ActivityComponent | Activity 从创建到销毁 | 与界面相关的依赖(适配器、屏幕管理器) |
FragmentComponent | Fragment 从创建到销毁 | Fragment 内使用的临时依赖 |
ViewModelComponent | ViewModel 存活期间 | 与 ViewModel 绑定的业务逻辑依赖 |
3.3.4 组件作用域注解
组件作用域确保同一个依赖在对应组件的生命周期内只创建一次。作用域注解与组件一一对应:
// @Singleton —— 对应 SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton // 全局唯一实例
fun provideDatabase(): Database = AppDatabase()
}
// @ActivityScoped —— 对应 ActivityComponent
@Module
@InstallIn(ActivityComponent::class)
object ScreenModule {
@Provides
@ActivityScoped // Activity 内唯一实例
fun provideScreenTracker(): ScreenTracker = ScreenTracker()
}
// @ViewModelScoped —— 对应 ViewModelComponent
@Module
@InstallIn(ViewModelComponent::class)
object ViewModelModule {
@Provides
@ViewModelScoped // ViewModel 内唯一实例
fun provideUserState(): UserState = UserState()
}
3.4 调试与验证
Hilt 在编译期会生成 Dagger* 实现类,可以通过以下方式验证模块配置是否正确:
- 在
build/generated/source/kapt/目录下查看生成的Dagger*Component类,确认模块方法是否被正确调用。 - 在
AndroidManifest.xml中确认已配置@HiltAndroidApp,并且 Application 类已标注。 - 运行时期望通过
@Inject注入的依赖如果为null,检查模块是否被安装到正确的组件。
// 典型调试日志(成功注入时无异常)
// 若模块缺失或作用域不匹配,编译期报错:
// error: [Dagger/MissingBinding] com.example.ApiService cannot be provided without an @Provides or @Binds method
3.5 常见错误与避坑指南
| 错误场景 | 原因 | 解决方案 |
|---|---|---|
| 编译报错 MissingBinding | 忘记添加 @Provides 或 @Binds | 检查模块内是否覆盖所有注入类型 |
| @Binds 方法有方法体 | @Binds 必须是抽象方法 | 去掉方法体,保留 abstract 声明 |
| 作用域不匹配 | @Singleton 用在 ActivityComponent 模块中 | 确保作用域与 @InstallIn 的组件一致 |
| 模块未安装到任何组件 | 遗漏 @InstallIn 注解 | 为模块添加 @InstallIn 并指定目标组件 |
| 多个模块提供同类型 | 产生冲突绑定 | 使用 @Named 或 @Qualifier 区分 |
3.6 性能与稳定性优化
- 作用域粒度控制:避免滥用 @Singleton,将全局依赖控制在最小集合(网络客户端、数据库、配置管理器),减少内存常驻。
- @Provides 方法轻量化:不要在 provide 方法中执行耗时操作,若必须初始化,使用
@Singleton缓存结果或借助Lazy注入。 - 模块组织:按功能或层(数据层、业务层、UI层)拆分模块,避免单个模块包含过多跨领域依赖,提升编译速度与可维护性。
- 编译期检查:开启 Hilt 的
strictBinding校验(在build.gradle中配置kapt { arguments { arg("dagger.hilt.strictBinding", "true") } }),提前捕获未使用的绑定。
本章小结:@Module 声明依赖提供单元,@InstallIn 将模块安装到指定组件,@Provides 负责实例创建,@Binds 负责接口绑定,组件作用域则控制实例的生命周期。掌握这 5 个元素,就掌握了 Hilt 依赖注入的模块化核心。下一章将深入 ViewModel 的注入机制,将本章的模块能力应用到 MVVM 架构中。