游乐游手机版
首页/AI教程/文章详情

Hilt依赖注入从手动new到自动装配的完整实现方法

时间:2026-07-01 15:17
Hilt基于Dagger的安卓依赖注入方案,编译期检查依赖完整性,自动为活动、片段、视图模型等组件注入对象,简化配置,解决手动创建对象导致的耦合高、难测试、复用困难等问题。

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

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

最直接的写法是什么?在构造函数里自己创建。

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

这种做法问题不少:耦合度高,ViewModel 得知道怎么创建各种底层对象,换实现就得改它;难以测试,单元测试想传个假的数据库或 Mock 接口进去,发现根本做不到;对象复用困难,多个地方都需要 ApiService,各自创建出来的就不是同一个实例。

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

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

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

为什么选 Hilt

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

Hilt 的核心优势很明确:它具备 Android 感知,自动为 Application、Activity、Fragment、Service、ViewModel 提供注入支持;编译期就能验证依赖是否缺失或类型不匹配,不会等到运行时崩溃;所有组件使用相同的注解体系,团队协作成本低;而且 Google 官方推荐,Jetpack 组件逐步都在提供 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

@HiltAndroidAppclass MyApplication : Application()

别忘了在 AndroidManifest.xml 中声明:

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

@AndroidEntryPoint:让 Android 组件支持注入

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

