Hilt 是Google 最新的依赖注入框架,其基于Dagger研发。Hilt可以说是专门为Android 打造,提供了一种将Dagger依赖项注入到Android应用程序的标准方法,而且创建了一些标准的组件和作用域,这些组件会自动集成到Android应用程序的各个生命周期中,以简化开发者的上手难度。
需要AndroidStudio4.0版本及以上以上
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
//Hilt 使用 Java 8 功能。如需在项目中启用 Java 8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
所有使用 Hilt 的应用都必须包含一个带有 @HiltAndroidApp 注释的 Application 类。在Application的上面必须使用这个注解才可以开启hilt依赖注入。
@HiltAndroidApp
class ExampleApplication : Application() { ... }
Inject注解和Dagger2中一样有两个作用:
提供依赖项:
//@Inject注解在类上,去标记Hilt如何去提供实例。
class AnalyticsAdapter @Inject constructor() { ... }
获取依赖项:
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
// 标记Hilt要给该类提供实例
@Inject
lateinit var analytics: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt_test)
Log.d("TAG",analytics.hashCode().toString())
}
...
}
运行项目可以看到,并没有手动实例化AnalyticsAdapter对象,而是通过依赖注入框架进行的实例化。
注:由 Hilt 注入的字段不能用私有修饰符进行修饰。使用 Hilt 注入私有字段会导致编译错误。
上文说到在 Application 类中设置了 Hilt 件后,Hilt 可以为带有 @AndroidEntryPoint 的组件注入其依赖项。
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
如果对Dagger2有所了解的朋友,肯定会对Compenent不陌生。Compenent的作用就是将依赖提供项注入到需要使用的地方。前文说过Hitl实际是对Dagger2的封装。在上文例子中并没有用Compenent进行注入就可以进行依赖项实例化。其中的原因就是Hitl默认实现了部分组件的Compenent,可以直接让使用,节省代码。也就说Hilt大幅简化了Dagger2的用法,使得不用通过@Component注解去编写桥接层的逻辑,但是也因此限定了注入功能只能从几个Android固定的入口点开始。 只需要在类上增加 @AndroidEntryPoint 即可支持以下类的注入
Hilt Compenent | 注入器面向的对象 |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel(请参阅JetPack-ViewModel扩展) |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View 与 @WithFragmentBindings |
ServiceComponent | Service |
注:如果Fragment使用,那么包含Fragment的Activity必须也要用该注解,如果View使用, 对应的Fragment和Activity也必须使用。
在之前Dagger-Android中,必须创建诸如ActivityScope,FragmentScope之类的范围注释,以管理对象的生命周期, 而Hitl只要使用@InstallIn的注解,就可以委托Hilt帮管理生命周期,Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的组件类的实例。
生成的组件 Component | 创建时机 | 销毁时机 |
---|---|---|
ApplicationComponent | Application#onCreate() | Application#onDestroy() |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | 视图销毁时 |
ViewWithFragmentComponent | View#super() | 视图销毁时 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
注:ActivityRetainedComponent是用于ViewModle的,了解ViewModle的应该知道 ViewModel在页面配置更改后(例如屏幕旋转)仍然存在,因此它在第一次调用 Activity#onCreate() 时创建,在最后一次调用 Activity#onDestroy() 时销毁。
默认情况下,Hilt 中的所有绑定都未限定作用域,这意味着,每当应用注入时,Hilt 都会创建所需类型的一个新实例。
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
@Inject lateinit var analytics2: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt_test)
Log.d("TAG",analytics.hashCode().toString())
Log.d("TAG",analytics2.hashCode().toString())
}
...
}
运行代码会发现analytics和analytics2是两个不同的对象,但是如果想要analytics和analytics2在ExampleActivity指向同一个对象。那么就要对组件的作用域进行限定, Hilt 也允许将绑定的作用域限定为特定组件。Hilt 只为绑定作用域限定到的组件的每个实例创建一次限定作用域的绑定,对该绑定的所有请求共享同一实例。
Android 类 | 生成的组件 | 作用域 |
---|---|---|
Application | ApplicationComponent | @Singleton |
View Model | ActivityRetainedComponent | @ActivityRetainedScope |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
带有 @WithFragmentBindings 注释的 View | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
例如上面例子中将组件作用域限定为@ActivityScoped:
@ActivityScoped
class AnalyticsAdapter @Inject constructor() { ... }
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
@Inject lateinit var analytics2: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt_test)
Log.d("TAG",analytics.hashCode().toString())
Log.d("TAG",analytics2.hashCode().toString())
}
...
}
运行代码会发现analytics和analytics2是两个相同的对象,它们会在该Activity中保持单例。相应的,如果要使用全局的单例对象,将 AnalyticsService 的作用域限定为 ApplicationComponent即可。 Dagger 允许绑定作用域到特定组件,如上表所示,在指定组件范围内,实例都只会创建一次,并且对该绑定的所有请求都将共享同一实例。
有时,类不能通过构造函数注入,比如接口, 第三方库, Builder模式构造的对象等。在hilt中可以通过module模块向 Hilt 提供绑定信息, Hilt 模块是一个带有 @Module 注释的类。与 Dagger 模块一样,它会告知 Hilt 如何提供某些类型的实例。与 Dagger 模块不同的是, 必须使用 @InstallIn 为 Hilt 模块添加注解,以告知 Hilt 每个module模块将用在或安装在哪个 Android 类中。在模块中需要了解 如下三个注解:
在 Hilt 模块中提供的依赖项可以在生成的所有与 Hilt 模块安装到的 Android 类关联的组件中使用。
如果某个类来自外部库,如 Retrofit、OkHttpClient 或 Room 数据库等类,或者必须使用构建器模式创建实例,也无法通过构造函数@Inject注入。则可以为Hilt创建一个模块,在 Hilt 模块内创建一个函数,并使用 @Provides 为该函数添加注解提供对象。
class AnalyticsAdapter constructor()
@Module
@InstallIn(ActivityComponent::class)
class AnalyticsAdapterModule {
@Provides
fun provideAnalyticsAnalyticsAdapter(): AnalyticsAdapter {
return AnalyticsAdapter()
}
}
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject
lateinit var analytics: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt_test)
Log.d("TAG",analytics.hashCode().toString())
}
...
}
带有 @Provides注解的函数会向 Hilt 提供以下信息:
@Binds 注释会告知 Hilt 在需要提供接口的实例时要使用哪种实现。带有@Binds注解的函数会向 Hilt 提供以下信息:
例如上面例子中AnalyticsService 类是一个接口,则您无法通过构造函数注入它,而应向 Hilt 提供绑定信息,方法是在 Hilt 模块内创建一个带有 @Binds 注释的抽象函数。
interface IAnalyticsService {
fun analyticsMethods()
}
class AnalyticsServiceImpl @Inject constructor(): IAnalyticsService {
//实现接口方法
override fun analyticsMethods() {
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
@AndroidEntryPoint
class HiltModuleActivity : AppCompatActivity() {
@Inject
lateinit var iAnalyticsService: IAnalyticsService
private val textView: TextView by lazy { findViewById(R.id.textView_info) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test_activity_hilt_module)
textView.text = iAnalyticsService.hashCode().toString()
}
}
如果一个类有多个构造方法,那么依赖注入的时候要用到哪个初始化函数进行初始化呢?这时就要用到@Qualifiers限定注解了,Dagger2中@Named也可以实现该功能。
例如:有一个Car类在构造的时候传入name生成对应品牌的车辆,要造BYD和BMW的车,在注入Car的时候就可以用@Qualifiers注解进行区分:
class Car constructor(var brand: String) {
fun getCarBrand(): String = brand
}
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BMWCarQualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BYDCarQualifier
@Module
@InstallIn(ActivityComponent::class)
class CarModule {
@BMWCarQualifier
@Provides
fun provideBYDCar(): Car {
return Car("比亚迪")
}
@BYDCarQualifier
@Provides
fun provideBMWCar(): Car {
return Car("宝马")
}
}
@AndroidEntryPoint
class HiltModuleActivity : AppCompatActivity() {
@BMWCarQualifier
@Inject
lateinit var bmWcar: Car
@BYDCarQualifier
@Inject
lateinit var bydcar: Car
private val textView: TextView by lazy { findViewById(R.id.textView_info) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test_activity_hilt_module)
val sb = StringBuilder()
sb.append("car:" + bydcar.getCarBrand() + " hashCode:" + bydcar.hashCode().toString() + "\n")
sb.append("car:" + bmWcar.getCarBrand() + " hashCode:" + bmWcar.hashCode().toString() + "\n")
textView.text = sb.toString()
}
}
运行代码发现通过@Qualifiers注解成功的对依赖注入对象进行了分类。