Jetpack Hilt 框架的基本使用

news2024/11/18 13:50:08

什么是 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_17
        targetCompatibility = 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 中:

<application
    android: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() {
    @Inject
    lateinit var musicPlayer: MusicPlayer

    override 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 {
    @Binds
    abstract fun bindDecoder(wavDecoder: WavDecoder): IDecoder
}

修改 MusicPlayer 类中的代码,调用刚刚提供的解码器:

class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {
    @Inject
    lateinit var decoder: IDecoder

    fun 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
    @Binds
    abstract fun bindWavDecoder(wavDecoder: WavDecoder): IDecoder
    
    @BindMp3Decoder
    @Binds
    abstract fun bindMp3Decoder(mp3Decoder: WavDecoder): IDecoder
}

回到 MusicPlayer 类,此时就可以让这个播放器同时支持两种格式的解码:

class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {
    @BindWavDecoder
    @Inject
    lateinit var wavDecoder: IDecoder
    
    @BindMp3Decoder
    @Inject
    lateinit var mp3Decoder: IDecoder

    fun 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 {
    @Provides
    fun 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() {
    @Inject
    lateinit var okHttpClient: OkHttpClient
}

为了方便开发者使用,我们在 NetworkModule 再给 Retrofit 类型提供实例,编写如下代码:

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
    @Provides
    fun 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() {
    @Inject
    lateinit var retrofit: Retrofit
}

Hilt 内置组件

使用 @Module 注入的类,需要使用 @InstallIn 注解指定注入的范围。Hilt 一共提供了 7 种组件类型,分别用于注入到不同的场景:

组件名注入范围
ApplicationComponentApplication
ActivityRetainedComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponent使用 @WithFragmentBindings 定义的 View
ServiceComponentService

若希望上方定义的 NetworkModule 可以在全项目中使用,只需这样修改:

@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {
}

Hilt 组件作用域

Hilt 默认会为每次的依赖注入行为都创建不同的实例。对应前面的 7 个内置组件,Hilt 也提供了 7 种组件作用域注解,如下所示:

组件作用域对应内置组件
@SingletonApplicationComponent
@ActivityRetainedScopeActivityRetainedComponent
@ActivityScopedActivityComponent
@FragmentScopedFragmentComponent
@ViewScopedViewComponent
@ViewScopedViewWithFragmentComponent
@ServiceScopedServiceComponent

若希望 NetworkModule 中提供的 Retrofit 和 OkHttpClient 实例在全局只创建一份,只需加上 @Singleton 注解:

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
                    .connectTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .writeTimeout(20, TimeUnit.SECONDS)
                    .build()
    }

    @Singleton
    @Provides
    fun 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()
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/592200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flutter 笔记 | Flutter 核心原理(四)绘制流程

Vsync 机制 在分析首帧渲染的过程中&#xff0c;可以发现Render Tree的渲染逻辑&#xff08;handleDrawFrame方法&#xff09;是直接执行的&#xff0c;但是后续每一帧的渲染都是Framework的主动调用导致的吗&#xff1f;实际上并非如此&#xff0c;也不能如此。试想一下&…

【017】C++ 指针变量详解,理解指针变量

C 指针变量详解 引言一、内存概述二、指针变量2.1、地址和指针变量的关系2.2、定义指针变量2.3、指针变量的初始化2.4、指针类型2.5、案例2.6、注意事项 三、数组元素的指针3.1、概述3.2、在使用中 [ ] 就是 *()的缩写3.3、指向同一数组的元素的两个指针变量间的关系 四、字符串…

6月销量狂欢季:测评自养号助力,引爆跨境电商销量!

随着夏季的到来&#xff0c;跨境电商卖家们迎来了一个极佳的销售机会。6月作为夏季的重要节点&#xff0c;各种活动和节日都为卖家们提供了引流和销售的良机。然而&#xff0c;要真正实现销量的爆发&#xff0c;单纯依靠传统的营销手段可能难以达到预期的效果。在这篇文章中&am…

AI+边缘,是如何加速制造转型的?

在现代工业中&#xff0c;提起智慧工厂、智能制造有一个经久不衰的话题&#xff0c;那便是IT和OT的融合。 IT&#xff08;Information Technology&#xff09;部门专注于处理数据&#xff0c;整个业务系统需要它来维持运营。而OT&#xff08;Operation Technology&#xff09;…

2023智源大会议程公开 |智能的物质基础专题论坛

6月9日&#xff0c;2023北京智源大会&#xff0c;将邀请这一领域的探索者、实践者、以及关心智能科学的每个人&#xff0c;共同拉开未来舞台的帷幕&#xff0c;你准备好了吗&#xff1f;与会知名嘉宾包括&#xff0c;图灵奖得主Yann LeCun、图灵奖得主Geoffrey Hinton、OpenAI创…

基于OA的采购系统和专业的招标采购管理系统区别

当前采购信息化百家争鸣&#xff0c;既有初级版的审批和记录电子化&#xff0c;也有中级版的业务全流程电子化&#xff0c;还有升级版的数智化创新形式&#xff08;如电商平台、智能评标、供应商风险评估、专家行为画像、大数据统计分析等&#xff09;。 近年来&#xff0c;招标…

Zotero文献在word中的引用

前提 确保你的word中有Zotero插件。如下图示&#xff1a; 具体操作 Step01 Zeroto中下载样式 在Zotero中添加相应的文献样式&#xff0c;具体如下&#xff1a; 打开Zotero“编辑”中的首选项&#xff0c;打开“引用”&#xff0c;从“获取更多样式”中搜寻你想要的文献样…

JAVA开发(手工处理数据库表数据的一些示例算法)

背景&#xff1a; 在项目开发中&#xff0c;有时候需要手动处理一下数据库表的数据。涉及到数据得到备份、恢复&#xff0c;清洗&#xff0c;计算&#xff0c;合并等操作。 举例记录一下最近对数据的一些处理过程。 1、对数据表进行数据量统计 select count(*) from table…

API接口的重要性和好处|附加淘宝api接口展示案例|商品数据采集演示

随着互联网的发展&#xff0c;API接口已经成为许多企业进行信息交流和数据管理的重要工具。通过API接口&#xff0c;企业之间能够快速、可靠地进行数据传输和信息共享&#xff0c;从而提高了企业的生产效率和服务质量。以下是API接口的重要性和好处的文章&#xff1a; 1.提高生…

Zabbix从入门到精通以及案例实操系列

1、Zabbix入门 1.1、Zabbix概述 Zabbix是一款能够监控各种网络参数以及服务器健康性和完整性的软件。Zabbix使用灵活的通知机制&#xff0c;允许用户为几乎任何事件配置基于邮件的告警。这样可以快速反馈服务器的问题。基于已存储的数据&#xff0c;Zabbix提供了出色的报告和…

5.2.2 IP地址的分配和使用

5.2.2 IP地址的分配和使用 我们已经学习了分类的IP地址&#xff0c;我们就来一起学习一下在实际的应用中IP地址是如何分配和使用的。在最初的IP地址编址方案中&#xff0c;因特网的每个物理网络都必须被分配一个唯一的网络地址&#xff0c;该网络上的主机每个主机都使用该网络…

Jenkins——maven 插件配置

文章目录 一、Maven 的集成二、在执行job的机器上安装好maven三、下载 maven 插件四、配置全局工具五、Maven 相关使用1、新建 job2、自由风格 job 中命令行使用 mvn 命令3、构建操作 一、Maven 的集成 在 Jenkins 上构建 Java 项目时需要使用 Maven 来进行构建打包 二、在执…

【笔记】微机原理及接口技术2 -- 存储器与IO接口技术

目录 存储器存储器分类存储器常用性能指标半导体存储器随机存取存储器 RAM动态随机存储器 DRAM三态缓冲器只读存储器 ROM存储器与 CPU 链接存储空间扩展方式内存寻址方法&#xff08;片选方式&#xff09;存储器小节思考题 I/O 接口技术IO 接口概述IO 端口编址寻址输入输出控制…

让仓库“零误差”,WMS仓库管理系统助力供应链升级

现代供应链的核心是以消费者和库存管理为中心&#xff0c;通过降低库存来提高产品的流通速度和供应链效率。而在信息技术快速发展的今天&#xff0c;企业的库存管理也面临着新的挑战&#xff1a; 1.仓库货品种类多&#xff0c;数量多&#xff0c;且摆放混乱&#xff0c;加大了查…

RWKV – transformer 与 RNN 的强强联合

在 NLP (Natural Language Processing, 自然语言处理) 领域&#xff0c;ChatGPT 和其他的聊天机器人应用引起了极大的关注。每个社区为构建自己的应用&#xff0c;也都在持续地寻求强大、可靠的开源模型。自 Vaswani 等人于 2017 年首次提出 Attention Is All You Need 之后&am…

SAP-MM-发票行项目格式

目的&#xff1a;SAP提供标准事物代码屏幕变式&#xff0c;但因各个公司运用的方式不同&#xff0c;可采用屏幕变式进行自定义&#xff0c;方便最终用户使用&#xff1b; 方案&#xff1a;采用屏幕变式T-CODE&#xff1a;OLMRLIST对相应事物代码进行调整&#xff1a; 案例&am…

ASCII Unicode UTF-8等等编码介绍

目录 背景 Unicode UTF-8 ISO-8859-1 GB2312和GBK ANSI UTF-16LE 和UTF-16BE UTF-16 LE 和BE是什么 如何处理字节序问题 "带有BOM的UTF-8"又是什么&#xff1f; 背景 由于计算机是美国人发明的&#xff0c;因此最早只有127个字母被编码到计算机中&#x…

1.5k star,搭建一个属于自己或团队的WIKI知识管理系统

项目简介 zyplayer-doc是一款适合团队和个人使用的WIKI文档管理工具&#xff0c;同时还包含数据库文档、Api接口文档。 体验地址&#xff1a;http://zyplayer.com 在线文档&#xff1a;http://doc.zyplayer.com 各模块介绍 zyplayer-doc-manage 文档管理后台 具有项目模块…

ue4技术方向学习路线如何安排?

作为初学者&#xff0c;您可能会感到UE4&#xff08;Unreal Engine 4&#xff09;的学习曲线很陡峭。但是&#xff0c;只要您有一个良好的学习路线和资源&#xff0c;就可以轻松地掌握UE4技术方向。下面是一个可以帮助您快速入门UE4技术方向的学习路线安排。 第一阶段&#xff…

【资料分享】TVS二极管

瞬态电压抑制二极管也称为TVS二极管&#xff0c;是一种保护用的电子零件&#xff0c;可以保护电器设备不受导线引入的电压尖峰破坏。 TVS二极管会和要保护的电路并联。 当其电压超过突崩溃准位时&#xff0c;直接分流过多的电流。TVS二极管是箝位器&#xff0c;会抑制超过其崩…