@AndroidEntryPointclass ArticleActivity : AppCompatActivity() {@Injectlateinit var repository: ArticleRepositoryoverride fun onCreate(sa vedInstanceState: Bundle?) {super.onCreate(sa vedInstanceState)// repository 已经可以使用了}}

Fragment 也一样:

@AndroidEntryPointclass ArticleFragment : Fragment() {@Injectlateinit 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

{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@Singletonfun provideOkHttpClient(): OkHttpClient {return OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS).addInterceptor(HttpLoggingInterceptor().apply {level = HttpLoggingInterceptor.Level.BODY}).build()}@Provides@Singletonfun provideRetrofit(client: OkHttpClient): Retrofit {return Retrofit.Builder().baseUrl("https://api.example.com/").client(client).addConverterFactory(GsonConverterFactory.create()).build()}@Provides@Singletonfun provideApiService(retrofit: Retrofit): ApiService {return retrofit.create(ApiService::class.ja va)}}

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

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

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

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

@Module@InstallIn(SingletonComponent::class)abstract class RepositoryModule {@Bindsabstract 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@Singletonfun provideDatabase(@ApplicationContext context: Context): AppDatabase {return Room.databaseBuilder(context,AppDatabase::class.ja va,"app.db").build()}@Providesfun 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

@HiltViewModelclass ArticleViewModel @Inject constructor(private val repository: ArticleRepository) : ViewModel() {private val _uiState = MutableStateFlow(ArticleUiState())val uiState: StateFlow = _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 时,不需要任何额外配置:

@AndroidEntryPointclass ArticleActivity : AppCompatActivity() {private val viewModel: ArticleViewModel by viewModels()override fun onCreate(sa vedInstanceState: Bundle?) {super.onCreate(sa vedInstanceState)viewModel.loadArticles()}}

Compose 中使用也很自然:

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

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

implementation "androidx.hilt:hilt-na vigation-compose:1.2.0"

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

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

作用域组件生命周期
@SingletonSingletonComponent整个应用
@ActivityScopedActivityComponentActivity 存活期间
@FragmentScopedFragmentComponentFragment 存活期间
@ViewModelScopedViewModelComponentViewModel 存活期间
@ServiceScopedServiceComponentService 存活期间

一般规则:网络层、数据库、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@ProdApifun provideProdApi(retrofit: Retrofit): ApiService {return retrofit.create(ProdApiService::class.ja va)}@Provides@Singleton@TestApifun provideTestApi(retrofit: Retrofit): ApiService {return retrofit.create(TestApiService::class.ja va)}}

注入时指定要哪个:

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

每个文件只做一件事,依赖关系很清晰:NetworkModule 提供 ApiServiceDatabaseModule 提供 ArticleDaoRepositoryModuleArticleRepositoryImpl 绑定到 ArticleRepository 接口,ArticleViewModel 构造函数注入 ArticleRepositoryArticleActivity 通过 by viewModels() 拿到 ViewModel。整个过程没有任何手动 new 或工厂方法,Hilt 在编译期生成了所有必要的胶水代码。

测试中的 Hilt

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

@UninstallModules(NetworkModule::class)@HiltAndroidTestclass ArticleRepositoryTest {@Injectlateinit var repository: ArticleRepository@Testfun testGetArticles() = runTest {val articles = repository.getArticles()assertTrue(articles.isNotEmpty())}}

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

@Module@TestInstallIn(components = [SingletonComponent::class],replaces = [NetworkModule::class])object FakeNetworkModule {@Provides@Singletonfun 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 的核心用法其实可以归纳得很简洁:@HiltAndroidApp 放在 Application 上,启动 Hilt;@AndroidEntryPoint 放在需要注入的 Android 组件上;@Inject constructor 标记普通类的构造函数;@Module + @InstallIn + @Provides 提供不能直接构造的对象;@Binds 简洁地绑定接口和实现;@HiltViewModel 配合 by viewModels()hiltViewModel() 注入 ViewModel;@Singleton 控制全局单例,其他作用域按需使用;@Qualifier 区分同类型的多个实现。

依赖注入不是为了让代码"看起来高级",而是为了解耦、方便测试、统一对象管理。Hilt 把这件事的门槛降到了很低,大部分 Android 项目都可以直接用起来。下一篇会聊 Paging 3 分页加载,它和 Hilt、ViewModel、Flow 配合后,列表页的数据加载会变得非常清晰。

来源:https://developer.aliyun.com/article/1744340
上一篇摆脱四个AI编程工具间复制粘贴我开发了Roundtable 下一篇阿里云ECS安装宝塔Linux面板用Alibaba Cloud Linux 3.2104可行吗
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
RAG四标融合企业知识资产体系四库协同GEO优化实践
AI教程 · 2026-07-01

RAG四标融合企业知识资产体系四库协同GEO优化实践

生成式AI正在彻底改写信息检索的底层逻辑。传统SEO依赖关键词堆砌和外链建设的策略,在大模型的内容采信规则下已经基本失效。取而代之的,是生成式引擎优化(GEO)。它不再关注外链数量,而是重点衡量你的知识是否结构化、证据链是否坚实、信源是否可靠——这些维度才是RAG(检索增强生成)架构真正看重的核心指

一个普通上班人分享WorkBuddy使用心得与真实体验
AI教程 · 2026-07-01

一个普通上班人分享WorkBuddy使用心得与真实体验

前言 最近我开始使用WorkBuddy——这是腾讯推出的一款AI办公工作台。差不多用了一周时间,趁印象还新鲜,把真实的使用感受记录下来,给还在犹豫的朋友做个参考。不吹不黑,只说实际体验。 初印象:不只是聊天机器人 之前用过不少AI工具,大多数就是个对话框,你问它答,答完就结束了。WorkBuddy不

AI幻觉变真功能实战教程:App Inventor 2视频录制拓展一周开发实录
AI教程 · 2026-07-01

AI幻觉变真功能实战教程:App Inventor 2视频录制拓展一周开发实录

先讲一个颇具戏剧性的开端。 这件事的开端颇显荒诞——有用户前来咨询,称AI Pro版的介绍中提到我们有一款“视频录制拓展”。团队全体成员都感到困惑,翻遍产品列表,发现根本不存在该组件。AI那种“一本正经胡说八道”的能力,这次确实让我们陷入尴尬。 按常理,此事到此便可结束——一句“抱歉,暂时没有这个拓

别再混淆OLAP和SQL-on-Hadoop两者查询本质不同
AI教程 · 2026-07-01

别再混淆OLAP和SQL-on-Hadoop两者查询本质不同

OLAP和SQL-on-Hadoop虽都使用SQL查询数据,但本质不同。SQL-on-Hadoop负责海量数据批量计算与ETL,查询速度秒级至分钟级;OLAP通过预聚合实现毫秒级多维分析,适合BI报表。两者在数据平台分工协作,前者是后厨加工,后者是前台快速服务。

GEO优化深度解析:AI偏好FAQ还是长文内容?
AI教程 · 2026-07-01

GEO优化深度解析:AI偏好FAQ还是长文内容?

在GEO优化中,AI对内容形式无统一偏好:FAQ在简单查询中引用率41%,长文在复杂查询中达58%。内容应基于用户意图选择形式,FAQ适配简单事实类问题,长文建立主题权威,两者互补而非替代。