关于kotlin:Kotlin-中使用-Hilt-的开发实践

15次阅读

共计 5259 个字符,预计需要花费 14 分钟才能阅读完成。

Hilt 是基于 Dagger 开发的全新的依赖项注入代码库,它简化了 Android 利用中 Dagger 的调用形式。本文通过简短的代码片段为您展现其外围性能以帮忙开发者们疾速入门 Hilt。

配置 Hilt

如需在利用中配置 Hilt,请先参考 Gradle Build Setup。

实现装置全副的依赖和插件当前,仅需在您的 Application 类之前增加 @HiltAndroidApp 注解即可开始应用 Hilt,而无需其它操作。

@HiltAndroidApp
class App : Application()

定义并且注入依赖项

当您写代码用到依赖项注入的时候,有两个要点须要思考:

  1. 您须要注入依赖项的类;
  2. 能够作为依赖项进行注入的类。

而上述这两点并不互斥,而且在很多状况下,您的类既能够注入依赖项同时也蕴含依赖。

使依赖项可注入

如果须要在 Hilt 中使某个类变得可注入,您须要通知 Hilt 如何创立该类的实例。该过程叫做绑定 (bindings)。

在 Hilt 中定义绑定有三种形式:

  1. 在构造函数上增加 @Inject 注解;
  2. 在模块上应用 @Binds 注解;
  3. 在模块上应用 @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 或者 @ActivityContextContext 对象就是默认可注入的。

注入依赖

当依赖可注入后,您能够应用 Hilt 通过两种形式:

  1. 作为构造函数的参数注入;
  2. 作为字段注入。

⮕ 作为结构函数参数注入

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 入口点:

  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

如果是 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 // 这里的实例和下面的雷同

  ...
}

在本例中,milkmoreMilk 指向同一个 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 装置的模块内的绑定。

作用域同样决定了注入实例的生命周期: 在本例中,被 FridgeLatteActivity 应用的 Milk 的独自实例会在 LatteActivityonCreate() 被调用的时候被创立——而当 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 的内容、它的工作原理,以及其它对您来说有用的个性,请移步官方网站,理解更多具体的介绍和参考文档。

正文完
 0