Jetpack Hilt 框架的基本使用
什么是 Hilt?
Hilt 是一个功能强大、用法简单的依赖注入框架,于 2020 年加入到 Jetpack 家族中。它是 Android 团队联系了 Dagger2 团队,一起开发出来的一个专门面向 Android 的依赖注入框架。相比于 Dagger2,Hilt 最明显的特征就是简单,并且提供了 Android 专属的 API。
在项目中引入 Hilt
此部分以使用了 Java 17 的 Jetpack Compose 新项目为例,开发工具使用 Android Studio 2023.1.1 Canary 版本。信息截止 2023 年 5 月。
第一步,打开 gradle/libs.versions.toml 文件,加入 Hilt 的 Gradle 插件相关配置:
[versions]
hilt = "2.46.1"[plugins]
hiltAndroid = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
再打开项目的 build.gradle.kts 文件,引入插件:
plugins {alias(libs.plugins.hiltAndroid) apply false
}
第二步,在 libs.versions.toml 中加入 Hilt 的插件和依赖库:
[libraries]
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
由于 Hilt 基于编译时注解实现,需要添加 kotlin-kapt 插件。在 app 的 build.gradle.kts 文件中再加入如下配置:
plugins {kotlin("kapt")
}android {compileOptions {// 这里设置为 Java 8 或以上即可sourceCompatibility = JavaVersion.VERSION_17targetCompatibility = JavaVersion.VERSION_17}
}dependencies {implementation(libs.hilt.android)kapt(libs.hilt.compiler)
}
现在,Hilt 已被成功引入到项目中。
Hilt 的基本用法
准备工作
使用 Hilt 时,必须自定义一个 Application 类,否则 Hilt 将无法正常工作。自定义的 Application 类中可以不写任何代码,但必须要加上 @HiltAndroidApp 注解。
@HiltAndroidApp
class MyApplication : Application() {
}
接下来将 MyApplication 注册到 AndroidManifest.xml 中:
<applicationandroid:name=".MainApplication">
</application>
准备工作到此已完成,接下来的任务是根据具体的业务逻辑使用 Hilt 进行依赖注入。
入口点
Hilt 简化了 Dagger2 的操作,使我们无需使用 @Component 注解编写桥接层逻辑,同时也限制了注入功能只能从几个 Android 固定的入口点开始:Application、Activity、Fragment、View、Service、BroadcastReceiver。
其中,只有 Application 入口点使用 @HiltAndroidApp 注解声明,其他所有入口点均使用 @AndroidEntryPoint 注解声明。例如,若希望在 Activity 中进行依赖注入,只需这样声明:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)}
}
不带参数的依赖注入
尝试定义一个类,在其构造函数上声明 @Inject 注解,如下:
class MusicPlayer() @Inject constructor() {fun init() {Log.d("MusicPlayer", "init")}
}
在 Activity 中注入,即可成功调用上面编写的 init() 方法:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {@Injectlateinit var musicPlayer: MusicPlayeroverride fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)musicPlayer.init()}
}
带参数的依赖注入
在上面的 MusicPlayer 类构造函数中加入一个 AudioDriver 参数,代表播放器组件依赖的系统音频驱动,如下所示:
class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {fun init() {Log.d("MusicPlayer", "init, audioDriver=$audioDriver")}
}
声明 AudioDriver 类时,也为其构造函数加上 @Inject 注解:
class AudioDriver @Inject constructor() {}
不需要再修改任何代码,即可成功调用 init() 方法,并成功打印 audioDriver 的 hashCode。
接口的依赖注入
定义一个 IDecoder 接口,代表播放音频时必备的音频解码器。接口中有两个待实现方法,分别用于创建解码器和销毁解码器、释放内存:
interface IDecoder {fun create()fun destroy()
}
实现用于解码 WAV 文件的 WavDecoder,在构造函数中加上 @Inject 注解:
class WavDecoder @Inject constructor() : IDecoder {override fun create() {Log.d("WavDecoder", "create")}override fun destroy() {Log.d("WavDecoder", "destroy")}
}
此外,再实现用于解码 MP3 文件的 Mp3Decoder,同样需要声明 @Inject 注解:
class Mp3Decoder @Inject constructor() : IDecoder {override fun create() {Log.d("Mp3Decoder", "create")}override fun destroy() {Log.d("Mp3Decoder", "destroy")}
}
新建一个抽象类,命名为 DecoderModule,在这个模块中通过定义抽象函数提供 IDecoder 接口所需要的实例:
@Module
@InstallIn(ActivityComponent::class)
abstract class DecoderModule {@Bindsabstract fun bindDecoder(wavDecoder: WavDecoder): IDecoder
}
修改 MusicPlayer 类中的代码,调用刚刚提供的解码器:
class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {@Injectlateinit var decoder: IDecoderfun init() {decoder.create()Log.d("MusicPlayer", "init, audioDriver=$audioDriver")decoder.destroy()}
}
此时再调用 init() 方法,即可看到 TAG 为 WavDecoder 的日志。
给相同类型注入不同实例
@Qualifer 接口用于给相同类型的类或接口注入不同的实例。分别定义两个注解,如下:
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindWavDecoder@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindMp3Decoder
回到 DecoderModule 中,定义两个抽象函数,将刚才定义的两个注解分别添加到两个函数上方:
@Module
@InstallIn(ActivityComponent::class)
abstract class DecoderModule {@BindWavDecoder@Bindsabstract fun bindWavDecoder(wavDecoder: WavDecoder): IDecoder@BindMp3Decoder@Bindsabstract fun bindMp3Decoder(mp3Decoder: WavDecoder): IDecoder
}
回到 MusicPlayer 类,此时就可以让这个播放器同时支持两种格式的解码:
class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {@BindWavDecoder@Injectlateinit var wavDecoder: IDecoder@BindMp3Decoder@Injectlateinit var mp3Decoder: IDecoderfun init() {wavDecoder.create()mp3Decoder.create()Log.d("MusicPlayer", "init, audioDriver=$audioDriver")wavDecoder.destroy()mp3Decoder.destroy()}
}
第三方类的依赖注入
假如我们想在 MainActivity 中注入 OkHttpClient,该类由 OkHttp 提供,我们无法为其构造函数加上 @Inject 注解。这种情况下,需要借助 @Module 注解定义一个非抽象类,此处命名为 NetworkModule。
在该类中定义一个方法,加上 @Provides 注解,在函数体中提供一个 OkHttpClient 的实例,如下:
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {@Providesfun provideOkHttpClient(): OkHttpClient {return OkHttpClient.Builder().connectTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).build()}
}
回到 MainActivity,使用 @Inject 注入 OkHttpClient,即可成功运行:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {@Injectlateinit var okHttpClient: OkHttpClient
}
为了方便开发者使用,我们在 NetworkModule 再给 Retrofit 类型提供实例,编写如下代码:
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {@Providesfun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {return Retrofit.Builder().addConverterFactory(GsonConverterFactory.create()).baseUrl("http://example.com/").client(okHttpClient).build()}
}
方法 provideRetrofit() 中的 okHttpClient 参数则会由 Hilt 自动使用 provideOkHttpClient() 方法进行创建。此时在 MainActivity 中再次尝试注入 Retrofit,也可以正常运行:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {@Injectlateinit var retrofit: Retrofit
}
Hilt 内置组件
使用 @Module 注入的类,需要使用 @InstallIn 注解指定注入的范围。Hilt 一共提供了 7 种组件类型,分别用于注入到不同的场景:
| 组件名 | 注入范围 |
|---|---|
| ApplicationComponent | Application |
| ActivityRetainedComponent | ViewModel |
| ActivityComponent | Activity |
| FragmentComponent | Fragment |
| ViewComponent | View |
| ViewWithFragmentComponent | 使用 @WithFragmentBindings 定义的 View |
| ServiceComponent | Service |
若希望上方定义的 NetworkModule 可以在全项目中使用,只需这样修改:
@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {
}
Hilt 组件作用域
Hilt 默认会为每次的依赖注入行为都创建不同的实例。对应前面的 7 个内置组件,Hilt 也提供了 7 种组件作用域注解,如下所示:
| 组件作用域 | 对应内置组件 |
|---|---|
| @Singleton | ApplicationComponent |
| @ActivityRetainedScope | ActivityRetainedComponent |
| @ActivityScoped | ActivityComponent |
| @FragmentScoped | FragmentComponent |
| @ViewScoped | ViewComponent |
| @ViewScoped | ViewWithFragmentComponent |
| @ServiceScoped | ServiceComponent |
若希望 NetworkModule 中提供的 Retrofit 和 OkHttpClient 实例在全局只创建一份,只需加上 @Singleton 注解:
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {@Singleton@Providesfun provideOkHttpClient(): OkHttpClient {return OkHttpClient.Builder().connectTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).build()}@Singleton@Providesfun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {return Retrofit.Builder().addConverterFactory(GsonConverterFactory.create()).baseUrl("http://example.com/").client(okHttpClient).build()}
}
作用域注解也可以被直接声明到任何可注入类的上方,例如前面添加的 AudioDriver 类:
@Singleton
class AudioDriver @Inject constructor() {
}
这就表示 AudioDriver 在全局范围内都会共享同一个实例,且全局都可以对 AudioDriver 类进行依赖注入。

