第1章:依赖注入——Hilt集成、@HiltViewModel、@Inject与@Module、Compose中的依赖注入

依赖注入,说白了就是「把对象创建的权利交出去」。

我刚开始写Android时,习惯在Activity里new一大堆对象。后来项目大了,改一个网络库要改十几个文件,那叫一个酸爽。嗯,从那以后,我再也不敢手写依赖了。

1.1 为什么需要依赖注入?

你想想看,一个ViewModel里可能要依赖Repository,Repository里又要依赖ApiService和Database。如果每个类都自己new依赖,代码就变成了「硬编码的意大利面」。

依赖注入解决三个核心问题:

  • 解耦:类不再负责创建自己的依赖
  • 可测试:可以轻松替换为Mock对象
  • 生命周期管理:单例、ActivityScope等由框架管理

核心思想:别找我要东西,我会把你要的东西给你。

1.2 Hilt集成:三步搞定

Hilt是Google官方基于Dagger的封装。我个人习惯在项目初期就集成它,不然后面改起来很痛苦。

第一步:项目级build.gradle

plugins {
    id 'com.google.dagger.hilt.android' version '2.48' apply false
}

第二步:模块级build.gradle

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

android {
    // ... 常规配置
}

dependencies {
    implementation 'com.google.dagger.hilt.android:2.48'
    kapt 'com.google.dagger.hilt.android:2.48-compiler'
    
    // ViewModel相关
    implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
}

第三步:Application类加注解

@HiltAndroidApp
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // 初始化工作
    }
}

💡 记得在AndroidManifest.xml中注册Application类,不然Hilt不会生效。

1.3 @HiltViewModel:让ViewModel自动注入

以前写ViewModel,要自己写ViewModelProvider.Factory,代码又臭又长。有了@HiltViewModel,一切都简单了。

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: MainRepository
) : ViewModel() {
    
    val userData = repository.getUserData().stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = null
    )
}

注意看,构造函数上的@Inject告诉Hilt:「这个ViewModel的依赖我来搞定」。你不需要写Factory,不需要手动创建ViewModel实例。

⚠️ 我曾经踩过一个坑:@HiltViewModel只能用在构造函数注入的场景。如果你的ViewModel有带参数的构造函数,必须全部通过@Inject注入,不能混用手动传参。

1.4 @Inject与@Module:提供依赖的两种方式

@Inject有两种用法:

  • 构造函数注入:告诉Hilt「这个类我可以自己创建自己」
  • 字段/方法注入:告诉Hilt「请把依赖塞给我」

但有些类你不能加@Inject,比如第三方库的Retrofit、Room数据库。这时候就需要@Module了。

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
}

@Module里用@Provides方法提供依赖。@InstallIn指定这个模块在哪个组件中生效——SingletonComponent就是整个应用生命周期。

注解 作用 使用场景
@Inject 标记需要注入的依赖 构造函数、字段、方法
@Module 定义依赖提供方式 第三方库、接口实现
@Provides 提供具体实例的方法 @Module内部
@Binds 将接口绑定到实现 接口与实现分离

经验之谈:能用@Inject就别用@Module。@Inject更简洁,编译时检查更严格。只有遇到第三方库或接口时,才考虑@Module。

1.5 Compose中的依赖注入

在Compose里获取ViewModel,只需要一行代码:

@Composable
fun MainScreen(
    viewModel: MainViewModel = hiltViewModel()
) {
    val userData by viewModel.userData.collectAsState()
    
    Column {
        Text(text = "用户数据:${userData?.name ?: "加载中..."}")
    }
}

hiltViewModel()是Hilt为Compose提供的扩展函数。它会自动从Compose的NavBackStackEntry或Activity中获取ViewModel。

如果你需要传递参数,比如导航参数:

@Composable
fun DetailScreen(
    itemId: String,
    viewModel: DetailViewModel = hiltViewModel()
) {
    // 使用viewModel
}

Hilt会自动处理ViewModel的创建和缓存。你不需要关心它什么时候创建、什么时候销毁。

💡 我个人习惯在Composable函数中把ViewModel作为默认参数,这样在预览时可以传入Mock数据,测试时也能替换。

1.6 依赖注入的完整流程

下面这张图展示了Hilt在Android应用中的完整工作流程:

Hilt依赖注入完整流程 @HiltAndroidApp Hilt组件(SingletonComponent等) @Module @Inject 构造函数 @HiltViewModel Compose UI(hiltViewModel()) 提供依赖 自动创建 自动注入

流程很清晰:

  1. Application启动时,Hilt初始化组件
  2. 组件从@Module或@Inject构造函数中获取依赖
  3. ViewModel通过@HiltViewModel自动注入依赖
  4. Compose UI通过hiltViewModel()获取ViewModel实例

1.7 避坑指南

我这些年用Hilt踩过不少坑,分享几个最常见的:

  • 忘记加@AndroidEntryPoint:Activity和Fragment必须加这个注解,否则Hilt不会注入。我曾经排查了半天,结果发现Activity上忘了加。
  • 循环依赖:A依赖B,B又依赖A。Hilt编译时会报错,但错误信息不太直观。解决办法是引入一个中间层或使用Provider<T>延迟加载。
  • Scope不匹配:在SingletonComponent里注入一个ActivityScoped的依赖,编译会报错。记住:父级不能注入子级的依赖。
  • Kapt配置遗漏:Hilt依赖kapt,如果build.gradle里没配kapt插件,编译会报一堆奇怪的错误。

⚠️ 我曾经在升级Hilt版本时,忘记同步更新kotlin-kapt插件版本,结果编译报错「找不到符号」。折腾了两小时才发现是版本不兼容。建议每次升级Hilt时,同步检查所有相关依赖的版本。

1.8 小结

依赖注入不是银弹,但它能帮你写出更干净、更可测试的代码。Hilt让Dagger的复杂度大大降低,配合Compose使用更是如虎添翼。

记住几个关键点:

  • @HiltAndroidApp是入口
  • @HiltViewModel让ViewModel自动注入
  • @Inject和@Module是提供依赖的两种方式
  • Compose中用hiltViewModel()获取ViewModel

嗯,这一章就到这里。代码写多了,你会发现依赖注入其实是个很自然的事情——就像你点外卖,不需要自己种菜、自己做饭,只需要告诉平台你要什么就行。


公众号:蓝海资料掘金营,微信deep3321