Android Hilt 笔记

🎯 为什么需要 Hilt?从 Dagger 的痛点说起

在 Hilt 出现之前,Android 开发者如果要使用依赖注入,最强大的选择是 Dagger。但 Dagger 的学习曲线非常陡峭,配置也相当复杂。你需要手动创建和管理大量的 ComponentModule,还要理解复杂的作用域和依赖关系图。很多开发者在使用 Dagger 的过程中,常常会陷入自己搭建的"依赖迷宫"中,难以调试和维护。

Google 推出 Hilt 的目的,就是在不改变 Dagger 强大能力的前提下,针对 Android 开发场景进行"固化"和"简化"。它通过预定义好 Android 应用中的常用组件(如 Application、Activity、Fragment 等)及其生命周期,让开发者无需手动创建和管理这些组件,只需通过注解即可完成依赖注入。


🧱 Hilt 的核心概念

在开始使用之前,你需要理解 Hilt 的三大核心构件:

  1. 组件 (Components) :Hilt 为 Android 中的每个标准类(Application、Activity、Fragment、Service、View 等)都预定义了一个对应的 Dagger 组件。这些组件负责管理依赖的生命周期,并自动跟随 Android 组件的生命周期创建和销毁。例如:

    • SingletonComponent:伴随 Application 的生命周期。
    • ActivityComponent:伴随 Activity 的生命周期。
    • FragmentComponent:伴随 Fragment 的生命周期。
    • ViewModelComponent:伴随 ViewModel 的生命周期。
  2. 作用域 (Scopes) :通过注解(如 @Singleton@ActivityScoped)来限定依赖在对应组件内的唯一性。例如,用 @Singleton 标记的依赖在全局只有一个实例,而用 @ActivityScoped 标记的依赖在同一个 Activity 内是同一个实例。

  3. 模块 (Modules) :当你想注入的类不是你自己写的(如 Retrofit、OkHttpClient 等第三方库),或者是一个接口时,就需要用到 Hilt 模块。它是一个用 @Module 注解的类,你需要在这个类中定义如何提供这些依赖的实例。同时,必须用 @InstallIn 注解指明这个模块安装在哪个组件中,从而让 Hilt 知道它的作用范围。


🚀 Hilt 的使用方法

1. 添加依赖与配置

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

gradle 复制代码
buildscript {
    ext {
        hilt_version = '2.48' // 请使用最新版本
    }
    dependencies {
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

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

gradle 复制代码
plugins {
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
    // ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

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

kotlin-kapt 插件用于处理 Hilt 的编译时注解,这是代码生成的基础。

2. 初始化 Hilt:@HiltAndroidApp

所有使用 Hilt 的应用都必须有一个带有 @HiltAndroidApp 注解的 Application 类。这会触发 Hilt 的代码生成,生成一个全局的 Dagger 组件(即 SingletonComponent)的基类。

kotlin 复制代码
@HiltAndroidApp
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // 应用初始化
    }
}

别忘了在 AndroidManifest.xml 中注册这个自定义的 Application 类。

3. 在 Android 类中注入:@AndroidEntryPoint

要在 Activity、Fragment、View、Service、BroadcastReceiver 中使用依赖注入,你需要为它们添加 @AndroidEntryPoint 注解。

kotlin 复制代码
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    // 通过 @Inject 注解的字段,声明需要注入的依赖
    @Inject
    lateinit var userRepository: UserRepository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 此时 userRepository 已经被自动注入了,可以直接使用
        println(userRepository.getUser())
    }
}

需要注意的是,如果给 Fragment 添加了 @AndroidEntryPoint,那么依赖它的所有 Activity 也必须添加 @AndroidEntryPoint

4. 提供依赖的三种方式

Hilt 提供了三种主要的方式来告诉它"如何创建某个类的实例"。

A. 构造函数注入:@Inject

对于你自己编写的、可以修改构造函数的类,这是最简单的方式。直接在类的构造函数上添加 @Inject 注解。

kotlin 复制代码
// 告诉 Hilt:当需要 UserRepository 时,就通过这个构造函数创建它
class UserRepository @Inject constructor() {
    fun getUser(): String = "User Data"
}

B. 模块 + @Provides

对于你无法修改构造函数的类(如 Retrofit、OkHttpClient 等第三方库),或者需要复杂构建逻辑的情况,你需要创建一个 Hilt 模块,并在其中定义一个用 @Provides 注解的方法。方法的返回值就是要提供的类型,Hilt 会调用这个方法获取实例。

kotlin 复制代码
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class) // 告知此模块安装在全局单例组件中
object NetworkModule {

    @Provides
    @Singleton // 标记为全局单例
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

C. 模块 + @Binds

当你想为一个接口 提供具体的实现类时,使用 @Binds@Binds 注解的方法是一个抽象方法,参数是接口的实现类,返回值是接口本身。它告诉 Hilt,当需要接口时,就使用这个实现类。

kotlin 复制代码
// 定义接口
interface UserService {
    fun getUser(): String
}

// 实现类,用 @Inject 标记其构造函数,以便 Hilt 能创建它
class UserServiceImpl @Inject constructor() : UserService {
    override fun getUser(): String = "User from service"
}

// 在模块中绑定接口与实现
@Module
@InstallIn(SingletonComponent::class)
abstract class ServiceModule {

    @Binds
    abstract fun bindUserService(impl: UserServiceImpl): UserService
}

5. 限定符:@Qualifier

当同一个类型需要提供多个不同的实例时,就需要用到限定符。例如,你可能需要两个不同的 Retrofit 实例(一个用于 GitHub API,一个用于其他 API)。Hilt 内置了 @ApplicationContext@ActivityContext 两个限定符来区分不同的 Context。你也可以自定义限定符。

kotlin 复制代码
// 定义两个限定符注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class GithubRetrofit

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherRetrofit

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

