第1章:Hilt与ViewModel集成原理

理解Hilt与ViewModel的集成原理,是构建可维护、可测试Android应用的基石。本章从依赖注入的核心动机出发,剖析ViewModel的生命周期特性,并深入Hilt的组件层次结构,阐明这三者如何协同工作以实现“零模板代码”的依赖注入。

1.1 依赖注入核心概念

依赖注入(DI)通过将对象的创建与消费分离,消除硬编码依赖。在Android开发中,手动管理依赖会导致大量工厂类、单例模式滥用以及测试困难。Hilt基于Dagger,在编译期生成高效、可溯源的注入代码。

概念 说明
依赖 一个对象需要协作的其他对象(如Repository需要RemoteDataSource)
注入器 负责提供依赖实例的组件(在Hilt中由@Component注解的接口表示)
绑定 将抽象类型映射到具体实现(通过@Module + @Binds 或 @Provides)
作用域 控制依赖实例的生命周期范围(如@Singleton、@ViewModelScoped)

关键洞察:Hilt将DI从“运行时手动组装”提升为“编译期声明式注入”,ViewModel不再需要关心自己的依赖来自哪里。

1.2 ViewModel生命周期

ViewModel必须比其宿主(Activity/Fragment)更长寿,以承受配置变更(如屏幕旋转)。标准方式是通过ViewModelProvider获取实例,但若ViewModel的构造函数需要参数,就需要自定义ViewModelFactory。Hilt通过@HiltViewModel注解和SavedStateHandle自动处理这一痛点。

// 标准手工工厂方式(繁琐)
class MyViewModelFactory(
    private val repo: MyRepository
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MyViewModel(repo) as T
    }
}

// Hilt 自动注入方式(简洁)
@HiltViewModel
class MyViewModel @Inject constructor(
    private val repo: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel()

生命周期对齐:Hilt保证注入到ViewModel中的依赖在ViewModel被清除时一并释放,无需手动取消订阅。

1.3 Hilt组件层次

Hilt定义了一套以Android生命周期为驱动的组件层次结构。每个组件都有明确的创建与销毁时机,并可以访问其父组件的绑定。ViewModel的注入发生在ActivityRetainedComponent(由@ViewModelScoped管理)或ActivityComponent级别。

// 指定注入发生在 ViewModel 宿主 Activity 的组件中
@Module
@InstallIn(ActivityComponent::class)
object NetworkModule {
    @Provides
    fun provideApiService(): ApiService = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .build()
        .create(ApiService::class.java)
}

// 使用 @ViewModelScoped 让依赖在 ViewModel 生命周期内保持单例
@Module
@InstallIn(ActivityRetainedComponent::class)
object RepoModule {
    @Provides
    @ViewModelScoped
    fun provideMyRepository(api: ApiService): MyRepository {
        return MyRepository(api)
    }
}
Hilt 组件层次与 ViewModel 集成关系 ApplicationComponent (@Singleton) ActivityRetainedComponent (@ViewModelScoped) ViewModel 注入点 ActivityComponent (@ActivityScoped) FragmentComponent (@FragmentScoped) ViewComponent (@ViewScoped) ViewComponent (@ViewScoped) ViewModel 生命周期边界 存活于 ActivityRetainedComponent 内,跨配置变更,随 Activity 销毁而清除

1.4 集成关键流程

  1. 声明依赖:使用@Module定义如何提供依赖,并用@InstallIn指定组件层级。
  2. 注入目标:在ViewModel构造函数上添加@Inject constructor,并用@HiltViewModel标注类。
  3. 宿主激活:Activity/Fragment使用@AndroidEntryPoint,Hilt自动生成Factory并绑定到生命周期。
  4. 获取实例:通过by viewModels()或hiltViewModel()扩展函数获取ViewModel。

常见错误:如果在ViewModel中注入一个@ActivityScoped的依赖,该依赖会跟随Activity生命周期,而非ViewModel。应优先使用@ViewModelScoped或@Singleton来保证生命周期对齐。

// 正确实践:依赖生命周期 <= ViewModel
@Module
@InstallIn(ActivityRetainedComponent::class)
object UserModule {
    @Provides
    @ViewModelScoped
    fun provideUserManager(): UserManager = UserManager()
}

// 使用
@HiltViewModel
class ProfileViewModel @Inject constructor(
    private val userManager: UserManager,
    private val savedStateHandle: SavedStateHandle
) : ViewModel()

通过本章的剖析,依赖注入不再是黑盒:它是一个声明式、编译期可验证、生命周期感知的优雅架构。下一站我们将通过Hello World项目,亲手实践Hilt与ViewModel的第一次集成。