首页 / Android 入门教程 / 依赖注入

Android 入门教程

依赖注入

本教程共 100 篇 · 第 97 篇 · 更新于 2026-07-28 · 约 9 分钟阅读

AndroidAndroid 入门教程依赖注入HiltDaggerJetpack

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 会自动找到 NoteDaoNoteApi 的实例传进来。

@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 创建到销毁
ActivityRetainedComponentActivity(配置变化不销毁)Activity 创建到销毁
ActivityComponentActivityActivity onCreate 到 onDestroy
ViewModelComponentViewModelViewModel 创建到销毁
FragmentComponentFragmentFragment onAttach 到 onDestroy
ViewComponentViewView 创建到销毁

匹配对应的生命周期范围注解:

  • SingletonComponent -> @Singleton
  • ViewModelComponent -> @HiltViewModel
  • ActivityComponent -> @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

维度手动 DIHilt
配置成本
维护成本中(多 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 适合「作用域内的临时依赖」。

常见坑

  1. 忘加 @HiltAndroidApp:Hilt 不工作,所有注入失败。
  2. 忘加 @AndroidEntryPoint:Activity/Fragment 注入失败。
  3. @InstallIn 范围错:ViewModel 用的依赖装在 SingletonComponent 没问题,反过来 Singleton 用 ViewModel 范围会编译错。
  4. 同类型多实例没用限定符:Hilt 不知道给哪个,编译报错。
  5. 依赖循环:A 依赖 B,B 依赖 A,编译报错。设计上避免。
  6. 测试忘 @HiltAndroidTest:注入不工作。
  7. ksp 和 kapt 混用:Hilt 现在支持 ksp,比 kapt 快,但有的库还要 kapt,可能冲突。

小结

依赖注入解耦组件依赖。手动 DI 简单清晰,小项目够用。Hilt 是官方 DI 库,注解驱动 + 编译期生成代码。@HiltAndroidApp 启用,@Inject 注入构造函数,@Module + @Provides 提供第三方依赖,@HiltViewModel 注入 ViewModel,hiltViewModel() 在 Compose 里获取。中大型项目推荐 Hilt,小项目手动 DI 也行。

下一章讲测试基础。