Hilt 依赖注入:从手动 new 到自动装配

简介: Hilt是Google官方推荐的Android依赖注入框架,基于Dagger构建,通过`@HiltAndroidApp`、`@AndroidEntryPoint`、`@Inject`等注解实现编译期自动装配,大幅降低耦合、提升可测性与可维护性。

Hilt 依赖注入:从手动 new 到自动装配

什么是依赖注入

在写代码时,一个类经常需要用到其他类。比如 ArticleViewModel 需要 ArticleRepositoryArticleRepository 又需要 ArticleDaoApiService

最直接的写法是在构造函数里自己创建:

class ArticleViewModel {
    private val repository = ArticleRepository(
        ArticleDao(database),
        ApiService.create()
    )
}

这种做法有几个问题:

  1. 耦合度高。 ViewModel 直接知道怎么创建 Repository、Dao、ApiService,换实现要改 ViewModel 代码。
  2. 难以测试。 写单元测试时想传一个假数据库或 Mock 接口进去,发现做不到。
  3. 对象复用困难。 多个地方都需要 ApiService,如果各自创建,就不是同一个实例。

依赖注入的思路很简单:不要让类自己创建依赖对象,而是从外部传进来。

class ArticleViewModel(
    private val repository: ArticleRepository
) {
    // 直接用 repository,不关心它怎么来的
}

但手写依赖注入时,需要在 Application 或 Activity 里手动层层构造对象,代码量会快速膨胀。这就是 DI 框架的价值——帮你自动完成对象的创建和传递。

为什么选 Hilt

Android 上常见的 DI 方案有 Dagger、Koin 和 Hilt。

  • Dagger 功能强大但配置复杂,学习曲线陡峭。
  • Koin 基于 Kotlin DSL,写法简洁,但运行时解析,编译期不检查。
  • Hilt 是 Google 在 Dagger 基础上封装的 Android 专用方案。它保留了编译期类型检查,同时大幅简化了配置。

Hilt 的核心优势:

  1. Android 感知。 自动为 Application、Activity、Fragment、Service、ViewModel 提供注入支持。
  2. 编译期验证。 依赖缺失或类型不匹配会在编译时暴露,不会等到运行时崩溃。
  3. 标准化。 所有组件使用相同的注解体系,团队协作成本低。
  4. Google 官方推荐。 Jetpack 组件(如 WorkManager、Navigation)逐步提供 Hilt 集成。

接入 Hilt

添加依赖

在项目根 build.gradle 中:

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

在 app 模块 build.gradle 中:

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

dependencies {
   
    implementation "com.google.dagger:hilt-android:2.51"
    kapt "com.google.dagger:hilt-android-compiler:2.51"
}

如果项目使用 KSP 替代 kapt:

plugins {
   
    id 'com.google.devtools.ksp'
    id 'com.google.dagger.hilt.android'
}

dependencies {
   
    implementation "com.google.dagger:hilt-android:2.51"
    ksp "com.google.dagger:hilt-android-compiler:2.51"
}

Application 入口

Hilt 需要一个继承自 Application 的类,并标注 @HiltAndroidApp

@HiltAndroidApp
class MyApplication : Application()

别忘了在 AndroidManifest.xml 中声明:

<application
    android:name=".MyApplication"
    ... >

@HiltAndroidApp 会触发 Hilt 的代码生成,创建一个应用级别的依赖容器。这是整个 Hilt 配置里唯一需要手动改 Application 的地方。

@AndroidEntryPoint:让 Android 组件支持注入

Activity、Fragment、Service、BroadcastReceiver 需要用 @AndroidEntryPoint 标记后才能使用注入:

@AndroidEntryPoint
class ArticleActivity : AppCompatActivity() {

    @Inject
    lateinit var repository: ArticleRepository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // repository 已经可以使用了
    }
}

Fragment 也一样:

@AndroidEntryPoint
class ArticleFragment : Fragment() {

    @Inject
    lateinit var repository: ArticleRepository
}

@AndroidEntryPoint 的含义是:这个 Android 组件需要 Hilt 提供依赖。Hilt 会在合适的生命周期回调中完成注入。

注意:如果 Fragment 的宿主 Activity 没有标 @AndroidEntryPoint,Fragment 的注入会报错。Hilt 的依赖关系是沿着组件层级向上传递的。

@Inject:标记构造函数注入

对于普通的 Kotlin 类,用 @Inject 标记构造函数即可让 Hilt 知道如何创建它:

