Hilt

Hilt 是 Android 专属的依赖注入 (Dependency Injection, DI) 库,它建立在著名的 Dagger 库之上,旨在简化 Dagger 在 Android 应用中的使用。它通过提供一套标准的组件和作用域,并自动生成和管理它们所需的模板代码,让开发者更容易地实现依赖注入。

1、为什么需要 Hilt?(解决的问题)

在没有依赖注入的情况下,一个类(如 ViewModel 或 Activity)可能需要自己创建它所依赖的对象。这会导致:
    代码耦合度高:类与它的依赖紧密绑定,难以单独测试和替换。
    难以测试:无法轻松传入模拟对象(Mock)进行单元测试。
    样板代码多:手动创建和管理依赖对象的生命周期非常繁琐。

Hilt 通过以下方式解决了这些问题:
    解耦:将依赖项的创建与使用它的类分离开。
    可测试性:可以轻松地为测试提供不同的实现(例如,将网络模块替换为模拟模块)。
    生命周期管理:Hilt 自动管理依赖对象的生命周期,使其与 Android 组件(如 Activity、Fragment)的生命周期对齐。
    减少样板代码:通过注解自动生成代码,开发者只需声明依赖关系。

2、Hilt 的核心概念

要理解 Hilt,必须掌握以下几个核心概念:
a. 依赖注入 (Dependency Injection)
一种设计模式,一个对象接收它所需要的其他对象(即它的“依赖”),而不是自己创建它们。例如:

// 没有 DI
class MyViewModel {
    private val repository = MyRepository() // 自己创建依赖,耦合紧密
}

// 有 DI
class MyViewModel(
    private val repository: MyRepository // 依赖从外部“注入”进来
)

b. @Inject 注解
用于告知 Hilt 如何提供某个类型的实例,以及标记需要被注入依赖的字段或构造函数。 构造函数注入:在类的构造函数上使用 @Inject,告诉 Hilt 如何创建该类的实例。

class MyRepository @Inject constructor() {
    // ...
}

字段注入:在 Android 组件(如 Activity、Fragment)的属性上使用 @Inject,告诉 Hilt 需要为此字段提供依赖。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var myRepository: MyRepository // Hilt 会自动注入这个字段
}

c. Hilt 模块 (@Module 和 @Provides)
当无法使用构造函数注入时(例如,需要注入接口、第三方库类或需要通过 builder 创建的对象),就需要使用 Hilt 模块。
@Module:标注一个类,告诉 Hilt 这是一个提供依赖的模块。
@Provides:标注模块类中的一个函数,告诉 Hilt 这个函数是提供某种类型实例的方法。

@Module
@InstallIn(SingletonComponent::class) // 告诉 Hilt 这个模块安装在哪个组件中
object NetworkModule {

    @Provides
    @Singleton // 单例作用域
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder().build()
    }

    // 假设 ApiService 是一个接口,无法用 @Inject 构造
    @Provides
    fun provideApiService(okHttpClient: OkHttpClient): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://example.com/")
            .client(okHttpClient)
            .build()
            .create(ApiService::class.java)
    }
}

d. Hilt 组件 (Component) 和作用域 (Scope)
Hilt 预定义了一系列与 Android 生命周期相关的组件,每个组件都有对应的作用域注解。
组件:Hilt 容器,负责将依赖注入到对应的 Android 类中。每个组件都有一个生命周期。
作用域:将依赖的生命周期限定在某个组件的生命周期内。例如,一个被 @Singleton 标注的依赖在整个应用生命周期内只有一个实例。
Android 类 生成的 Hilt 组件 作用域
Application SingletonComponent @Singleton
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
带有 @WithFragmentBindings 的 View ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped
如何使用:在模块上使用 @InstallIn 指定它属于哪个组件,在 @Provides 方法或可注入的类上使用作用域注解来限定生命周期。

@Module
@InstallIn(ActivityComponent::class) // 这个模块安装在 Activity 组件中
object MyActivityModule {
    @Provides
    @ActivityScoped 
    fun provideMyActivityScopedDep(): MyDep { ... }
}

e. @AndroidEntryPoint
标注一个 Android 组件(如 Activity, Fragment, View, Service, BroadcastReceiver),表示这个类是 Hilt 的入口点,Hilt 会为它生成所需的组件,并允许在其中进行字段注入。
 
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModel: MyViewModel
    // ...
}

3. Hilt 与 ViewModel

Hilt 对 ViewModel 提供了专门的支持,使得注入 ViewModel 变得非常简单。
1、添加依赖:确保添加了 hilt-androidx-viewmodel 依赖。
2、使用 @HiltViewModel:在 ViewModel 的构造函数上使用 @HiltViewModel 和 @Inject。

@HiltViewModel
class MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle, // Hilt 自动提供
    private val repository: MyRepository // 你的自定义依赖
) : ViewModel() {
    // ...
}

3、在 Activity/Fragment 中获取:使用普通的 by viewModels() 委托属性来获取 ViewModel 实例,Hilt 会自动处理依赖注入。    

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    // 无需 @Inject,使用 viewModels() 委托
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // viewModel is ready to use
    }
}


4. 重要注解一览(常用并解释)

@HiltAndroidApp :标注在 Application 上,触发 Hilt 的代码生成与顶级组件(必须有)。
@AndroidEntryPoint:标注 Activity/Fragment/Service/View/Receiver 等,允许成员注入(@Inject 字段)。
@HiltViewModel:标注ViewModel,配合@Inject 构造器让 Hilt 提供 ViewModel。可与 hiltViewModel()(Compose)或 by viewModels() + Hilt 集成使用。
@Module/@InstallIn(...) :定义绑定/提供函数并指定安装到哪个 Component。
@Provides/@Binds :提供实例与接口绑定(@Binds 更轻量但要求接口实现类以参数形式传入)。
@Qualifier / @Named:用于区分同类型的多个绑定。
@Singleton、@ActivityScoped@ViewModelScoped 等:绑定实例生命周期。