Hilt 是 Android 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,提供了一种在应用中使用 DI(依赖项注入)的标准方法。Hilt 在 Dagger 的基础上构建而成,因而能够受益于 Dagger 的编译时正确性、运行时性能、可伸缩性。
在project/build.gradle下加入hilt的插件
dependencies {
//hilt编译插件
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
在app/build.gradle下加入hilt依赖
// Hilt dependencies
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
class HelloPresenter @Inject constructor() {
...
}
所有使用 Hilt 的应用都必须包含一个带有 @HiltAndroidApp 注解的 Application 类。@HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器。
@HiltAndroidApp
class MyHiltApplication : Application() {}
生成的这一 Hilt 组件会附加到 Application 对象的生命周期,并为其提供依赖项。此外,它也是应用的父组件,这意味着,其他组件可以访问它提供的依赖项。
在 Application 类中设置了 Hilt 且有了应用级组件后,Hilt 可以为带有 @AndroidEntryPoint 注解的其他 Android 类提供依赖项:
@AndroidEntryPoint
class MainActivity : ComponentActivity() { ... }
@AndroidEntryPoint 会为项目中的每个 Android 类生成一个单独的 Hilt 组件。这些组件可以从它们各自的父类接收依赖项。使用 @Inject 注解执行字段注入:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var presenter: HelloPresenter
...
}
如 2-Step0 所示,在类构造器上使用@Inject即可,不再赘述。
某些情况下,依赖项不能通过构造函数提供实例,如:不能通过构造函数提供接口实例。这些情况下,可以使用 Hilt 模块向 Hilt 提供绑定信息。
Hilt 模块是一个带有@Module注解的类。与 Dagger 模块一样,它会告知 Hilt 如何提供某些类型的实例。与 Dagger 模块不同的是,必须使用@InstallIn为 Hilt 模块添加注解,以告知 Hilt 每个模块将用在或安装在哪个 Android 类中。
上面例子,假如HelloPresenter类的构造器中依赖HelloService接口:
class HelloPresenter @Inject constructor(
private val service: HelloService
) {
...
}
interface HelloService {
...
}
HelloService是接口类,无法通过构造器提供实例。此时可通过带有@Module注解的类以及带有@Binds注解的方法,向 Hilt 提供实现类实例。
interface HelloService {
...
}
class HelloServiceImpl @Inject constructor() : HelloService {
override fun hello(name: String): String {
...
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class HelloModule {
/**
* 提供 HelloService 实现类实例。参数为实现类,告知 Hilt 如何提供实现类的实例
*/
@Binds
abstract fun bindHelloService(service: HelloServiceImpl): HelloService
}
接口不是无法通过构造函数提供实例的唯一一种情况。如果某个类来自外部库(如 Retrofit、OkHttpClient 等类),或者必须使用构建器模式创建实例,也无法通过构造函数提供实例。
接着前面的例子来讲。如果 HelloService 类的实例是使用 Retrofit 创建的,则需要告知 Hilt 如何提供此类型的实例,方法是在 Hilt 模块内创建一个函数,并使用 @Provides 为该函数添加注解。
@Module
@InstallIn(ActivityComponent::class)
object HelloModule {
@Provides
fun provideHelloService(): HelloService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(HelloService::class.java)
}
}
我们知道,接口是可以有多个实现的。本例中当 HelloService 有多个实现类的时候,我们可能需要写多个 @Binds 或者 @Provides 修饰的方法,那么如何区分要使用哪个实现类呢?Hilt中提供了自定义的注解限定符。这些限定符可以作用在@Binds和@Provides修饰的方法上。比如下面定义两个限定符
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class HelloServiceForLocal
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class HelloServiceForRemote
然后在使用的地方进行标注,如下所示:
@Module
abstract class HelloModule {
/**
* 提供 HelloService 实现类实例。参数为实现类,告知 Hilt 如何提供实现类的实例
*/
@HelloServiceForLocal
@Binds
abstract fun bindHelloService(service: HelloServiceImpl): HelloService
@HelloServiceForRemote
@Provides
fun provideHelloService(): HelloService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(HelloService::class.java)
}
}
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@HelloServiceForLocal
@Inject
lateinit var localService: HelloService
@HelloServiceForRemote
@Inject
lateinit var remoteService: HelloService
...
}
代码中 localService 是由HelloModule#bindHelloService方法提供是实例,remoteService是由HelloModule#provideHelloService方法提供的实例。
Hilt 提供了一些预定义的限定符。例如,某些类可能需要来自应用或 activity 的 Context 类,因此 Hilt 提供了 @ApplicationContext 和 @ActivityContext 限定符。
假设本例中的 HelloPresenter 类需要 activity 的上下文,则代码可以做如下修改:
class HelloPresenter @Inject constructor(
@ActivityContext context: Context
) { ... }
对于需要字段注入的每个 Android 类,都有一个关联的 Hilt 组件,可以在 @InstallIn 注解中引用该组件。每个 Hilt 组件负责将其绑定注入相应的 Android 类(如上述示例中使用的ActivityComponent)。
Hilt 提供了以下组件:
Hilt 组件 | 注入器面向的对象 |
---|---|
SingletonComponent | Application |
ActivityRetainedComponent | 不适用 |
ViewModelComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | 带有 @WithFragmentBindings 注解的 View |
ServiceComponent | Service |
ps:Hilt 不会为广播接收器生成组件,因为 Hilt 直接从 SingletonComponent 注入广播接收器。
Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的组件类的实例。
Hilt 生成的组件 | 创建时机 | 销毁时机 |
---|---|---|
SingletonComponent | Application#onCreate() | Application 已销毁 |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ViewModelComponent | ViewModel 已创建 | Activity#onDestroy() |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | View 已销毁 |
ViewWithFragmentComponent | View#super() | View 已销毁 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
默认情况下,Hilt 中的所有注入都未限定作用域。这意味着,每当应用请求注入依赖项时,Hilt 都会创建所需类型的一个新实例。
在本例中,每当 Hilt 提供 HelloPresenter 作为其他类型的依赖项或通过字段注入提供它(如在 MainActivity 中)时,Hilt 都会提供 HelloPresenter 的一个新实例。
Hilt 也允许将提供依赖项的作用域限定为特定组件。Hilt 只为作用域限定到的组件的每个实例创建一次限定作用域的注入,对该注入的所有请求共享同一实例。
下表列出了生成的每个组件的作用域注解:
Hilt 生成的组件 | Android 类 | 作用域 |
---|---|---|
SingletonComponent | Application | @Singleton |
ActivityRetainedComponent | Activity | @ActivityRetainedScoped |
ViewModelComponent | ViewModel | @ViewModelScoped |
ActivityComponent | Activity | @ViewModelScoped |
FragmentComponent | Fragment | @ViewModelScoped |
ViewComponent | View | @ViewModelScoped |
ViewWithFragmentComponent | 带有 @WithFragmentBindings 注解的 View | @ViewScoped |
ServiceComponent | Service | @ServiceScoped |
在本例中,如果您使用 @ActivityScoped 将 HelloPresenter 的作用域限定为 ActivityComponent,Hilt 会在相应 activity 的整个生命周期内提供 HelloPresenter 的同一实例。
将模块安装到组件后,其绑定就可以用作该组件中其他绑定的依赖项,也可以用作组件层次结构中该组件下的任何子组件中其他绑定的依赖项:
每个 Hilt 组件都附带一组默认绑定,Hilt 可以将其作为依赖项注入自定义绑定。这些绑定对应于常规 activity 和 fragment 类型,而不对应于任何特定子类。因为 Hilt 会使用单个 activity 组件定义来注入所有 activity。每个 activity 都有此组件的不同实例。
Android 组件 | 默认绑定 |
---|---|
SingletonComponent | Application |
ActivityRetainedComponent | Application |
ViewModelComponent | SavedStateHandle |
ActivityComponent | Application 和 Activity |
FragmentComponent | Application、Activity 和 Fragment |
ViewComponent | Application、Activity 和 View |
ViewWithFragmentComponent | Application、Activity、Fragment 和 View |
ServiceComponent | Application 和 Service |
此外,还可以使用 @ApplicationContext 获得 Application 上下文绑定。例如:
class HelloPresenter @Inject constructor(
@ApplicationContext context: Context
) { ... }
可以使用 @ActivityContext 获得 Activity 上下文绑定。例如:
class HelloPresenter @Inject constructor(
@ActivityContext context: Context
) { ... }
Hilt 在依赖项注入库 Dagger 的基础上构建而成,提供了一种将 Dagger 纳入 Android 应用的标准方法。
关于 Dagger,Hilt 的目标如下:
由于 Android 操作系统会实例化它自己的许多框架类,因此在 Android 应用中使用 Dagger 要求您编写大量的样板。Hilt 可减少在 Android 应用中使用 Dagger 所涉及的样板代码。Hilt 会自动生成并提供以下各项:
Dagger 和 Hilt 代码可以共存于同一代码库中。不过,在大多数情况下,最好使用 Hilt 管理您在 Android 上对 Dagger 的所有使用。