依赖注入框架 Hilt 的使用
这篇记录下 Hilt 最基本的使用方法。
Hilt 是基于 Dagger 开发的依赖注入框架。我们知道 Dagger 是 Java 开发中无可否认的功能最为强大的依赖注入框架,但是它的缺点是使用起来比较复杂,尤其是对于初学者而言,学习曲线异常陡峭。而依赖注入框架在 Android 开发中又是一个非常重要的工具,因此,在 Kotlin 成为了 Android 开发者的首选语言之后,开源社区中诞生了许多基于 Kotlin 依赖注入框架,比如 Koin 和 Kodein。谷歌当然也注意到了这一点,所以也基于 Dagger 推出了更简单易上手的 Hilt,它的主要优势是:
- 基于 Android 简化了 Dagger 相关的基础架构;
- 提供了一组标准的 Component 和 Scope 以简化使用、提升可读性以及便于代码共享;
- 简化针对不同的构建类型(比如测试、调试或发布类型)配置不同的绑定。
之所以能做到以上这几点,是因为 Hilt 自动帮我们做了很多工作,比如自动生成用于和 Android Framwork 绑定的 Component/Scoped annotations/Bindings/Qualifier 等,而如果使用 Dagger 的话,这些都是需要我们自己手动编写代码来管理的。
Hilt 的基本使用
使用方式上,Hilt 和 Dagger 相比最明显的区别是,不再需要定义 Component,对于每一个 Android 基础类,Hilt 会自动为它生成对应的 Component,并且会根据安卓组件的生命周期来创建和销毁。如下:
Hilt 组件 | 注入器面向的对象 |
---|---|
ApplicationComponent |
Application |
ActivityRetainedComponent |
ViewModel |
ActivityComponent |
Activity |
FragmentComponent |
Fragment |
ViewComponent |
View |
ViewWithFragmentComponent |
带有 @WithFragmentBindings 注释的 View |
ServiceComponent |
Service |
因此,对于一个最简单的 MVVM 项目而言,使用 Hilt 做依赖注入一般需要按照以下的步骤:
- 添加 Hilt 插件和依赖,对于每个 module 都要单独添加
- 使用
@HiltAndroidApp
标注你自定义的Application
- 使用
@AndroidEntryPoint
标注你的Fragment
或者Activity
- 使用
@HiltViewModel
标注你的 ViewModel 类,以及用@Inject
标注构造器 - 使用
@Inject
标注需要注入的依赖,比如 Repository 等 - 定义 Hilt 模块,用于提供无法直接注入的依赖,比如接口类和外部的依赖类等
- 使用
@InstallIn
标注该模块的作用范围 - 使用
@Provides
标注提供每个依赖的方法 - 使用
@Binds
标注需要注入依赖的接口和具体实现(通过抽象类和抽象方法)
- 使用
具体例子可以参考我的开源小项目:Jithub
模块和组件
虽然 Hilt 大大简化了依赖注入的使用,但是使用方式上和 Dagger 并没有太大区别,最基础的组成部分依旧是模块和组件。对于某个 Hilt 模块,如果我们想要注入多个相同类型的依赖,同样需要通过定义限定符来实现。
使用注解定义限定符(例子来自官方文档):
1 |
|
除此之外,我们还可以使用 Hilt 自带的限定符,比如 @ActivityContext
和 @ApplicationContext
等。
组件的使用细节
之前提到过对于不同的 Android 类,Hilt 会为之生成对应的组件,我们在模块中通过 @InstalledIn
引用组件,然后 Hilt 会将模块安装到对应的组件中,最后再把依赖注入到组件中。
除此之外,每个组件都有自己的生命周期,而且我们还可以为组件限定作用域。不同的组件作用域内可使用的依赖也不同,比如 @FragmentScoped
作用域内的依赖可以在 @ActivityScoped
的组件中使用,但是无法在 @ViewScoped
的组件中使用。
Android 类 | 生成的组件 | 作用域 |
---|---|---|
Application |
ApplicationComponent |
@Singleton |
View Model |
ActivityRetainedComponent |
@ActivityRetainedScope |
Activity |
ActivityComponent |
@ActivityScoped |
Fragment |
FragmentComponent |
@FragmentScoped |
View |
ViewComponent |
@ViewScoped |
带有 @WithFragmentBindings 注释的 View |
ViewWithFragmentComponent |
@ViewScoped |
Service |
ServiceComponent |
@ServiceScoped |
其它细节
如果需要在 Hilt 不支持的类中注入依赖,比如 ContentProvider
,我们可以通过创建 EntryPoint
来访问这些依赖。
通过 @EntryPoint
创建依赖入口(通常是一个接口),然后在其中定义方法提供所需要的依赖,再通过 @InstalledIn
来定义安装到哪个组件。另外,访问 EntryPoint
依赖也和普通的依赖不同,我们需要通过之前定义好的 EntryPoint
接口来访问依赖项。
1 |
|
EntryPointAccessors
中包含不同的创建方法,对应于创建不同的组件,比如 EntryPointAccessors.fromApplication()
对应于 SingletonComponent
,fromActivity()
对应于 ActivityComponent
等。创建 EntryPoint
接口对象之后,我们就可以通过它来访问依赖了。
小结
可以看到,Hilt 大大简化了传统依赖注入框架的使用方式,但是核心功能基本和 Dagger 保持一致,因此,开发人员可以将更多的注意力放在开发上,从而提升开发效率。