如上图所示,对某个类声明了某种作用域注解后,该注解的箭头所能指到的地方,都可以对该类进行依赖注入,同时在该范围内共享同一个实例。
预置 Qualifier
若前面定义的 AudioDriver 类需要一个 Context 参数,需要在该参数前加上一个 @ApplicationContext 注解,Hilt 会提供一个 Application 类型的 Context 给到 AudioDriver 类当中,代码即可编译通过:
@Singleton
class AudioDriver @Inject constructor(@ApplicationContext val context: Context) {
}
如果需要 Activity 或其他类型的 Context,使用 Hilt 预置的另外一种 Qualifier 即可:
@Singleton
class AudioDriver @Inject constructor(@ActivityContext val context: Context) {
}
此时编译代码会报错,因为 AudioDriver 类是 Singleton 的,与 Qualifier 的范围不匹配。
对于 Activity 和 Application 这两个类型,Hilt 为它们预置好了注入功能。如果某个类依赖于 Activity 或 Application,不需要添加任何注解,Hilt 可以自动识别,如下:
class AudioDriver @Inject constructor(val application: Application) {
}class AudioDriver @Inject constructor(val activity: Activity) {
}
注意必须是 Application 和 Activity 这两个类型,即使声明它们的子类型,编译都无法通过。
HiltViewModel 的使用
先在 libs.versions.toml 中声明相关依赖,如下:
[versions]
hilt-lifecycle-viewmodel = "1.0.0-alpha03"[libraries]
androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hilt-lifecycle-viewmodel" }
在 build.gradle.kts 中添加依赖:
dependencies {kapt(libs.androidx.hilt.compiler)
}
通过 @HiltViewModel 注解提供一个 ViewModel:
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
}
然后,带有 @AndroidEntryPoint 注解的 Activity 或 Fragment 即可使用 ViewModelProvider 或 by viewModels() 扩展照常获取 ViewModel 实例:
class MainActivity : AppCompatActivity() {private val mainViewModel: MainViewModel by viewModels()
}
相关文章:
Jetpack Hilt 框架的基本使用
什么是 Hilt? Hilt 是一个功能强大、用法简单的依赖注入框架,于 2020 年加入到 Jetpack 家族中。它是 Android 团队联系了 Dagger2 团队,一起开发出来的一个专门面向 Android 的依赖注入框架。相比于 Dagger2,Hilt 最明显的特征就…...
exec()在不同namespace执行结果的区别
记录一个很tricky的问题,下面这段code在执行func1时会出现NameError: name List is not defined,但执行func2时一切正常。 import typescontent """ from typing import Listclass GeneratedData:qna: List"""def func1…...
人工智能革命中的22个隐藏职业:推动科技行业的变革
作者 | Manas Sadangi 随着人工智能技术的不断发展,它正在创造一系列前所未有的就业机会。虽然数据科学家、机器学习工程师和人工智能研究人员等传统的人工智能角色得到了广泛认可,但在推动科技行业变革方面,还有一些鲜为人知的职业同样重要。…...
算法题3 — 求字符串中的最长子串
文章目录 题目示例示例1示例2示例3 解题解法1解法2 leetcode 题目 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 示例1 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 示例…...
【FreeRTOS】——中断优先级设置中断相关寄存器临界段代码保护调度器挂起与恢复
目录 前言: 一、中断优先级设置 二、中断相关寄存器(STM32-Cortex M3) 三、临界段代码保护 四、任务调度器的挂起和恢复 总结: 前言: 博客笔记根据正点原子视频教程编辑,仅供学习交流使用࿰…...
1.2 什么是eBPF?(下)
四,eBPF的优势 4.1 eBPF程序的动态加载 eBPF程序可以动态地加载到内核中,或从内核中删除。这个要与内核模块的加载与卸载区分开来。这里顺便讨论下eBPF程序与内核模块的区别,如下: 而Linux内核模块是面向内核API编程的,可以直接运行在内核当中。eBPF程序是面向BPF体系结构…...
掌握哪些测试技术才能说自己已经学成了?
一、过硬的基础能力 其实所有的测试大佬都是从底层基础开始的,随着时间,经验的积累慢慢变成大佬。要想稳扎稳打在测试行业深耕,成为测试大牛,首当其冲的肯定就是拥有过硬的基础,所有的基础都是根基,后期所…...
什么是C语言?
C语言是一种高级编程语言,于1972年由Dennis Ritchie在贝尔实验室开发出来。它是一种通用的、结构化的编程语言,被广泛用于系统软件、嵌入式系统、游戏开发以及科学计算等领域。 C语言的设计目标是提供一种简洁、高效、可移植的编程语言,以便…...
SAP-物料主数据-质量管理视图字段解析
过账到质检库存:要勾选,否则收货后库存不进入质检库存HU检验:收货到启用HU管理的库位时产生检验批,例如某个成品物料是收货到C002库位,该库位启用了HU管理,那么此处要勾选。但是如果勾选了,却收…...
TOP RPA·脱普×实在丨日用品企业脱普签约实在智能,构建全域数据智能运营系统
近日,实在智能与脱普日用化学品(中国)有限公司(简称“脱普企业”)在脱普企业上海总部举行“全域数据智能运营”项目启动会,双方领导及项目组关键成员共同参会,就项目目标、实施进程、沟通机制等…...
【Android】Handler(四)Looper的相关知识点
Handler 机制是 Android 多线程间通信的一种常见方式。每个 Handler 对象由一个 Looper 和一个 MessageQueue 组成,用于将 Message 对象处理到指定的线程中。通过创建 Handler 实例,在子线程中创建 Message 对象并通过sendMessage()方法发送给 Handler&a…...
Redis缓存雪崩及解决办法
缓存雪崩 1.缓存雪崩是指在同- -时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到 达数据库,带来巨大压力。 2.解决方案: ◆给不同的Key的TTL添加随机值 ◆利用Redis集群提高服务的可用性 ◆给缓存业务添加降级限流策略 降级可做为系统的保底…...
Maven私服仓库配置-Nexus详解
目录 一、什么是Maven私服?二、Maven 私服优势三、Maven 私服搭建四、Sonatype Nexus介绍五、Nexus仓库属性和分类六、Nexus仓库配置以及创建仓库七、Nexus配置用户角色八、Maven SNAPSHOT(快照)九、项目当中配置Nexus上传依赖十、项目当中配置Nexus下载依赖十一、测…...
Systrace系列10 —— Binder 和锁竞争解读
本文主要是对 Systrace 中的 Binder 和锁信息进行简单介绍,简单介绍了 Binder 的情况,介绍了 Systrace 中 Binder 通信的表现形式,以及 Binder 信息查看,SystemServer 锁竞争分析等。 Binder 概述 Android 的大部分进程间通信都使用 Binder,这里对 Binder 不做过多的解释…...
React Hooks中使用useState异步回调获取不到最新值的问题
ReactHook中useState异步回调获取不到最新值及解决⽅案 预先了解 setState 的两种传参⽅式 1、直接传⼊新值 setState(options); 列如: const [state, setState] useState(0); setState(state 1); 2、传⼊回调函数 setState(callBack); 例如: …...
JavaScript 高级 (完结)
目录 深浅拷贝 浅拷贝 深拷贝 递归实现深拷贝 js库lodash里面cloneDeep内部实现了深拷贝 JSON序列化 异常处理 throw 抛异常 try /catch 捕获异常 debugg 处理this this指向 普通函数 箭头函数 改变this call() apply() bind() call apply bind 总结 性能优化…...
【P30】JMeter 事务控制器(Transaction Controller)
文章目录 一、事务控制器(Transaction Controller)参数说明二、测试计划设计2.2.1、勾选 Generate parent sample2.2.1、勾选 Include duration of timer and pre-post processors in generated sample 一、事务控制器(Transaction Controlle…...
【MySQL】MySQL的事务原理和实现?
文章目录 MySQL事务的底层实现原理一、事务的目的可靠性和并发处理 二、实现事务功能的三个技术2.1 redo log 与 undo log介绍2.1.1 redo log2.1.2undo log 2.2 mysql锁技术2.2.1 mysql锁技术 2.3 MVCC基础 三、事务的实现3.1 原子性的实现3.1.1 undo log 的生成3.1.2 根据undo…...
S7-300Smart1200的ISO on TCP通信
1、西门子PLC的通信资源 1.1 S7-1200 的PROFINET 通信口 S7-1200 CPU 本体上集成了一个 PROFINET 通信口,支持以太网和基于 TCP/IP 的通信标准。使用这个通信口可以实现 S7-1200 CPU 与编程设备的通信,与HMI触摸屏的通信,以及与其它 CPU 之间的通信。这个PROFINET 物理接口…...
Spark写入Hive报错Mkdir failed on :com.alibaba.jfs.JindoRequestPath
1. 报错内容 23/05/31 14:32:13 INFO [Driver] FsStats: cmdmkdirs, srcoss://sync-to-bi.[马赛克].aliyuncs.com/tmp/hive, dstnull, size0, parameterFsPermission:rwx-wx-wx, time-in-ms32, version3.5.0 23/05/31 14:32:13 ERROR [Driver] ApplicationMaster: User class …...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
