依赖注入
本教程共 100 篇 · 第 97 篇 · 更新于 2026-07-28 · 约 9 分钟阅读
97. 依赖注入
本节目标:理解依赖注入的原理,学会手动 DI,掌握 Hilt 的核心注解(@Inject、@Module、@Provides、@HiltViewModel),把 ViewModel 和 Repository 自动组装起来。
依赖注入(Dependency Injection,DI)是架构里绕不开的话题。前面写 ViewModel 时,依赖 Repository,Repository 依赖 Dao 和 Api。手动 new 实例太啰嗦,DI 帮你自动搞定。
为什么需要 DI
打个比方,你要盖房子,需要砖头、水泥、工人。最笨的办法是你自己造砖、烧水泥、雇工人。聪明的办法是打电话给供应商,他们说「你需要啥我送来」。
DI 就是这个供应商。类不自己 new 依赖,而是声明「我需要啥」,外部负责提供。
好处:
- 解耦:类不依赖具体实现,换实现不用改类。
- 可测试:测试时传 Mock 依赖。
- 复用:依赖单例管理,避免重复创建。
手动 DI
最简单的 DI 是手写。前面章节演示过:
// Application 持有依赖
class App : Application() {
val database by lazy { AppDatabase.getInstance(this) }
val noteApi by lazy { RetrofitClient.api }
val noteRepository by lazy { NoteRepository(database.noteDao(), noteApi) }
}
// ViewModel 工厂
class NoteViewModelFactory(private val repo: NoteRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return NoteViewModel(repo) as T
}
}
// Activity 用
val app = application as App
val viewModel by viewModels<NoteViewModel> {
NoteViewModelFactory(app.noteRepository)
}
手动 DI 简单清晰,小项目完全够用。但依赖一多,写 Factory 重复劳动。
Hilt 是什么
Hilt 是 Google 出的 DI 库,基于 Dagger 但大幅简化。Dagger 学习曲线陡,Hilt 提供注解和默认配置,Android 项目几乎一行配置就能用。
特点:
- 注解驱动:
@Inject声明依赖,@Module提供依赖。 - 编译期生成代码:比反射快,编译时报错。
- Android 集成:内置 Application、Activity、Fragment、ViewModel 的注入。
- 和 Compose、ViewModel、Room 等深度集成。
加依赖
项目级 build.gradle.kts:
plugins {
id("com.google.dagger.hilt.android") version "2.52" apply false
id("com.google.devtools.ksp") version "2.0.21-1.0.28" apply false
}
模块级:
plugins {
id("com.google.dagger.hilt.android")
id("com.google.devtools.ksp")
}
dependencies {
implementation("com.google.dagger:hilt-android:2.52")
ksp("com.google.dagger:hilt-android-compiler:2.52")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
}
启用 Hilt
定义 Application 类,加 @HiltAndroidApp:
@HiltAndroidApp
class App : Application()
manifest 里用:
<application android:name=".App" ...>
这一步是 Hilt 的入口,所有 Hilt 功能都从这里展开。
@Inject 注入构造函数
类需要依赖时,构造函数加 @Inject:
class NoteRepository @Inject constructor(
private val dao: NoteDao,
private val api: NoteApi
) {
// ...
}
Hilt 会自动找到 NoteDao 和 NoteApi 的实例传进来。
@Module 提供依赖
有些类不能直接 @Inject(比如接口、第三方库的类、需要配置的对象)。用 @Module 提供它们。
@Module
@InstallIn(SingletonComponent::class) // 生命周期范围
object DatabaseModule {
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build()
}
@Provides
fun provideNoteDao(db: AppDatabase): NoteDao {
return db.noteDao()
}
}
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
@Provides
fun provideNoteApi(retrofit: Retrofit): NoteApi {
return retrofit.create(NoteApi::class.java)
}
}
要点:
@Module标记这是提供依赖的模块。@InstallIn(...)指定依赖的生命周期范围。SingletonComponent是整个应用。@Provides标记提供方法。@Singleton让单例生效。
Hilt 组件范围
@InstallIn 的几种 Component:
| Component | 范围 | 创建/销毁 |
|---|---|---|
SingletonComponent | 应用 | Application 创建到销毁 |
ActivityRetainedComponent | Activity(配置变化不销毁) | Activity 创建到销毁 |
ActivityComponent | Activity | Activity onCreate 到 onDestroy |
ViewModelComponent | ViewModel | ViewModel 创建到销毁 |
FragmentComponent | Fragment | Fragment onAttach 到 onDestroy |
ViewComponent | View | View 创建到销毁 |
匹配对应的生命周期范围注解:
SingletonComponent->@SingletonViewModelComponent->@HiltViewModelActivityComponent->@ActivityScoped
@HiltViewModel
ViewModel 注入用 @HiltViewModel,配合 @Inject:
@HiltViewModel
class NoteViewModel @Inject constructor(
private val repo: NoteRepository
) : ViewModel() {
// ...
}
不需要写 Factory,Hilt 自动生成。
在 Compose 里获取
import androidx.hilt.navigation.compose.hiltViewModel
@Composable
fun NoteScreen(viewModel: NoteViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// ...
}
hiltViewModel() 自动从 Hilt 拿 ViewModel 实例,按 Navigation 目的地作用域。
在 Activity 里获取
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: NoteViewModel by viewModels()
}
@AndroidEntryPoint 让 Activity 支持 Hilt 注入。
预定义限定符
Hilt 提供几个预定义的限定符,避免类型冲突:
// 注入 Application Context
class MyRepository @Inject constructor(
@ApplicationContext private val context: Context
) { ... }
// 注入 Activity Context
class MyView @Inject constructor(
@ActivityContext private val context: Context
) { ... }
两个 Context 类型相同,靠限定符区分。
自定义限定符
同类型多个实例,用自定义限定符区分:
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthRetrofit
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ImageRetrofit
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
@AuthRetrofit
fun provideAuthRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://auth.example.com/")
.build()
@Provides
@Singleton
@ImageRetrofit
fun provideImageRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://img.example.com/")
.build()
}
// 使用
class MyRepository @Inject constructor(
@AuthRetrofit private val authRetrofit: Retrofit,
@ImageRetrofit private val imageRetrofit: Retrofit
)
测试
Hilt 测试方便,能替换依赖:
@HiltAndroidTest
class MyTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var repo: NoteRepository
@Before
fun init() {
hiltRule.inject()
}
@Test
fun test() {
// 用真实 repo 测试
}
}
替换依赖用 @UninstallModules + 测试 Module:
@UninstallModules(DatabaseModule::class)
@HiltAndroidTest
class MyTest {
@Module
@InstallIn(SingletonComponent::class)
@TestInstallIn(components = [SingletonComponent::class], replaces = [DatabaseModule::class])
object TestDatabaseModule {
@Provides fun provideAppDatabase(): AppDatabase = mockAppDatabase
}
}
Hilt vs 手动 DI
| 维度 | 手动 DI | Hilt |
|---|---|---|
| 配置成本 | 低 | 中 |
| 维护成本 | 中(多 Factory) | 低 |
| 编译速度 | 快 | 略慢 |
| 学习曲线 | 低 | 中 |
| 适合项目 | 小项目 | 中大型项目 |
Tip小项目(3-5 个屏幕)手动 DI 够用。中大型项目用 Hilt 省事。
Compose 里的局部 DI
Compose 不需要 Hilt 也能做局部 DI,用 CompositionLocal:
val LocalRepository = staticCompositionLocalOf<NoteRepository> {
error("No Repository provided")
}
// 顶层提供
CompositionLocalProvider(LocalRepository provides noteRepository) {
NoteScreen()
}
// 内层用
@Composable
fun NoteScreen() {
val repo = LocalRepository.current
// ...
}
但全局依赖还是用 Hilt 更合适,CompositionLocal 适合「作用域内的临时依赖」。
常见坑
- 忘加
@HiltAndroidApp:Hilt 不工作,所有注入失败。 - 忘加
@AndroidEntryPoint:Activity/Fragment 注入失败。 @InstallIn范围错:ViewModel 用的依赖装在SingletonComponent没问题,反过来 Singleton 用 ViewModel 范围会编译错。- 同类型多实例没用限定符:Hilt 不知道给哪个,编译报错。
- 依赖循环:A 依赖 B,B 依赖 A,编译报错。设计上避免。
- 测试忘
@HiltAndroidTest:注入不工作。 - ksp 和 kapt 混用:Hilt 现在支持 ksp,比 kapt 快,但有的库还要 kapt,可能冲突。
小结
依赖注入解耦组件依赖。手动 DI 简单清晰,小项目够用。Hilt 是官方 DI 库,注解驱动 + 编译期生成代码。@HiltAndroidApp 启用,@Inject 注入构造函数,@Module + @Provides 提供第三方依赖,@HiltViewModel 注入 ViewModel,hiltViewModel() 在 Compose 里获取。中大型项目推荐 Hilt,小项目手动 DI 也行。
下一章讲测试基础。