Hilt 是基于 Dagger 开发的全新的依赖项注入代码库,它简化了 Android 应用中 Dagger 的调用方式。本文经过简短的代码片断为您展现其核心功能以帮助开发者们快速入门 Hilt。html
如需在应用中配置 Hilt,请先参考 Gradle Build Setup。android
完成安装所有的依赖和插件之后,仅需在您的 Application 类以前添加 @HiltAndroidApp 注解便可开始使用 Hilt,而无需其它操做。git
@HiltAndroidApp class App : Application()
当您写代码用到依赖项注入的时候,有两个要点须要考虑:github
而上述这两点并不互斥,并且在不少状况下,您的类既能够注入依赖项同时也包含依赖。api
使依赖项可注入缓存
若是须要在 Hilt 中使某个类变得可注入,您须要告诉 Hilt 如何建立该类的实例。该过程叫作绑定 (bindings)。框架
在 Hilt 中定义绑定有三种方式:ide
@Inject
注解;@Binds
注解;@Provides
注解。⮕ 在构造函数上使用 @Inject
注解函数
任何类的构造函数均可以添加 @Inject
注解,这样该类在整个工程中均可以做为依赖进行注入。测试
class OatMilk @Inject constructor() { ... }
⮕ 使用模块
在 Hilt 中另外两种将类转为可注入的方法是使用模块。
Hilt 模块 就好像 "菜谱",它能够告诉 Hilt 如何建立那些不具有构造函数的类的实例,好比接口或者系统服务。
此外,在您的测试中,任何模块均可以被其它模块所替代。这有利于使用 mock 替换接口实现。
模块经过 @InstallIn 注解被安装在特定的 Hilt 组件 中。这一部分我会在后面详细介绍。
选项 1: 使用 @Binds
为接口建立绑定
若是您但愿在须要 Milk
时候,使用 OatMilk
在代码中取而代之,那么能够在模块中建立一个抽象方法,而后为该方法添加 @Binds
注解。注意 OatMilk 自己必须是可注入的,仅需在 OatMilk 的构造函数上添加 @Inject
注解便可。
interface Milk { ... } class OatMilk @Inject constructor(): Milk { ... } @Module @InstallIn(ActivityComponent::class) abstract class MilkModule { @Binds abstract fun bindMilk(oatMilk: OatMilk): Milk }
选项 2: 使用 @Provides 来建立工厂函数
当实例没法被直接建立,您能够建立一个 provider。provider 就是能够返回对象实例的工厂函数。
一个典型的例子就是系统服务,好比 ConnectivityManager
,它们的实例须要经过 Context 对象来返回。
@Module @InstallIn(ApplicationComponent::class) object ConnectivityManagerModule { @Provides fun provideConnectivityManager( @ApplicationContext context: Context ) = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
只要使用注解 @ApplicationContext
或者 @ActivityContext
,Context
对象就是默承认注入的。
注入依赖
当依赖可注入后,您可使用 Hilt 经过两种方式:
⮕ 做为构造函数参数注入
interface Milk { ... } interface Coffee { ... } class Latte @Inject constructor( private val Milk milk, private val Coffee coffee ) { ... }
若是构造函数使用了注解 @Inject
,Hilt 会根据您为类型所定义的绑定来注入全部的参数。
⮕ 做为字段注入
interface Milk { ... } interface Coffee { ... } @AndroidEntryPoint class LatteActivity : AppCompatActivity() { @Inject lateinit var milk: Milk @Inject lateinit var coffee: Coffee ... }
若是类是入口点,这里特指使用了 @AndroidEntryPoint
注解的类 (后面章节会详细介绍),那么该类中全部包含 @Inject
注解的字段均会被注入。
使用 @Inject
注解的字段必须是 public 类型的。也能够添加 lateinit
来避免字段空值,由于它们在注入以前的初始值就是 null
。
请注意做为字段注入依赖项的场景仅仅适合类必须包含无参构造函数的状况,好比 Activity
。在大多数场景下,您更应经过构造函数的参数来注入依赖项。
入口点
还记得我在上文里提到,在不少状况下,您的类会在经过依赖注入建立的同时包含被注入的依赖项。有些状况下,您的类可能不是经过依赖项注入来建立,可是仍然会被注入依赖项。一个典型的例子就是 activity,它是由 Android 框架内部建立的,而不是由 Hilt 建立。
这些类属于 Hilt 依赖图谱的 入口点,并且 Hilt 须要知道这些类包含要注入的依赖。
⮕ Android 入口点
大部分入口点是所谓的 Android 入口点:
若是是 Android 入口点,请添加 @AndroidEntryPoint 注解。
@AndroidEntryPoint class LatteActivity : AppCompatActivity() { ... }
⮕ 其它入口点
Android 入口点对于大多数应用已经足够,可是若是您使用了不含有 Dagger 的库或者还没有在 Hilt 中支持的 Android 组件,那么您可能须要建立您本身的入口点来手动访问 Hilt 依赖图谱。详情请查看 将任意类转换为入口点。
ViewModel
ViewModel 是一个特例: 由于框架会建立它们,它既不是被直接实例化的,也不是 Android 入口点。ViewModel 须要使用特殊的 @HiltViewModel
注解,当 ViewModel
经过 byViewModels()
建立的时候,该注解使 Hilt 可以向 ViewModel 注入依赖,和其它类的 @Inject
注解的原理类似。
interface Milk { ... } interface Coffee { ... } @HiltViewModel class LatteViewModel @Inject constructor( private val milk: Milk, private val coffee: Coffee ) : ViewModel() { ... } @AndroidEntryPoint class LatteActivity : AppCompatActivity() { private val viewModel: LatteViewModel by viewModels() ... }
若是您须要访问 ViewModel
已缓存的状态,能够添加 @Assisted
注解,将 SavedStateHandle 做为构造函数参数进行注入。
@HiltViewModel class LatteViewModel @Inject constructor( @Assisted private val savedState: SavedStateHandle, private val milk: Milk, private val coffee: Coffee ) : ViewModel() { ... }
要使用 @ViewModelInject
,您可能须要添加更多依赖。更多详细内容请详见 Hilt 和 Jetpack 集成指南。
组件
各个模块都是安装在 Hilt 组件 中的,经过 @InstallIn(<组件名>)
指定。模块的组件主要用于防止意外将依赖注入到错误的位置。好比,@InstallIn(ServiceComponent.class)
能够防止注解所修饰的模块中的 binding 和 provider 被 activity 调用。
此外,binding 的做用域会被限制在组件所属的整个模块。也就是接下来咱们要讲的...
做用域
默认状况下,绑定都未被限定做用域。正如上面的示例,意味着每次注入 Milk
的时候,您均可以得到一个新的 OatMilk
实例。若是添加了 @ActivityScoped
注解,那么您会将绑定的做用域限制到 ActivityComponent
。
@Module @InstallIn(ActivityComponent::class) abstract class MilkModule { @ActivityScoped @Binds abstract fun bindMilk(oatMilk: OatMilk): Milk }
如今您的模块被限制做用域了,Hilt 在每一个 activity 实例中仅建立一个 OatMilk 实例。此外,OatMilk 实例会绑定到 activity 的生命周期中——当 activity 的 onCreate() 被调用的时候,它会被建立,而当 activity 的 onDestroy() 被调用的时候,它会被销毁。
@AndroidEntryPoint class LatteActivity : AppCompatActivity() { @Inject lateinit var milk: Milk @Inject lateinit var moreMilk: Milk //这里的实例和上面的相同 ... }
在本例中,milk
和 moreMilk
指向同一个 OatMilk
实例。然而,若是您有多个 LatteActivity
实例,它们会包含各自的 OatMilk
实例。
相应的,其它被注入到该 activity 的依赖,它们的做用域是一致的。所以它们也会引用到相同的 OatMilk
实例:
// Milk 实例的建立会在 Fridge 存在以前,由于它被绑定到了 activity 的生命周期中 class Fridge @Inject constructor(private val Milk milk) { ... } @AndroidEntryPoint class LatteActivity : AppCompatActivity() { // 下面四项共享了同一个 Milk 实例 @Inject lateinit var milk: Milk @Inject lateinit var moreMilk: Milk @Inject lateinit var fridge: Fridge @Inject lateinit var backupFridge: Fridge ... }
做用域依赖于您的模块所安装的组件,好比 @ActivityScoped
仅仅用于在 ActivityComponent
安装的模块内的绑定。
做用域一样决定了注入实例的生命周期: 在本例中,被 Fridge
和 LatteActivity
使用的 Milk 的单独实例会在 LatteActivity
的 onCreate()
被调用的时候被建立——而当 onDestroy() 被调用的时候被销毁。这也意味着当配置发生改变的时候,Milk 不会 "幸免",由于配置发生改变的时候会调用 activity 的 onDestroy()。您能够经过使用生命周期更长的做用域来避免该问题,好比使用 @ActivityRetainedScope
。
若是想要了解可用的做用域列表、相关的组件以及所遵循的生命周期,请参见 Hilt 组件。
Provider 注入
有些时候您但愿可以更加直接地控制注入实例的建立。好比,您可能但愿基于业务逻辑,注入某个类型的一个实例或者几个实例。针对这样的场景,您可使用 dagger.Provider:
class Spices @Inject constructor() { ... } class Latte @Inject constructor( private val spiceProvider: Provider<Spices> ) { fun addSpices() { val spices = spiceProvider.get()// 建立 Spices 的新实例 ... } }
provider 注入能够忽略具体的依赖类型以及注入的方式。任何可被注入的内容都可以封装在 Provider<...>
中来使用 provider 注入的方式。
依赖注入框架 (像 Dagger 和 Guice) 一般被用于大型且复杂的项目。而 Hilt 既容易上手,配置起来又很是简单,同时做为独立的代码包,还兼顾了 Dagger 中可被各类类型应用,不管代码规模大小,都可兼容的强大特性。
若是您但愿了解更多关于 Hilt 的内容、它的工做原理,以及其它对您来讲有用的特性,请移步官方网站,了解更多详细的介绍和参考文档。