class ArticleRepository @Inject constructor(
    private val apiService: ApiService,
    private val articleDao: ArticleDao
) {
    suspend fun getArticles(): List<Article> {
        val remote = apiService.fetchArticles()
        articleDao.insertAll(remote.map { it.toEntity() })
        return remote
    }
}

当 Hilt 需要创建 ArticleRepository 时,它会自动去找 ApiServiceArticleDao 的创建方式,层层解析,直到所有依赖都满足。

如果某个依赖找不到,编译时就会报错,而不是等到运行时崩溃。这是 Hilt(基于 Dagger)最实用的特性之一。

@Module 和 @Provides:告诉 Hilt 如何创建特殊对象

不是所有类都能用 @Inject constructor 标记。比如第三方库的类、接口实现、需要复杂初始化逻辑的对象。这时需要用 Module 来提供。

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .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)
    }
}

几个关键点:

  • @Module 表示这是一个提供依赖的模块。
  • @InstallIn(SingletonComponent::class) 表示这个 Module 安装在全局单例容器中,整个应用共享。
  • @Provides 标注的方法就是创建对象的方式。
  • @Singleton 表示全局只创建一次,所有注入的地方共享同一个实例。

方法的参数就是依赖。Hilt 会自动解析:provideRetrofit 需要 OkHttpClient,Hilt 会先调用 provideOkHttpClient(),拿到结果后传给 provideRetrofit()

@Binds:接口绑定的简洁写法

如果接口只有一个实现,可以用 @Binds 代替 @Provides,代码更简洁:

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

    @Binds
    abstract fun bindArticleRepository(
        impl: ArticleRepositoryImpl
    ): ArticleRepository
}

@Binds 只能用在抽象类和抽象方法上。它告诉 Hilt:当需要 ArticleRepository 接口时,提供 ArticleRepositoryImpl 实现。

使用 @Binds 的前提是实现类的构造函数标了 @Inject

class ArticleRepositoryImpl @Inject constructor(
    private val api: ApiService,
    private val dao: ArticleDao
) : ArticleRepository {
    // ...
}

Room 数据库的 Module 写法

Room 的 Database 和 DAO 通常也用 Module 提供:

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app.db"
        ).build()
    }

    @Provides
    fun provideArticleDao(database: AppDatabase): ArticleDao {
        return database.articleDao()
    }
}

@ApplicationContext 是 Hilt 内置的 Qualifier,用来注入 Application 的 Context。如果需要 Activity 的 Context,用 @ActivityContext

这样配置后,任何类的构造函数里只要写 private val dao: ArticleDao,Hilt 就会自动创建并注入。

Hilt 与 ViewModel

ViewModel 的注入方式稍有不同。不能直接用 @Inject constructor 配合 @AndroidEntryPoint,而是要用 @HiltViewModel

@HiltViewModel
class ArticleViewModel @Inject constructor(
    private val repository: ArticleRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(ArticleUiState())
    val uiState: StateFlow<ArticleUiState> = _uiState.asStateFlow()

    fun loadArticles() {
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(loading = true)
            try {
                val articles = repository.getArticles()
                _uiState.value = ArticleUiState(articles = articles)
            } catch (e: Exception) {
                _uiState.value = _uiState.value.copy(
                    loading = false,
                    errorMessage = "加载失败"
                )
            }
        }
    }
}

在 Activity 或 Fragment 中获取 ViewModel 时,不需要任何额外配置:

@AndroidEntryPoint
class ArticleActivity : AppCompatActivity() {

    private val viewModel: ArticleViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.loadArticles()
    }
}

Compose 中使用也很自然:

@Composable
fun ArticleScreen(
    viewModel: ArticleViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // 根据 uiState 渲染 UI
}

hiltViewModel() 来自 androidx.hilt:hilt-navigation-compose,需要额外添加依赖:

implementation "androidx.hilt:hilt-navigation-compose:1.2.0"

作用域:控制对象的生命周期

Hilt 中不同的组件有不同的作用域:

作用域 组件 生命周期
@Singleton SingletonComponent 整个应用
@ActivityScoped ActivityComponent Activity 存活期间
@FragmentScoped FragmentComponent Fragment 存活期间
@ViewModelScoped ViewModelComponent ViewModel 存活期间
@ServiceScoped ServiceComponent Service 存活期间

一般规则:

  • 网络层、数据库、Repository 用 @Singleton,全局共享。
  • ViewModel 用 @HiltViewModel,内部依赖默认跟随 ViewModel 生命周期。
  • 只有确实需要和 Activity 或 Fragment 生命周期绑定的对象,才使用对应作用域。

