5、依赖注入入门:为什么需要DI,Hilt框架的配置与基本使用
依赖注入,简称DI。这个词你肯定听过无数遍了。
但说实话,我刚开始接触Android时,觉得这东西就是「为了用而用」。不就是new个对象吗?搞那么复杂干嘛?直到我接手了一个老项目——一个Activity里塞了十几个Presenter,每个Presenter又依赖好几个Repository,Repository又依赖Dao和ApiService……
嗯,那感觉就像拆炸弹。改一个构造参数,牵连七八个文件。从那天起,我彻底服了DI。
5.1 为什么需要依赖注入?
我们先想一个问题:一个类要干活,往往需要别的类帮忙。比如ViewModel需要Repository,Repository需要ApiService。这种「需要别人帮忙」的关系,就叫依赖。
传统写法是这样的:
class UserRepository {
private val api = RetrofitApiService() // 自己new
private val dao = UserDao() // 自己new
fun getUser(): User {
return api.fetchUser()
}
}
有什么问题?
- 耦合太紧:UserRepository 直接绑死了 RetrofitApiService。哪天想换成Mock数据做测试?得改代码。
- 难以测试:你没法单独测UserRepository的逻辑,因为它的依赖是写死的。
- 重复创建:每个地方new一次,内存里一堆重复对象。
依赖注入的核心思想就一句话:别自己new,让别人给你送过来。
依赖注入的本质:把对象的创建和绑定,从类内部移到外部。类只负责声明「我需要什么」,不负责「怎么造出来」。
用DI改写后:
class UserRepository @Inject constructor(
private val api: ApiService,
private val dao: UserDao
) {
fun getUser(): User {
return api.fetchUser()
}
}
看到区别了吗?UserRepository不再自己new东西了。它只是说:「给我一个ApiService和一个UserDao,我就能干活。」至于这两个东西怎么创建、是不是单例、是不是Mock——它不管。
这就是依赖倒置原则的体现。高层模块不依赖低层模块的具体实现,而是依赖抽象接口。
5.2 手动DI vs 框架DI
你可能会说:「那我手动写个工厂类,把所有依赖集中管理,不也行吗?」
当然行。我早期项目就是这么干的。写一个AppContainer类,里面new好所有对象,然后通过Application传递。
但项目一大了,问题就来了:
- 容器类越来越臃肿,几百行new语句
- 对象生命周期难管理——Activity销毁了,对象还在吗?
- 作用域混乱——单例、Activity作用域、Fragment作用域,手动维护很痛苦
这时候就需要一个框架来帮我们做这些脏活累活。在Android生态里,Google官方推荐的是Hilt。
我的建议:小项目可以手动DI,但一旦超过5个模块,直接上Hilt。省下的时间远大于学习成本。
5.3 Hilt是什么?
Hilt是Google基于Dagger封装的Android专用DI框架。它解决了Dagger配置繁琐的问题,同时保留了Dagger的编译期检查和性能优势。
说白了,Hilt就是Dagger的「傻瓜版」。你不需要写@Component、@Module、@Binds这些复杂注解,Hilt帮你自动生成。
Hilt的核心概念就这几个:
| 概念 | 作用 | 类比 |
|---|---|---|
| @HiltAndroidApp | 标记Application类,Hilt的入口 | 总开关 |
| @AndroidEntryPoint | 标记Activity/Fragment/View等,启用注入 | 插座 |
| @Inject | 告诉Hilt「这个对象我来提供」或「我需要这个对象」 | 插头 |
| @Module | 当第三方类无法加@Inject时,用Module提供 | 适配器 |
| @Provides | 在Module中声明如何创建对象 | 工厂方法 |
| @Singleton | 标记为全局单例 | 全局唯一 |
下面这张图展示了Hilt在MVVM架构中的位置和作用:
5.4 Hilt的配置步骤
配置Hilt其实就三步。我带你走一遍。
5.4.1 添加依赖
在项目根目录的 build.gradle 中:
// build.gradle (Project级别)
plugins {
id 'com.google.dagger.hilt.android' version '2.48' apply false
}
在 app/build.gradle 中:
// app/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.dagger.hilt.android'
id 'kotlin-kapt'
}
android {
// ... 你的配置
}
dependencies {
implementation 'com.google.dagger:hilt-android:2.48'
kapt 'com.google.dagger:hilt-compiler:2.48'
// 如果要用Hilt ViewModel
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
// 或者
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
}
注意:Hilt使用kapt进行编译期代码生成。如果你迁移到了KSP,需要额外配置。我个人建议在稳定版本中继续用kapt,等Hilt官方全面支持KSP再迁移。
5.4.2 创建Application类并添加注解
@HiltAndroidApp
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 初始化工作
}
}
别忘了在AndroidManifest.xml中声明:
<application
android:name=".MyApplication"
...>
...
</application>
5.4.3 在Activity中启用注入
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var repository: UserRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// repository 已经自动注入好了,直接用
repository.getUser()
}
}
就这么简单。你不需要写任何工厂类,不需要手动传参。Hilt在编译期自动生成了所有依赖图。
5.5 基本使用:@Inject 和 @Module
刚才的例子中,UserRepository 是通过 @Inject constructor 提供的。但有些类我们没法加 @Inject,比如 Retrofit、OkHttpClient、Room 数据库等第三方库。
这时候就需要 @Module 了。
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
这里有几个关键点:
- @InstallIn(SingletonComponent::class):表示这个Module里的对象是全局单例,整个App生命周期内只创建一次。
- @Provides:告诉Hilt「这个对象怎么创建」。
- @Singleton:标记为单例。如果不加,每次注入都会创建新对象。
避坑指南:我曾经犯过一个错误——在Module里用@Singleton标记了Retrofit,但忘了标记OkHttpClient。结果每次注入Retrofit时,OkHttpClient都是新的,导致连接池失效。记住:依赖链上的所有对象都要统一作用域。
5.6 Hilt的作用域
Hilt提供了几种作用域,控制对象的生命周期:
| 作用域 | 生命周期 | 使用场景 |
|---|---|---|
| @Singleton | 整个App进程 | Retrofit、Room、SharedPreferences |
| @ActivityScoped | Activity创建到销毁 | Activity相关的导航、资源 |
| @FragmentScoped | Fragment创建到销毁 | Fragment特有的逻辑 |
| @ViewModelScoped | ViewModel创建到销毁 | ViewModel持有的数据 |
选择作用域的原则很简单:对象需要活多久,就用对应的作用域。别一股脑全用@Singleton,那会导致内存泄漏。
5.7 小结
依赖注入不是银弹,但它解决了Android开发中一个很实际的问题——对象管理混乱。
Hilt让DI变得简单。你只需要记住三个核心注解:@HiltAndroidApp、@AndroidEntryPoint、@Inject。剩下的,Hilt在编译期帮你搞定。
嗯,这一章的内容就到这。配置好Hilt后,下一章我们会用它来串联整个MVVM架构。
核心要点回顾:
- DI的核心是「别自己new,让别人送」
- Hilt是Dagger的Android封装,配置简单
- @Inject用于自己写的类,@Module用于第三方库
- 作用域控制对象生命周期,别滥用@Singleton
公众号:蓝海资料掘金营,微信deep3321