第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应用中的完整工作流程:
流程很清晰:
- Application启动时,Hilt初始化组件
- 组件从@Module或@Inject构造函数中获取依赖
- ViewModel通过@HiltViewModel自动注入依赖
- 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