不要在 SingletonComponent 的 Module 中注入 Activity Context。全局单例的寿命远超 Activity,持有 Activity Context 会导致内存泄漏。

@Qualifier:区分同类型依赖

有时接口有多个实现,Hilt 不知道该注入哪一个。比如项目中有正式环境和测试环境两套 ApiService

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ProdApi

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class TestApi

在 Module 中标注:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    @ProdApi
    fun provideProdApi(retrofit: Retrofit): ApiService {
        return retrofit.create(ProdApiService::class.java)
    }

    @Provides
    @Singleton
    @TestApi
    fun provideTestApi(retrofit: Retrofit): ApiService {
        return retrofit.create(TestApiService::class.java)
    }
}

注入时指定要哪个:

class DataRepository @Inject constructor(
    @ProdApi private val apiService: ApiService
)

Hilt 内置了两个常用 Qualifier:@ApplicationContext@ActivityContext,不需要自己定义。

实战:一个完整的 Hilt 项目结构

整理一下典型项目中 Hilt 的目录组织:

com.example.app/
├── MyApplication.kt              // @HiltAndroidApp
├── di/
│   ├── NetworkModule.kt          // OkHttpClient, Retrofit, ApiService
│   ├── DatabaseModule.kt         // Room Database, DAO
│   └── RepositoryModule.kt       // @Binds 接口绑定
├── data/
│   ├── remote/
│   │   └── ApiService.kt
│   ├── local/
│   │   ├── AppDatabase.kt
│   │   └── ArticleDao.kt
│   └── repository/
│       ├── ArticleRepository.kt       // 接口
│       └── ArticleRepositoryImpl.kt   // @Inject constructor
├── ui/
│   ├── ArticleActivity.kt        // @AndroidEntryPoint
│   └── ArticleViewModel.kt       // @HiltViewModel

每个文件只做一件事,依赖关系清晰:

  1. NetworkModule 提供 ApiService
  2. DatabaseModule 提供 ArticleDao
  3. RepositoryModuleArticleRepositoryImpl 绑定到 ArticleRepository 接口。
  4. ArticleViewModel 构造函数注入 ArticleRepository
  5. ArticleActivity 通过 by viewModels() 拿到 ViewModel。

整个过程没有任何手动 new 或工厂方法。Hilt 在编译期生成了所有必要的胶水代码。

测试中的 Hilt

Hilt 对测试支持很好。可以针对测试替换某些依赖:

@UninstallModules(NetworkModule::class)
@HiltAndroidTest
class ArticleRepositoryTest {

    @Inject
    lateinit var repository: ArticleRepository

    @Test
    fun testGetArticles() = runTest {
        val articles = repository.getArticles()
        assertTrue(articles.isNotEmpty())
    }
}

也可以用 @TestInstallIn 在测试时替换整个 Module:

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [NetworkModule::class]
)
object FakeNetworkModule {

    @Provides
    @Singleton
    fun provideApiService(): ApiService {
        return FakeApiService()
    }
}

这样测试时用的是假接口实现,不需要真实网络请求。

常见坑

忘记加 @AndroidEntryPoint Activity 或 Fragment 里用 @Inject 之前必须先标这个注解,否则注入不会生效,变量为 null。

Module 忘记 @InstallIn @Module 必须配合 @InstallIn 指定安装到哪个组件,否则编译报错。

在 SingletonComponent 中使用 Activity Context。 全局单例的生命周期远超 Activity,持有 Activity Context 会泄漏。需要 Context 时用 @ApplicationContext

ViewModel 没有用 @HiltViewModel 直接在 ViewModel 构造函数写 @Inject 配合 by viewModels() 不行。ViewModel 必须用 @HiltViewModel 标注。

循环依赖。 A 依赖 B,B 依赖 A,Hilt 在编译期会检测到并报错。遇到时需要重新审视架构设计,通常意味着职责划分有问题。

第三方库对象忘记提供 Module。 像 Gson、Picasso、Firebase 这些第三方库的实例不能 @Inject constructor,必须通过 @Module + @Provides 提供。

kapt/ksp 增量编译问题。 偶尔遇到编译不通过但代码没问题的情况,Clean + Rebuild 通常能解决。

总结

