Hilt
目前支持注入的 Android
类共有如下 6 种:
Application
(通过使用@HiltAndroidApp
)Activity
Fragment
View
Service
BroadcastReceiver
除 Application
需要 @HiltAndroidApp
修饰外,其他需要依赖注入的类都需要添加 @AndroidEntryPoint
注解。
@AndroidEntryPoint
class MainHiltActivity: AppCompatActivity() {
}
@Inject
接下来就可以通过 @Inject
来执行注入了:
@AndroidEntryPoint
class MainHiltActivity: AppCompatActivity() {
@Inject
lateinit var capBean: CapBean
}
到这里还并不能完成 capBean
字段的注入,这时 Hilt
并不知道如何创建实例对象。需要在构造函数上添加 @Inject
注解。这时 Hilt
就知道如何创建需要注入的实例对象了。
class CapBean @Inject constructor()
同理如果需要注入对象的构造函数中带有参数那么也需要对所需参数的构造函数添加 @Inject
注解。
class CapBean @Inject constructor(val waterBean: WaterBean)
class WaterBean @Inject constructor()
@Module
有些情况无法通过在构造函数中添加 @Inject
注解的方式来告知 Hilt
如何提供该类或接口的实例,比如:
- 接口。
- 来自外部库的类。
可以通过 @Module
注解来完成。@Module
注解会生成 Hilt
模块,在模块中提供具体的实现。
@Binds
注入接口实例,需要通过 @Module + @Binds
注解的方式。
首先定义一个接口:
interface Water {
fun drink()
}
然后定义接口的实现类:
class Milk @Inject constructor() : Water {
override fun drink() {
Log.e(“water”, “drink milk”)
}
}
然后定义一个抽象类添加 @Module
注解:
@Module
@InstallIn(ApplicationComponent::class)
abstract class WaterModule {
}
在抽象类中创建一个带 @Binds
注释的抽象函数:
@Module
@InstallIn(ApplicationComponent::class)
abstract class WaterModule {
@Binds
abstract fun bindsWater(milk: Milk): Water
}
带有 @Binds
注解的函数会向 Hilt
提供以下信息:
- 函数返回类型会告知
Hilt
函数提供哪个接口的实例。 - 函数参数会告知
Hilt
要提供哪种实现。
到这里 Hilt
就知道如何将接口的具体实现注入了。
@AndroidEntryPoint
class MainHiltActivity: AppCompatActivity() {
@Inject
lateinit var water: Water
}
@Provides
如果某个类不归你所有,也无法通过构造函数注入。可以通过 @Module + @Provides
注解告诉 Hilt
如何提供此类型的实例。
首先定义一个 @Module
注解修饰的普通类,这里是普通类而不是抽象类,因为我们要提供类实例的具体实现方式。以 OkHttpClient
为例:
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
}
创建函数添加 @Provides
注解,函数体提供 OkHttpClient
对象实例的具体创建:
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
}
}
带有 @Provides
注解的函数会向 Hilt
提供以下信息:
- 函数返回类型会告知
Hilt
函数提供哪个类型的实例。 - 函数参数会告知
Hilt
相应类型的依赖项。 - 函数主体会告知
Hilt
如何提供相应类型的实例。每当需要提供该类型的实例时,Hilt
都会执行函数主体。
这时就可以通过 Hilt
注
入了:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var okHttpClient: OkHttpClient
…
}
@Qualifier
在上面我们通过 @Binds
提供了 Water
接口的 Milk
实现,并成功注入到了类中。那如果在调用类中不仅需要 Milk
实现还需要另一个 Water
接口的实现 Juice
。要怎么处理呢?
先添加 Juice
实现:
class Juice @Inject constructor() : Water {
override fun drink() {
Log.e(“water”, “drink juice”)
}
}
依然要添加到 @Module
模块中:
@Module
@InstallIn(ActivityComponent::class)
abstract class WaterModule {
@Binds
abstract fun bindsMilk(milk: Milk): Water
@Binds
abstract fun bindsJuice(juice: Juice): Water
}
到这里直接注入调用类中还不可以:
@AndroidEntryPoint
class MainHiltActivity: AppCompatActivity() {
@Inject
lateinit var juice: Water
@Inject
lateinit var milk: Water
}
Hilt
没有办法区分两个 Water
实例的不同的,这时需要 @Qualifier
来定义注解区分相同类型的不同实例了。
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MilkWater
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MilkWater
然后将定义的两个注解添加到 WaterModule
的函数中,以告诉 Hilt
如何提供同一实例的不同实例:
@Module
@InstallIn(ActivityComponent::class)
abstract class WaterModule {
@MilkWater
@Binds
abstract fun bindsMilk(milk: Milk): Water
@JuiceWater
@Binds
abstract fun bindsJuice(juice: Juice): Water
}
在调用类中也需要添加我们刚才定义的注解来区分两个实例的不同:
@AndroidEntryPoint
class MainHiltActivity: AppCompatActivity() {
@JuiceWater
@Inject
lateinit var juice: Water
@MilkWater
@Inject
lateinit var milk: Water
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_hilt)
juice.drink()
milk.drink()
}
}
输出如下:
E/water: drink juice
E/water: drink milk
@ApplicationContext、@ActivityContext
这是 Hilt
提供了一些预定义的限定符。
@ApplicationContext
:提供Application
的Context
。@ActivityContext
:提供Activity
的Context
。
Hilt 组件
Hilt
组件的作用是将 Hilt
提供的实例注入到相应的 Android
类。Hilt
组件使用如下:
@Module
@InstallIn(ActivityComponent::class)
object MainModule {
@Provides
fun provideCoffeeBean(): CoffeeBean {
return CoffeeBean()
}
}
由于 @InstallIn
注解中设置的组件为 ActivityComponent
,表示 Hilt
将通过 MainModule
为 Activity
提供的实例。
@Installin
@Installin
注解如下:
@Retention(CLASS)
@Target({ElementType.TYPE})
@GeneratesRootInput
public @interface InstallIn {
Class<?>[] value();
}
@Installin
包含一个字段,字段可用值为 Hilt
提供的组件,这些组件代表要注入的目标 Android
类。如下表:
Hilt 组件 | 注入器面向的对象 |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | 带有 @WithFragmentBindings 注释的 View |
ServiceComponent | Service |
组件生命周期
Hilt
会按照相应 Android
类的生命周期自动创建和销毁生成的组件类的实例。
组件作用域
默认情况下,Hilt
中所有的实例都未设置限定作用域。也就是说,每次注入依赖时 Hilt
都会提供新的实例。如下:
class UserBean @Inject constructor()
@AndroidEntryPoint
class MainHiltActivity: AppCompatActivity() {
@Inject
lateinit var firstUserBean: UserBean
@Inject
lateinit var secondUserBean: UserBean
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_hilt)
Log.e(“hilt”, firstUserBean.toString())
Log.e(“hilt”, secondUserBean.toString())
}
}
输出如下:
E/hilt: com.sample.hilt.UserBean@b01decb
E/hilt: com.sample.hilt.UserBean@dafe6a8
通过设置作用域注解,可以在相应 Android
类中共享同一实例。如下:
// 设置 UserBean 的作用域范围为 Activity
类
@ActivityScoped
onCreate(savedInstanceState)
setContentView(R.layout.activity_main_hilt)
Log.e(“hilt”, firstUserBean.toString())
Log.e(“hilt”, secondUserBean.toString())
}
}
输出如下:
E/hilt: com.sample.hilt.UserBean@b01decb
E/hilt: com.sample.hilt.UserBean@dafe6a8
通过设置作用域注解,可以在相应 Android
类中共享同一实例。如下:
// 设置 UserBean 的作用域范围为 Activity
类
@ActivityScoped