关于android:Hilt-介绍-MAD-Skills

295次阅读

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

本文是 MAD Skills 系列 中无关 Hilt 的第一篇文章!在本文中,咱们将探讨依赖项注入 (DI) 对利用的重要性,以及 Jetpack 举荐的 Android DI 解决方案——Hilt。

如果您更喜爱通过视频理解此内容,能够 点击这里 查看。

在 Android 利用中,您能够通过遵循依赖项注入的准则,为良好的利用架构奠定根底。这有助于重用代码、易于重构、易于测试!更多对于 DI 的益处,请参阅: Android 中的依赖项注入。

在我的项目中创立类的实例时,您能够通过提供及传递所需依赖项,手动解决依赖关系图。

然而每次都手动执行会减少模版代码并且容易出错。以 iosched 我的项目 (Google I/O 开源利用) 中的一个 ViewModel 为例,您能设想创立一个 FeedViewModel 所需的依赖项及传递依赖项须要多大的代码量吗?

class FeedViewModel(
    private val loadCurrentMomentUseCase: LoadCurrentMomentUseCase,
    loadAnnouncementsUseCase: LoadAnnouncementsUseCase,
    private val loadStarredAndReservedSessionsUseCase: LoadStarredAndReservedSessionsUseCase,
    getTimeZoneUseCase: GetTimeZoneUseCase,
    getConferenceStateUseCase: GetConferenceStateUseCase,
    private val timeProvider: TimeProvider,
    private val analyticsHelper: AnalyticsHelper,
    private val signInViewModelDelegate: SignInViewModelDelegate,
    themedActivityDelegate: ThemedActivityDelegate,
    private val snackbarMessageManager: SnackbarMessageManager
) : ViewModel(),
    FeedEventListener,
    ThemedActivityDelegate by themedActivityDelegate,
    SignInViewModelDelegate by signInViewModelDelegate {/* ... */}

这是简单且机械化的,并且咱们很容易弄错依赖关系。依赖项注入库能够让咱们利用 DI 的劣势,而无需手动提供依赖关系,因为库会帮您生成所有须要的代码。这也就是 Hilt 发挥作用的中央。

Hilt

Hilt 是一个由 Google 开发的依赖项注入库,它通过解决简单的依赖关系并为您生成本来须要手动编写的模版代码,帮忙您在利用中充分利用 DI 的最佳实际。

Hilt 通过应用注解在编译期帮您生成代码,来保障运行时性能。这是利用 JVM DI 库 Dagger 的能力实现的,而 Hilt 是基于 Dagger 构建的。

Hilt 是 Jetpack 举荐的 Android 利用 DI 解决方案,它附带工具并且反对其余 Jetpack 库。

疾速开始

所有应用 Hilt 的利用都必须蕴含被 @HiltAndroidApp 注解的 Application 类,它会在编译期触发 Hilt 的代码生成。为了 Hilt 能将依赖项注入到 Activity 中,Activity 须要应用 @AndroidEntryPoint 注解。

@HiltAndroidApp
class MusicApp : Application()

@AndroidEntryPoint
class PlayActivity : AppCompatActivity() { /* ... */}

注入一个依赖项时,须要在您心愿注入的变量上增加 @Inject 注解。super.onCreate 被调用后,所有 Hilt 注入的变量都将可用。

@AndroidEntryPoint
class PlayActivity : AppCompatActivity() {

  @Inject lateinit var player: MusicPlayer

  override fun onCreate(savedInstanceState: Bundle) {super.onCreate(bundle)
    player.play("YHLQMDLG")
  }
}

在本案例中,咱们向 PlayActivity 内注入 MusicPlayer,然而 Hilt 是如何晓得怎么提供 MusicPlayer 类型的实例呢?还须要额定的工作!咱们还须要通知 Hilt 如何解决,当然还是应用注解!

在类的构造方法上增加 @Inject 注解,通知 Hilt 怎么创立该类的实例。

class MusicPlayer @Inject constructor() {fun play(id: String) {...}
}

这就是将依赖项注入到 Activity 中所需的全部内容!非常简单!咱们从一个简略的例子开始,因为 MusicPlayer 并不依赖任何其余类型。然而如果咱们将其余依赖作为参数传递,Hilt 会在提供 MusicPlayer 的实例时解决并满足这些依赖项。

实际上,这是一个非常简单高级的例子。然而如果您必须手动实现咱们上述工作,您会怎么做?

手动实现

手动执行 DI 时,您须要一个依赖项容器,它负责提供类型的实例并治理这些实例的生命周期。简略的说,这些就是 Hilt 在幕后所做的内容。

当咱们在 Activity 上增加 @AndroidEntryPoint 注解时,Hilt 会主动创立一个依赖项容器,并治理、关联到 PlayActivity 上。这里咱们手动实现 PlayActivityContainer 容器。通过在 MusicPlayer 上增加 @Inject 注解,等同于通知容器如何提供 MusicPlayer 的实例。

// PlayActivity 已被增加 @AndroidEntryPoint 注解
class PlayActivityContainer {