Hilt 的核心用法可以归纳为:

  1. @HiltAndroidApp 放在 Application 上,启动 Hilt。
  2. @AndroidEntryPoint 放在需要注入的 Android 组件上。
  3. @Inject constructor 标记普通类的构造函数。
  4. @Module + @InstallIn + @Provides 提供不能直接构造的对象。
  5. @Binds 简洁地绑定接口和实现。
  6. @HiltViewModel 配合 by viewModels()hiltViewModel() 注入 ViewModel。
  7. @Singleton 控制全局单例,其他作用域按需使用。
  8. @Qualifier 区分同类型的多个实现。

依赖注入不是为了让代码"看起来高级",而是为了解耦、方便测试、统一对象管理。Hilt 把这件事的门槛降到了很低,大部分 Android 项目都可以直接用起来。

下一篇会聊 Paging 3 分页加载,它和 Hilt、ViewModel、Flow 配合后,列表页的数据加载会变得非常清晰。

相关文章
|
5天前
|
人工智能 定位技术 SEO
我学 GEO 第 15 天:终于知道AI GEO该如何做?
我是暴走的莉莉酱,边旅行边研究AI GEO的数字游民。专注普通人如何提升“AI可见度”——让AI在回答用户问题时准确识别、理解并推荐你。不讲玄学,只做可测、可调、可持续的GEO实践。
421 125
|
8天前
|
机器学习/深度学习 人工智能 调度
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
HappyHorse 1.1 是新一代视频生成大模型,全面升级动态表现力、角色一致性、指令遵循、视觉质感与音画协同能力。支持I2V/T2V/R2V三类生成,适配短剧、电商广告、品牌营销等场景,提供高质、流畅、可控的AI视频生产力。
712 5
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
|
5天前
|
缓存 人工智能 运维
阿里云618百炼大模型Qwen3.7-Max功能、免费试用、订阅计费、配置接入详解
Qwen3.7-MAX是阿里云百炼平台推出的通义千问3.7系列旗舰大语言模型,专为智能体时代复杂任务打造,依托阿里云全域算力与自研技术,在逻辑推理、长文本处理、代码工程、长周期自主执行等领域达到行业顶尖水平。2026年618期间,该模型推出多重免费试用权益、按量计费5折、订阅套餐优惠等专属福利,覆盖个人开发者、团队与企业全场景需求,以下从核心功能、免费试用、订阅计费、配置接入四方面展开详细解析。
414 123
|
4天前
|
人工智能 自然语言处理 API
阿里云Token Plan团队版解析:功能、三档套餐与省钱订阅指南
阿里云百炼平台推出的Token Plan团队版,是面向企业与团队的AI大模型订阅服务,以Credits为统一计量单位,整合文本与图像生成模型,提供团队管理、数据安全、多工具兼容等核心能力,解决团队零散订阅AI服务的管理混乱、成本失控、数据安全等痛点。本文将从核心定位、套餐详情、计费规则、团队管理、工具兼容、便宜订阅技巧等方面,全面解析Token Plan团队版,帮助企业与团队高效、低成本地使用AI服务。
308 108
|
5天前
|
存储 人工智能 数据可视化
别再手动复制 Skill 了:多 Agent 时代的 Skill 管理方案
多 Agent 场景下 Skill 的统一管理与同步。
256 123
|
19天前
|
缓存 测试技术 API
Qwen 3.7 Plus 与 Max 实测:性价比与多模态能力差异解析(2026)
2026 年 6 月 1 日,阿里悄无声息地发布了 Qwen 3.7 Plus,距 Qwen 3.7 Max 上线刚好 11 天。同样的 1M 上下文,同样的 35 小时自治上限。但价格才是头条:Plus 是 0.40/M输入,Max是 2.50/M——便宜约 6 倍——并且还能看图、看视频。Vision Arena 上 Plus 已经排到 #16。所以这周真正值得讨论的问题不是”要不要为视觉能力买单”,而是”Max 凭什么用 6 倍价格换来 2 个百分点的 benchmark 领先”。
|
12天前
|
缓存 人工智能 运维
GLM 5.2自托管全流程实战:硬件选型、vLLM/SGLang部署与成本盈亏测算
2026年智谱发布GLM 5.2超大混合专家模型,区别于以往仅开放API的闭源大模型,该模型权重以MIT开源协议对外发布,企业与开发者可完整下载、本地审计、私有化部署,实现数据不出环境、自定义微调、自主调度推理资源。GLM 5.2拥有753B总参数,原生支持百万级上下文窗口,在代码生成、长文档推理、数学逻辑等多项基准测试中对标国际顶尖商用模型,是首款可完整自托管的前沿代码向大模型。
937 0
|
13天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)