Dagger/Hilt 依赖注入:简化 Android DI

FreeGuideOnline 最新 2026-06-17

Dagger/Hilt 依赖注入:简化 Android DI

依赖注入(DI)是现代 Android 开发的基础技术之一,它能帮助我们构建松耦合、易测试、可维护的应用。本教程将从零开始,带你掌握 Google 推荐的 DI 方案 —— Hilt,并理解其背后的 Dagger 原理。无需预备知识,我们将用最直观的例子带你上手。


1. 为什么需要依赖注入?

在开发中,一个类经常需要依赖其他类的实例才能工作。比如 Car 需要 Engine

class Car {
    private val engine = Engine()
    fun start() {
        engine.ignite()
    }
}

这种直接 new 的方式存在几个问题:

  • 强耦合CarEngine 的具体实现绑定,无法灵活替换(如换成 ElectricEngine)。
  • 测试困难:无法模拟 Engine 进行单元测试。
  • 生命周期管理复杂:多次创建相同实例,内存浪费或状态不一致。

依赖注入 的核心思想是:将对象的创建和依赖关系交给外部容器管理,类只需声明自己需要什么,而不负责创建。上述代码可变为:

class Car @Inject constructor(private val engine: Engine) {
    fun start() { engine.ignite() }
}

这样,Engine 的实例由容器注入,Car 类变得纯粹、可测试。


2. 什么是 Hilt?

Hilt 是基于 Dagger 的依赖注入库,专为 Android 设计。Dagger 的强大在于编译时生成代码,无运行时性能损耗,但学习曲线陡峭。Hilt 对 Dagger 进行了高层封装,简化了 ComponentModule 的配置,让开发者用少量注解即可实现全应用范围的 DI。

Hilt 的特点:

  • Android 专属优化:内置了对 Application、Activity、Fragment、ViewModel 等 Android 组件的支持。
  • 自动注入生命周期感知:比如 ViewModel 的注入自动与 SavedStateHandle 关联。
  • 标准化与简化:通过约定优于配置,大幅减少样板代码。
  • 无缝集成其他 Jetpack 库:如 Navigation Compose。

3. 环境搭建

3.1 项目级 build.gradle

在项目根目录的 build.gradle 中添加 Hilt 的 Gradle 插件:

buildscript {
    ext.hilt_version = "2.50"
    dependencies {
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

3.2 模块级 build.gradle

在 app 模块的 build.gradle 中应用插件并添加依赖:

plugins {
    id 'com.android.application'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android { ... }

dependencies {
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-compiler:$hilt_version"

    // 可选:用于测试
    testImplementation 'com.google.dagger:hilt-android-testing:$hilt_version'
    kaptTest 'com.google.dagger:hilt-compiler:$hilt_version'
}

注意:从 Hilt 2.40 起也可使用 KSP(Kotlin Symbol Processing)替代 kapt,以获得更快编译速度。本教程以 kapt 演示。


4. 快速入门:从 Application 到 ViewModel

4.1 创建 Application 并标记 @HiltAndroidApp

所有使用 Hilt 的应用都需要一个自定义 Application 类,并添加 @HiltAndroidApp 注解。这会将 Application 变为 Hilt 的依赖容器根。

@HiltAndroidApp
class MyApp : Application()

别忘了在 AndroidManifest.xml 中声明:

<application android:name=".MyApp" ...>

4.2 让你的 Activity 能被注入

要让 Activity 获得注入能力,需标记 @AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    // 后续可注入依赖
}

同理,Fragment 也可标注 @AndroidEntryPoint,但其宿主 Activity 也必须标注。

4.3 注入一个简单依赖

现在我们注入一个 Repository 类。假设项目中有一个 UserRepository

class UserRepository @Inject constructor() {
    fun getUser(): String = "John Doe"
}

关键点@Inject constructor() 告诉 Hilt 如何创建 UserRepository 实例。

MainActivity 中声明一个字段并注入:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var userRepository: UserRepository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d("Hilt", "User: ${userRepository.getUser()}")
    }
}

@Inject 注解的字段由 Hilt 在 onCreate() 之前自动初始化,因此可直接使用。


5. 深入依赖提供方式

并非所有类都能直接在构造函数上加 @Inject(例如第三方库、接口等)。这时需要借助 Hilt Module。

5.1 使用 @Module 和 @Provides

假设我们有一个接口 AnalyticsService

interface AnalyticsService {
    fun logEvent(event: String)
}

class FirebaseAnalytics @Inject constructor() : AnalyticsService {
    override fun logEvent(event: String) { /*...*/ }
}

我们想注入 AnalyticsService,需要告诉 Hilt 当遇到该接口时使用哪个实现。

创建一个用 @Module@InstallIn 注释的类:

@Module
@InstallIn(SingletonComponent::class) // 说明模块的生命周期范围
abstract class AnalyticsModule {