  // MusicPlayer 已被增加 @Inject 注解
  fun provideMusicPlayer() = MusicPlayer()

}

在 Activity 中,咱们须要创立一个容器实例,并应用它对 Activity 的依赖项赋值。对于 Hilt 而言,在 Activity 上增加 @AndroidEntryPoint 注解时也实现了容器实例的创立。

class PlayActivity : AppCompatActivity() {

  private lateinit var player: MusicPlayer

  // 在 Activity 上增加 @AndroidEntryPoint 注解时由 Hilt 创立
  private lateinit var container: PlayActivityContainer


  override fun onCreate(savedInstanceState: Bundle) {

    // @AndroidEntryPoint 同样为您创立并填充字段
    container = PlayActivityContainer()
    player = container.provideMusicPlayer()

    super.onCreate(bundle)
    player.play("YHLQMDLG")
  }
}

注解回顾

至此,咱们曾经看见,当 @Inject 注解被增加到类的构造函数上时,它会通知 Hilt 如何提供该类的实例。当变量被增加 @Inject 注解,并且变量所属的类被增加 @AndroidEntryPoint 注解时,Hilt 会向该类中注入一个相应类型的实例。

@AndroidEntryPoint 注解能够增加到绝大部分 Android 框架类上,不仅仅是 Activity。它会为被增加注解的类去创立一个依赖项容器的实例,并填充所有增加了 @Inject 注解的变量。

Application 类上增加 @HiltAndroidApp 注解,除了触发 Hilt 生成代码之外,还创立了一个与 Application 关联的依赖项容器。

Hilt 模块

咱们既然曾经理解了 Hilt 根底,那一起来进步示例的复杂性吧。当初,MusicPlayer 的构造函数中,须要一个依赖项 MusicDatabase

class MusicPlayer @Inject constructor(private val db: MusicDatabase) {fun play(id: String) {...}
}

因而,咱们须要通知 Hilt 如何提供 MusicDatabase 实例。当类型是一个接口,或者您无奈在构造函数上增加 @Inject,例如类来自于您无奈批改的库。

假如咱们在利用中 应用 Room 作为持久性存储库。回到咱们手动实现 PlayActivityContainer 的场景中,当咱们通过 Room 提供 MusicDatabase 时,这将是一个抽象类,咱们心愿在提供依赖项时执行一些代码。接下来,当提供 MusicPlayer 的实例时,咱们须要调用提供或者满足 MusicDatabase 依赖项的办法。

class PlayActivityContainer(val context: Context) {fun provideMusicDatabase(): MusicDatabase {
    return Room.databaseBuilder(context, MusicDatabase::class.java, "music.db").build()}

  fun provideMusicPlayer() = MusicPlayer(provideMusicDatabase()
  )
}

在 Hilt 中咱们无需放心传递依赖,因为它会主动关联所有须要传递的依赖项。然而,咱们须要让 Hilt 晓得如何提供 MusicDatabase 类型的实例。为此,咱们应用 Hilt 模块。

Hilt 模块是一个被增加了 @Module 注解的类。在该类中,咱们能够实现函数来通知 Hilt 如何提供确切类型的实例。Hilt 已知的此类信息在行业内也被称为绑定。

@Module
@InstallIn(SingletonComponent::class)
object DataModule {

  @Provides
  fun provideMusicDB(@ApplicationContext context: Context): MusicDatabase {
    return Room.databaseBuilder(context, MusicDatabase::class.java, "music.db").build()}
}

在该函数上增加 @Provides 注解用来通知 Hilt 如何提供 MusicDatabase 类型的实例。函数体蕴含 Hilt 须要执行的代码块,这与咱们手动实现完全一致。

返回类型 MusicDatabase 告知 Hilt 此函数提供什么类型。函数的参数通知 Hilt 该类型所需的依赖项。本案例中,ApplicationContext 曾经在 Hilt 中可用。这段代码告知 Hilt 如何提供 MusicDatabase 类型的实例,换句话说,咱们曾经有了一个 MusicDatabase 绑定

Hilt 模块还须要增加 @InstallIn 注解,用来示意这些信息在哪些依赖项容器或者组件中可用。然而什么是组件?咱们来介绍更多细节。

Hilt 组件

组件是 Hilt 生成的一个类,负责提供类型的实例,就像咱们手动实现的容器一样。在编译期,Hilt 遍历依赖关系图,并生成代码,来提供所有类型并携带它们的传递依赖项。

△ 组件是一个 Hilt 生成的类,负责提供类型的实例

Hilt 为绝大多数 Android 框架类生成组件 (或称为依赖项容器)。每个组件关联信息 (或称为绑定) 通过组件层次结构向下传递。

△ Hilt 的组件层次结构

如果 MusicDatabase 的绑定在 SingletonComponent (对应 Application 类 ) 中是可用的,那么绑定在其余组件中也可用。

当您在 Android 框架类上增加 @AndroidEntryPoint 注解时,Hilt 将在编译期主动生成组件,并实现组件的创立、治理以及关联到与之对应的类中。

