11、Hilt依赖注入(上):Hilt的组件层次与作用域、@Module与@Provides的用法、@Binds与接口注入、Hilt在ViewModel中的集成
依赖注入,说白了就是帮你把对象“组装”好,你不用自己 new。我刚开始接触 Android 时,最烦的就是在 Activity 里写一堆初始化代码。后来用了 Dagger,又觉得配置太复杂。直到 Google 推出了 Hilt,我才觉得依赖注入终于“接地气”了。
这一章,我们来聊聊 Hilt 的核心机制。我会结合自己踩过的坑,带你搞懂组件层次、作用域、Module 写法,还有 ViewModel 的集成。
Hilt 的组件层次与作用域
Hilt 的组件层次,说白了就是一套“生命周期管理”机制。每个组件都对应 Android 的一个生命周期节点。
我画了一张图,帮你快速理解:
从上图可以看到,Hilt 的组件是分层的。最顶层是 SingletonComponent,对应 Application 的生命周期。往下依次是 Activity、Fragment、View 等。
每个组件都有自己的作用域注解。比如 @Singleton 对应 Application 级别,@ActivityScoped 对应 Activity 级别。你想想看,如果一个对象被标记为 @Singleton,那它在整个应用里只有一个实例。如果标记为 @ActivityScoped,那它在同一个 Activity 里是单例的。
核心原则:上层组件可以访问下层组件的依赖,但下层组件不能访问上层组件的依赖。说白了,就是“爸爸可以拿儿子的东西,儿子不能拿爸爸的东西”。
我在项目中遇到过一个问题:有个网络请求的 Retrofit 实例,我把它放在了 @ActivityScoped 里。结果每次旋转屏幕,Activity 重建,Retrofit 也跟着重建。后来我改成 @Singleton,问题就解决了。嗯,这里要注意,作用域选错了,性能会受影响。
@Module 与 @Provides 的用法
有些类不是我们自己写的,比如 Retrofit、OkHttp、Room 数据库。这些第三方类,我们没法给它们加 @Inject 注解。这时候就需要 @Module 和 @Provides 来帮忙。
说白了,@Module 就是一个“生产车间”,@Provides 就是车间里的“生产线”。
来看一个例子:
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
这里有几个关键点:
@InstallIn(SingletonComponent::class)表示这个 Module 安装在 SingletonComponent 里,也就是 Application 级别。@Provides告诉 Hilt:“嘿,这个函数能生产某个类型的对象”。- 函数参数会自动注入。比如
provideRetrofit需要OkHttpClient,Hilt 会自动从容器里找。
我的习惯:我会把网络相关的 Module 单独放在一个 di 包下,命名为 NetworkModule。数据库、本地存储也各自独立。这样找起来方便,也符合单一职责。
你可能会问:为什么用 object 而不是 class?因为 Module 里通常没有状态,用 object 更简洁。如果 Module 需要构造函数参数,那就用 class。
@Binds 与接口注入
接口注入,说白了就是“面向接口编程”。我们定义一个接口,然后告诉 Hilt:“当有人要这个接口时,给他这个实现类”。
举个例子:
// 定义接口
interface UserRepository {
fun getUser(): User
}
// 实现类
class UserRepositoryImpl @Inject constructor(
private val apiService: ApiService
) : UserRepository {
override fun getUser(): User {
return apiService.fetchUser()
}
}
// Module 绑定
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindUserRepository(
userRepositoryImpl: UserRepositoryImpl
): UserRepository
}
这里用 @Binds 而不是 @Provides。为什么?因为 @Binds 更高效。它告诉 Hilt:“这个接口的实现类就是它,你直接拿过去用”。而 @Provides 需要执行一个函数,多了一层调用。
我曾经踩过的坑:一开始我分不清 @Binds 和 @Provides,全都用 @Provides。后来发现编译时间变长了,而且代码也不够清晰。记住:@Binds 用于接口绑定,@Provides 用于第三方类或复杂构造。
另外,@Binds 方法必须是抽象的,而且 Module 必须是 abstract class,不能是 object。这个细节要注意。
Hilt 在 ViewModel 中的集成
ViewModel 的注入,是 Hilt 最常用的场景之一。以前我们要用 ViewModelProvider.Factory,写一堆模板代码。有了 Hilt,一切都变得简单了。
首先,在 ViewModel 上加上 @HiltViewModel 注解:
@HiltViewModel
class MainViewModel @Inject constructor(
private val userRepository: UserRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user.asStateFlow()
fun loadUser() {
viewModelScope.launch {
_user.value = userRepository.getUser()
}
}
}
然后,在 Activity 或 Fragment 里,直接用 by viewModels() 或 by hiltViewModel():
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.loadUser()
}
}
就这么简单。Hilt 会自动创建 ViewModel,并注入它的依赖。你不需要写任何 Factory。
注意:使用 @HiltViewModel 时,ViewModel 的构造函数必须用 @Inject 注解。而且 SavedStateHandle 是自动注入的,不需要你手动提供。
我记得有一次,同事问我:“为什么我的 ViewModel 里拿不到 Application 上下文?” 我一看,他用了 @Inject 但没加 @HiltViewModel。嗯,这个注解不能少,它是 Hilt 识别 ViewModel 的关键。
另外,如果你需要在 ViewModel 里使用 Application 上下文,可以用 @ApplicationContext 注解:
@HiltViewModel
class MyViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val repository: UserRepository
) : ViewModel() {
// ...
}
但我不建议这么做。ViewModel 应该尽量不依赖 Context,除非你真的需要。比如获取系统服务或读取资源文件。
小结
这一章我们聊了 Hilt 的组件层次、作用域、Module 和 Provides、Binds 接口注入,还有 ViewModel 的集成。说白了,Hilt 就是帮你管理对象的生命周期和依赖关系,让你少写模板代码。
我个人觉得,Hilt 最大的价值在于“约定优于配置”。你只要按照规则写注解,它就能自动帮你搞定一切。当然,刚开始接触时可能会觉得注解太多,但用习惯了就会发现,它真的能提升开发效率。
下一章,我们会继续深入 Hilt,聊聊多模块项目中的依赖注入、自定义组件,以及一些高级用法。到时候见。
公众号:蓝海资料掘金营,微信deep3321