随着Jetpack Compose正式发布,越来越多的Android开发者开始尝试在项目中使用这个现代化的UI工具箱。与传统的View System相比,Compose提供了声明式UI、无需过度操作View的API等优势。但在实际项目开发中,我们也需要处理依赖注入、异步编程等基础架构的需求。本文将介绍如何在Compose项目中使用Hilt进行依赖注入。
什么是Hilt?
Hilt是Google官方推出的一款基于Dagger
的依赖注入库,专门面向Android的特性进行了优化。相比Dagger
,Hilt
更容易上手和集成,并且对Android类进行了专门绑定,无需手动编写模块。
在Compose项目中配置Hilt
- 在项目根
build.gradle.kts
中添加Hilt
插件:
bash
plugins {
...
id("com.google.dagger.hilt.android") version "2.51" apply false
}
- 在app级别的
build.gradle.kts
中应用Hilt
插件并添加相关依赖:
scss
plugins {
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
}
android {
...
}
dependencies {
...
// Dagger - Hilt
implementation("com.google.dagger:hilt-android:2.51")
kapt("com.google.dagger:hilt-compiler:2.51")
}
- 在Application类中注册
HiltAndroidApp
:
kotlin
@HiltAndroidApp
class MyApplication: Application() {
...
}
至此,基本配置就完成了。
在Compose中使用Hilt
- 创建接口和实现类
假设我们需要创建一个显示问候语的Repository
:
kotlin
interface GreetingRepository {
fun getGreeting(): String
}
@Singleton // 标记为单例
class DefaultGreetingRepository @Inject constructor(): GreetingRepository {
override fun getGreeting() = "Hello Compose + Hilt!"
}
- 依赖注入
在Composable函数中,我们可以使用@AndroidEntryPoint
获取Hilt
依赖:
less
@AndroidEntryPoint
@Composable
fun GreetingScreen(
greetingRepository: GreetingRepository = hiltViewModel() // 注入GreetingRepository
) {
Text(greetingRepository.getGreeting())
}
对于在其他的Android类(如Activity/Fragment
)中使用依赖的场景,也可以使用@AndroidEntryPoint
和@Inject
的方式进行注入,这里不再赘述。
- 视图绑定 除了上面提到的
ViewModel
或Repository
注入,Hilt
也支持给Composable
函数提供视图模型。这在处理复杂UI逻辑时非常有用:
kotlin
@HiltViewModel
class GreetingViewModel @Inject constructor(
private val repository: GreetingRepository
) : ViewModel() {
val greeting: String = repository.getGreeting()
}
@AndroidEntryPoint
@Composable
fun GreetingScreen(
viewModel: GreetingViewModel = hiltViewModel() // 注册ViewModel
) {
Text(viewModel.greeting)
}
小结
本文简要介绍了在Compose项目中集成Hilt的基本步骤,并演示了如何使用Hilt注入Repository和ViewModel。与传统的手动依赖注入相比,Hilt能够减少很多模板代码,提高开发效率。它与Compose的搭配使用,有助于保持页面代码的干净和可维护性。
下面让我们接着上文来深入探讨一下Hilt的原理,以及在Hilt出现之前Android项目是如何处理依赖注入的,并对比一下Hilt和其他类似依赖注入库的优缺点及使用场景。
Hilt的实现原理
Hilt 本质上是一个基于 Dagger2 的封装和扩展。Dagger2是一个功能强大但相对底层的依赖注入框架,需要手动编写大量模块化代码。Hilt 在此基础上做了以下的改进:
- Android 类自动生成 :Hilt利用了 Dagger2 的编译时注解处理能力,能够自动生成 Android (如
Activity/Fragment
)对应的绑定代码,省去了手动编写模块的步骤。 - 预定义组件 :Hilt 内置了几个关键的组件,如
ApplicationComponent
和ActivityRetainedComponent
等。开发者无需手动创建这些组件。 - 组件层次结构 :Hilt 闱开发者预先设计好了组件的层级结构,并将 Android 类的这些组件绑定。例如 Activity 的一来就被绑定到
ActivityComponent
。 - 依赖作用域:Hilt 利用作用域注解确保注入的对象生命周期与宿主组件相同,避免了内存泄露问题。
- 优化注入过程 :Hilt 对注入过程做了优化,使其所需编码更少,只需在对应类上添加
@AndroidEntryPoint
或@HiltViewModel
注解即可。
总的来说,Hilt 在保留了 Dagger2 强大能力的同时,精简和优化了 Android 平台上的使用体验,让依赖注入变得更易上手。
Hilt之前的依赖注入方案
在 Hilt 出现之前,Android 开发者主要使用以下方式进行依赖注入:
- 手动实例化 :最传统的做法就是在需要的地方
new
出对应的对象实例,这种方式简单但难以维护和单元测试。 - 服务定位器(Service Locator) :将对象的创建和获取集中到一个
Locator
类中,通过静态方法获取对象。虽然简化了实例化流程,但作用域管理仍然存在问题。
kotlin
object ServiceLocator {
private val greetingRepo = DefaultGreetingRepository()
fun getGreetingRepository(): GreetingRepository {
return greetingRepo
}
}
// 使用时:
val repo = ServiceLocator.getGreetingRepository()
- 手动 Dagger2:直接使用 Dagger2 框架,需要手写非常多模块化代码,侵入性强,学习成本高。
kotlin
// 定义模块
class GrettingModule {
@Provides
fun provideGreetingRepository(): GreetingRepository {
return DefaultGreetingRepositoryy()
}
}
// 定义 Component
@Singleton
@Component(modules = [GreetingModule::class])
interface GreetingComponent {
fun getGreetingRepository(): GreetingRepository
}
// 使用时需要先获取 Component 实例
val component = DaggerGteetingComponent.builder().build()
val repo = component.getGreetingRepository()
- 其他 DI 库 :例如
Koin
、Kodein
等,都需要一定的集成成本。
这些传统方案各有缺陷,有的存在内存泄露问题,有的编码量大且不易维护等。这就是 Google 决定推出 Hilt 的主要原因。
kotlin
// 定义模块
val appModule = module {
single<GreetingRepository> { DefaultGreetingRepository() }
}
// 注入 Koin
startKoin {
androidContext(this@MyApp)
modules(appModule)
}
// 使用时通过 get()获取
val repo: GreetingRepository = get()
Hilt 与其他 DI 库的比较
除了 Google 自家的 Hilt,还有一些其他依赖注入库也可以在 Android 项目中使用,比如 Koin、Kodein 等。我们来对比一下它们的优缺点:
Hilt:
- 优点:继承简单、完全基于 Dagger2提供可靠性保证、与 Jetpack 高度集成、内存安全。
- 缺点:不太灵活,必须遵循 Hilt 的规范,如果想要自定义相对比较困难。
Koin:
- 优点:使用 Kotlin DSL 非常轻量简洁,对 Java 项目也有支持。
- 缺点:功能没有 Hilt 全面,不支持注解绑定,可能存在内存泄露风险。
Kodein:
- 优点:轻量级,零渗透设计,易于集成和使用。
- 缺点:相比 Koin 功能偏少,项目维护不太活跃,对大型项目支持较弱。
总结:
- 对于只需要轻量级依赖注入的小型项目,可以考虑 Koin 或 Kodein。
- 对于稳定性和性能有要求的大中型项目,尤其是使用 Jetpack 等官方库的,Hilt 无疑是更好的选择。
- 如果有复杂的自定义需求,仍然可以直接使用 Dagger2。
使用场景分析
不同的依赖注入方案在不同场景下有不同的使用考量:
- 新项目:如果是全新的项目,建议直接使用 Hilt,它集成简单、使用方便,且有 Google 的长期支持保证。
- 已有项目:如果项目中已经使用了其他 DI 库,短期内切换到 Hilt 的成本可能较高。除非对稳定性和性能有非常大的需求,否则可以暂时维持现有方案。
- 小型项目 :对于小型 Demo 或简单功能的 App,直接手动
new
对象实例未尝不可,过度使用 DI 反而增加了复杂度和学习成本。 - 大型项目:大型项目对于可维护性和可测实性的要求更高,合理使用 Hilt 将极大地提升代码质量和架构质量。
- 多平台项目:如果设计多平台开发,Hilt 主要面向 Android 平台,可以考虑更加通用的方案如手动 Dagger2.
总之,在选择依赖注入解决方案时,需要结合项目的实际需求、开发人员的掌握程度、现有架构等因素进行权衡。切勿为了使用而使用,增加不必要的复杂度。对于大多数 Android 开发人员来说,Hilt 可以说是当下最优雅且易于上手的 DI 方案了。