模块的 @InstallIn 注解用于管制这些绑定的可用地位,以及它们能够应用哪些其余绑定。

限定作用域

回到手动创立 PlayActivityContainer 的代码中,您是否意识到一个问题?每次须要 MusicDatabase 依赖项时,咱们都会创立一个不同的实例。

class PlayActivityContainer(val context: Context) {fun provideMusicDatabase(): MusicDatabase {
    return Room.databaseBuilder(context, MusicDatabase::class.java, "music.db").build()}

  fun provideMusicPlayer() = MusicPlayer(provideMusicDatabase()
  )
}

这并不是咱们想要的,因为咱们可能心愿在整个利用中重用雷同的 MusicDatabase 实例。咱们能够通过持有一个变量来共享雷同的实例,而不是一个函数。

class PlayActivityContainer {

  val musicDatabase: MusicDatabase =
    Room.databaseBuilder(context, MusicDatabase::class.java, "music.db").build()

  fun provideMusicPlayer() = MusicPlayer(musicDatabase)
}

基本上咱们会将 MusicDatabase 类型的作用域限定到该容器中,因为咱们总是会提供雷同的实例作为依赖项。如何通过 Hilt 来实现这一点呢?好吧,毫无疑问,应用另一个注解!

在增加了 @Provides 注解的办法上,咱们能够通过应用 @Singleton 注解来通知 Hilt 组件总是共享该类型的雷同实例。

@Module
@InstallIn(SingletonComponent::class)
object DataModule {

  @Singleton  
  @Provides
  fun provideMusicDB(@ApplicationContext context: Context): MusicDatabase {
    return Room.databaseBuilder(context, MusicDatabase::class.java, "music.db").build()}
}

@Singleton 是一个作用域注解。每一个 Hilt 组件都有与之关联的作用域注解。

△ 不同 Hilt 组件的作用域注解

如果您想要限定一个类型的作用域为 ActivityComponent,您须要应用 ActivityScoped 注解。这些注解不仅能够在模块中应用,还能够增加到类上,前提是该类的构造方法曾经被增加 @Inject 注解。

绑定

有两种类型的绑定:

  • 未限定作用域绑定 : 没有增加作用域注解的绑定,例如 MusicPlayer,如果它们没有被装载到模块中,则所有组件都能够应用这些绑定。
  • 限定作用域绑定 : 增加了作用域注解的绑定,例如 MusicDatabase,以及被装载到模块中的未限定作用域绑定,只有对应组件及其组件层次结构下方组件能够应用这些绑定。

Jetpack 扩大

Hilt 能够与最风行的 Jetpack 库的集成应用: ViewModel、Navigation、Compose 以及 WorkManager。

除了 ViewModel,每个集成都须要在我的项目中增加不同的库。获取更多信息,请查阅: Hilt 和 Jetpack 集成。您还记得咱们在文章结尾看到的 iosched 中的 FeedViewModel 代码吗?您想看看应用 Hilt 反对之后的成果吗?

@HiltViewModel
class FeedViewModel @Inject constructor(
    private val loadCurrentMomentUseCase: LoadCurrentMomentUseCase,
    loadAnnouncementsUseCase: LoadAnnouncementsUseCase,
    private val loadStarredAndReservedSessionsUseCase: LoadStarredAndReservedSessionsUseCase,
    getTimeZoneUseCase: GetTimeZoneUseCase,
    getConferenceStateUseCase: GetConferenceStateUseCase,
    private val timeProvider: TimeProvider,
    private val analyticsHelper: AnalyticsHelper,
    private val signInViewModelDelegate: SignInViewModelDelegate,
    themedActivityDelegate: ThemedActivityDelegate,
    private val snackbarMessageManager: SnackbarMessageManager
) : ViewModel(),
    FeedEventListener,
    ThemedActivityDelegate by themedActivityDelegate,
    SignInViewModelDelegate by signInViewModelDelegate {/* ... */}

为了让 Hilt 晓得如何提供该 ViewModel 的实例,咱们不仅要在构造函数上增加 @Inject 注解,还须要对这个类增加 @HiltViewModel 注解。

就是这样,Hilt 会帮忙您创立 ViewModel 的提供程序,您无需再手动解决。

理解更多

Hilt 基于另一个风行的依赖注入库 Dagger 进行构建!在接下来的文章中,Dagger 将会被频繁提及!如果您正在应用 Dagger,Dagger 能够与 Hilt 配合应用,请查看咱们之前的文章《从 Dagger 迁徙到 Hilt 可带来的收益》。无关 Hilt 的更多信息,您能够参阅以下资源:

  • Dagger 基础知识
  • 迁徙到 Hilt 指南
  • Hilt 及 Dagger 注解的区别及应用形式的备忘录
  • 应用 Hilt 实现依赖项注入
  • Codelab: 在 Android 利用中应用 Hilt

以上是本文的全部内容,咱们行将推出更多 MAD Skills,敬请关注后续更新。

欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!

正文完
 0