    @Binds
    abstract fun bindAnalyticsService(
        firebaseAnalytics: FirebaseAnalytics
    ): AnalyticsService
}
  • @InstallIn(SingletonComponent::class):将模块提供的依赖绑定到 SingletonComponent(应用级单例),这意味着依赖在应用生命周期内只创建一次。
  • @Binds:用于接口-实现的绑定。因为方法的参数 FirebaseAnalytics 上已有 @Inject constructor,Hilt 知道如何创建它。

如果你需要提供无法用 @Inject 创建的第三方类,使用 @Provides

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

    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .build()
    }
}

@Provides 方法体包含实例化逻辑,适合需要复杂构建或第三方库的情况。

5.2 常用组件生命周期

@InstallIn 可指定多种 Android 组件作用域:

组件 描述
SingletonComponent 应用全局单例
ActivityRetainedComponent 跨配置变更存活(如 ViewModel)
ViewModelComponent 绑定到 ViewModel 的生命周期
ActivityComponent 绑定到 Activity 生命周期
FragmentComponent 绑定到 Fragment 生命周期

选择合适的 scope 可以避免内存泄漏并优化性能。


6. 注入 ViewModel

Hilt 对 Android Architecture Components 提供了原生支持。要注入 ViewModel,只需给 ViewModel 添加 @HiltViewModel 注解,并在 ActivityFragment 中正常声明。

6.1 创建可注入的 ViewModel

@HiltViewModel
class MainViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    fun getUserName(): String = userRepository.getUser()
}

6.2 在 Activity 中使用

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 使用 viewModel
    }
}

by viewModels() 会自动感知 @HiltViewModel,并从 Hilt 容器中获取依赖构造 ViewModel。


7. 高级特性速览

7.1 依赖作用域自定义

你可以通过自定义作用域注解(如 @ActivityScoped)控制依赖的生命周期与 Activity 一致。Hilt 提供了几个预定义作用域:@Singleton@ActivityScoped@ViewModelScoped 等。

@Module
@InstallIn(ActivityComponent::class) // 模块安装到 ActivityComponent
object AdapterModule {

    @ActivityScoped
    @Provides
    fun provideSomeAdapter(): SomeAdapter = SomeAdapter()
}

使用 @ActivityScoped 后,同一 Activity 内多次注入会得到同一个实例。

7.2 注入 Context 与资源

Hilt 预绑定了 ApplicationActivity 上下文,可以直接注入:

@AndroidEntryPoint
class MyFragment : Fragment() {
    @ApplicationContext lateinit var appContext: Context
    @ActivityContext lateinit var activityContext: Context
}

7.3 多重绑定(Set 与 Map)

有时你需要注入一组相同类型的依赖。Hilt 支持将多个 @Provides/@Binds 收集到 SetMap 中:

@Module
@InstallIn(SingletonComponent::class)
object PluginModule {
    @IntoSet
    @Provides
    fun providePluginOne(): Plugin = PluginOne()

    @IntoSet
    @Provides
    fun providePluginTwo(): Plugin = PluginTwo()
}

// 注入时
class CentralManager @Inject constructor(
    val plugins: Set<@JvmSuppressWildcards Plugin>
)

7.4 与 Navigation Compose 的集成

Hilt 完全支持 Jetpack Compose,通过 hiltViewModel() 可在 Composable 中获取 ViewModel:

@Composable
fun MyScreen(viewModel: MainViewModel = hiltViewModel()) {
    // ...
}

需要添加依赖 androidx.hilt:hilt-navigation-compose


8. 测试中的依赖替换

Hilt 提供便捷的测试支持,可替换真实依赖为测试替身。

8.1 配置测试 Application

创建一个测试专用的 Application 类并标注 @HiltTestApplication

8.2 在测试中使用 @UninstallModules 和 @BindValue

@HiltAndroidTest
@UninstallModules(AnalyticsModule::class) // 移除生产模块
class ExampleTest {

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @BindValue @JvmField
    val fakeAnalytics: AnalyticsService = FakeAnalyticsService()

    @Before
    fun init() {
        hiltRule.inject()
    }

    @Test
    fun testSomething() { ... }
}

这样测试环境就会使用我们创建的假对象,无需修改生产代码。


9. 总结与最佳实践

  • 一切从 @HiltAndroidApp 开始,它是 Hilt 的入口。
  • 能用 @Inject constructor 的类优先用,无需写 Module,更简洁。
  • 接口绑定用 @Binds,第三方库/需要创建逻辑的用 @Provides,并选用合适的组件 @InstallIn
  • ViewModel 加 @HiltViewModel,享受自动注入 SavedStateHandle 的好处。
  • 关注作用域,避免不必要的单例导致的内存泄漏或测试问题。
  • 利用 Hilt Gradle 插件,它会在编译时检查依赖图的正确性,错误早期暴露。
  • 迁移 Dagger 项目时,可逐步将 @Component@Subcomponent 转换为 Hilt 的组件体系,官方提供迁移指南。

Hilt 让 Android 依赖注入变得简单、标准化且安全。掌握它,不仅提高开发效率,也让你的应用架构更加健壮。赶快在你的下一个项目中尝试吧!


延伸阅读