    @GithubRetrofit
    @Provides
    @Singleton
    fun provideGithubRetrofit(): Retrofit {
        return Retrofit.Builder().baseUrl("https://api.github.com/").build()
    }

    @OtherRetrofit
    @Provides
    @Singleton
    fun provideOtherRetrofit(): Retrofit {
        return Retrofit.Builder().baseUrl("https://other.api.com/").build()
    }
}

// 使用时,用对应的限定符注解字段
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    @GithubRetrofit
    lateinit var githubRetrofit: Retrofit

    @Inject
    @OtherRetrofit
    lateinit var otherRetrofit: Retrofit
}

6. 为 ViewModel 注入:@HiltViewModel

Hilt 与 Jetpack ViewModel 的集成非常出色。你只需在 ViewModel 上添加 @HiltViewModel 注解,并在其构造函数中使用 @Inject,Hilt 就会自动帮你创建 ViewModel,并注入所需的依赖。你的 Activity/Fragment 无需再关心如何创建 ViewModel。

kotlin 复制代码
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {

    fun loadUser() {
        // 使用 userRepository 获取数据
    }
}

在 Activity 或 Fragment 中,你就可以像往常一样通过 by viewModels()hiltNavGraphViewModels 获取 ViewModel 实例了,无需任何额外的工厂类。


🧪 Hilt 的测试支持

Hilt 的强大之处也体现在测试上。它提供了专门的测试 API,让你能轻松地在单元测试和插桩测试中替换依赖,实现隔离测试。

  1. 配置测试 :为测试类添加 @HiltAndroidTest 注解和一个 HiltAndroidRule,并使用 HiltTestApplication 作为测试的 Application。
  2. 注入依赖 :在测试类中,你可以像在生产代码中一样使用 @Inject 来注入所需的对象。
  3. 替换模块 :Hilt 提供了 @TestInstallIn@UninstallModules 两种方式来替换生产代码中的模块。
    • @TestInstallIn:用于定义一个全局的测试模块,替换掉整个生产模块。适合大多数测试场景。
    • @UninstallModules:用于在单个测试类中,临时卸载某些生产模块,然后通过 @BindValue 等方式提供模拟实现。这种方式非常灵活,但可能会导致构建时间变慢,应谨慎使用。
kotlin 复制代码
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @get:Rule
    val hiltRule = HiltAndroidRule(this)

    @Inject
    lateinit var userRepository: UserRepository // 注入真实或模拟的对象

    @Before
    fun setup() {
        hiltRule.inject() // 执行注入
    }

    // ... 测试代码
}

⚙️ 原理简析:Hilt 是如何工作的?

Hilt 的核心原理是编译时注解处理与代码生成

  1. 注解处理 :在编译时,Hilt 的注解处理器会扫描所有带有 @HiltAndroidApp@AndroidEntryPoint@Module@Inject 等注解的代码。
  2. 生成 Dagger 组件 :基于扫描结果,Hilt 会自动生成一系列标准的 Dagger 组件(如 SingletonComponentActivityComponent 等),这些组件已经预先配置好了与 Android 组件生命周期的绑定关系。例如,ActivityComponent 会在 Activity 的 onCreate() 时创建,在 onDestroy() 时销毁。
  3. 生成代码 :Hilt 还会为被 @AndroidEntryPoint 标记的类生成一个基类。这个基类负责在合适的生命周期回调中(如 onCreate())执行实际的注入操作,即从对应的 Dagger 组件中获取依赖,并赋值给带有 @Inject 注解的字段。
  4. 无运行时反射:所有这些工作都在编译时完成,生成的代码是纯粹的、高效的 Dagger 代码,因此在运行时几乎没有性能损耗。

💡 总结与最佳实践

  • Hilt = Dagger 的 Android 最佳实践:它不是为了取代 Dagger,而是为了让开发者能以更低的成本享受到 Dagger 的强大能力。
  • 几乎零模板代码 :你不再需要编写繁琐的 Component 和工厂类,只需关注如何用注解声明依赖关系。
  • 与 Jetpack 深度集成:对 ViewModel、Navigation、Compose 等都有很好的支持。
  • 测试友好:提供了强大的测试 API,让依赖替换变得异常简单。
  • 性能优化技巧 :合理使用作用域注解(如 @Singleton)来管理对象生命周期;在模块设计上保持"单一职责",便于测试替换;对于大量测试,优先使用 @TestInstallIn 来提升构建速度。

Hilt 的出现,极大地改善了 Android 开发的依赖注入体验。希望这篇讲解能帮助你更好地理解和使用 Hilt,写出更健壮、更可维护的代码。

相关推荐
Ehtan_Zheng16 分钟前
ActivityMetricsLogger 深度剖析:系统如何追踪启动耗时
android
用户69371750013841 小时前
Android 开发,别只钻技术一亩三分地,也该学点“广度”了
android·前端·后端
唔661 小时前
原生 Android(Kotlin)仅串口「继承架构」完整案例二
android·开发语言·kotlin
一直都在5721 小时前
MySQL索引优化
android·数据库·mysql
代码s贝多芬的音符3 小时前
android mlkit 实现仰卧起坐和俯卧撑识别
android
jwn9994 小时前
Laravel9.x核心特性全解析
android
今天又在写代码4 小时前
数据智能分析平台部署服务器
android·服务器·adb
梦里花开知多少5 小时前
深入谈谈Launcher的启动流程
android·架构
jwn9995 小时前
Laravel11.x新特性全解析
android·开发语言·php·laravel
我就是马云飞6 小时前
停更5年后,我为什么重新开始写技术内容了
android·前端·程序员