type
status
slug
date
summary
tags
category
password
icon
Hilt是什么,有什么用?
个人认为学习Hilt应该先学习Dagger2,毕竟hilt就是对dagger2的进一步封装 Hilt 是 Android 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。 Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,提供了一种在应用中使用 DI(依赖项注入)的标准方法。Hilt 在热门 DI 库 Dagger 的基础上构建而成,因而能够受益于 Dagger 的编译时正确性、运行时性能、可伸缩性和 Android Studio 支持。
Hilt的官方文档 可以查看这个链接
结合具体实例 本文旨在通过一个具体的实例来阐述Hilt的作用和用法, 根据官方教程的流程具体制造一个实例并进行更详细的分析。
引入Hilt
用android studio创建一个安卓基于Kotlin的工程 HiltBasic 修改build.gradle(project)
接下来修改该build.gradle(app)
Hilt 应用类
所有使用 Hilt 的应用都必须包含一个带有 @HiltAndroidApp 注释的 Application 类。 @HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器。 那么在我们的实例中创建这样一个Application类
生成的这一 Hilt 组件会附加到 Application 对象的生命周期,并为其提供依赖项。此外,它也是应用的父组件,这意味着,其他组件可以访问它提供的依赖项。我们查看生成的源码可以看到一个Hilt_MyApplication类,这是此注解生成类之一,它是hilt组建全局的管理者。
将依赖项注入 Android 类
简单使用
在 Application 类中设置了 Hilt 且有了应用级组件后,Hilt 可以为带有 @AndroidEntryPoint 注释的其他 Android 类提供依赖项,现在我们先在我们熟悉的MainActivity添加这个注释,看看会发生什么:
编译后我们发现多出了一个生成类"Hilt_MainActivity", 现在通过这个注解使得我们的MainActivity成了“Hilt_MainActivity”的子类,如果您使用 @AndroidEntryPoint 为某个 Android 类添加注释,则还必须为依赖于该类的 Android 类添加注释。例如,如果您为某个 Fragment 添加注释,则还必须为使用该 Fragment 的所有 Activity 添加注释。
现在我们使用它,在项目中添加一个文件di.kt, 并定义User
修改MainActivity,添加一个按钮,点击按钮后设置并打印User
运行后发现并没有崩溃,一切正常。
这就是依赖注入,这就是Hilt最基本的使用, 它帮我们创建了User,如果用Dagger2的话我们还得自己写Component,还得自己去创建Component,还得调用注入方法。那么Hilt把这些活都帮我们做了。了解Dagger2的话,您应该已经感觉到了,这玩意就是为了帮我们节省时间的。但由于省去了很多步骤,导致代码有时候感觉挺突兀的,比如上面的User。
当构造函数不能直接构造时
有时,类型不能通过构造函数注入。发生这种情况可能有多种原因。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不归您所有的类型,如来自外部库的类。在这些情况下,您可以使用 Hilt 模块向 Hilt 提供绑定信息。
Hilt 模块是一个带有
@Module 注释的类。与 Dagger 模块一样,它会告知 Hilt 如何提供某些类型的实例。与 Dagger 模块不同的是,您必须使用 @InstallIn 为 Hilt 模块添加注释,以告知 Hilt 每个模块将用在或安装在哪个 Android 类中。使用@Binds注入接口实例
为了说明@Binds注解,我们在我们的di.kt中添加一个接口两个类,来看代码
我们定义了一个接口Engine,还有ChinaEngine 和ChinaCar类, 我们再在MainActivity中定义一个ChinaCar实例
现在我们发现这个chinaCar的构造函数需要一个接口,所以不能通过构造函数注入,那么我们怎么告诉Hilt我们的chinaCar需要一个实际上为ChinaEngine的实例呢(注意ChinaEngine是可以用构造函数注入的),@Binds就是干这事儿的。
在我们的di.kt添加如下代码:(因为是例子,所有代码都写在了一个文件,实际上应该分开)
现在Hilt就知道怎么创建ChinaCar了,试试吧:
一切正常,log正常打印出来了。
使用@Provides
另一种不能用构造函数注入的情况:接口不是无法通过构造函数注入类型的唯一一种情况。如果某个类不归您所有(因为它来自外部库,如 Retrofit、
OkHttpClient 或 Room 数据库等类),或者必须使用构建器模式创建实例,也无法通过构造函数注入。接着前面的例子来讲。如果 Dog类需要一个构造参数,Hilt 如何提供此类型的实例? 方法是在 Hilt 模块内创建一个函数,并使用 @Provides 为该函数添加注释。 带有注释的函数会向 Hilt 提供以下信息:
- 函数返回类型会告知 Hilt 函数提供哪个类型的实例。
- 函数参数会告知 Hilt 相应类型的依赖项。
- 函数主体会告知 Hilt 如何提供相应类型的实例。每当需要提供该类型的实例时,Hilt 都会执行函数主体。 先来写Dog类:
添加新的Module用来提供dog的创建办法
现在可以定义一个dog来试试,一切okay
为同一类型提供多个绑定
现在有人说了 我有两只狗一个叫“京巴”一个叫“泰迪”, 还有一辆用美国引擎的国产车, 你的需求很正常, 如果您需要让 Hilt 以依赖项的形式提供同一类型的不同实现,必须向 Hilt 提供多个绑定。您可以使用限定符为同一类型定义多个绑定。
限定符是一种注释,当为某个类型定义了多个绑定时,您可以使用它来标识该类型的特定绑定。
仍然接着前面的例子来讲。首先,定义要用于为
@Binds 或 @Provides 方法添加注释的限定符,下面直接上代码了,先来定义两个限定符:除了自定义限定符,还可以用Hilt(dagger2)已经定义好的@Named,这个后边会解释。 中国车也可以有美国引擎,我们先来添加一个美国引擎
通过限定符来区分中国引擎和美国引擎,并告诉Hilt
在主界面定义两台车,分别用不同的引擎
测试一下,一切正常
Hilt 中的预定义限定符
Hilt 提供了一些预定义的限定符。例如,由于您可能需要来自应用或 Activity 的 Context 类,因此 Hilt 提供了 @ApplicationContext 和 @ActivityContext 限定符。 假设本例中的 User类需要 Activity 的上下文。以下代码演示了如何向 User提供 Activity 上下文
在主界面调用这个showMsg, toast正常弹了出来。 这说明context参数Hilt帮我们获取并保持了,当然了从结构化设计以及生命周期等等方面来说,不应该在user类中有context这种东西,本例旨在说明知识点,请勿较真儿。
如需了解 Hilt 中提供的其他预定义绑定,请参阅组件默认绑定。
为 Android 类生成的组件
对于您可以从中执行字段注入的每个 Android 类,都有一个关联的 Hilt 组件,您可以在 @InstallIn 注释中引用该组件。每个 Hilt 组件负责将其绑定注入相应的 Android 类。 前面的示例演示了如何在 Hilt 模块中使用 ActivityComponent。
组件生命周期
Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的组件类的实例。
组件作用域
默认情况下,Hilt 中的所有绑定都未限定作用域。这意味着,每当应用请求绑定时,Hilt 都会创建所需类型的一个新实例。 在本例中,每当 定义一个User都将创建一个新实例。 不过,Hilt 也允许将绑定的作用域限定为特定组件。Hilt 只为绑定作用域限定到的组件的每个实例创建一次限定作用域的绑定,对该绑定的所有请求共享同一实例。 下表列出了生成的每个组件的作用域注释
在本例中,如果您使用 @ActivityScoped 将 User的作用域限定为 ActivityComponent,Hilt 会在相应 Activity 的整个生命周期内提供 User的同一实例:
为了测试在Activity内单例,我们在定义一个User
我们通过调试,打印等发现两个User确实是同一个了。
如果有兴趣,可以在创建一个新的Activity看看两个activity下的的User是否相同,以此来验证作用域。注意:将绑定的作用域限定为某个组件的成本可能很高,因为提供的对象在该组件被销毁之前一直保留在内存中。请在应用中尽量少用限定作用域的绑定。如果绑定的内部状态要求在某一作用域内使用同一实例,或者绑定的创建成本很高,那么将绑定的作用域限定为某个组件是一种恰当的做法。 其它的作用域也是同理,可以自己试验下。
值得一提的是作用域限定为 SingletonComponent(@Singleton),这相当于在整个App内单例,这个可能是我们很多单例时会使用的情况。
组件默认绑定 每个 Hilt 组件都附带一组默认绑定,Hilt 可以将其作为依赖项注入您自己的自定义绑定。请注意,这些绑定对应于常规 Activity 和 Fragment 类型,而不对应于任何特定子类。这是因为,Hilt 会使用单个 Activity 组件定义来注入所有 Activity。每个 Activity 都有此组件的不同实例。
在 Hilt 不支持的类中注入依赖项
您可能需要在 Hilt 不支持的类中执行字段注入。 在这些情况下,您可以使用 @EntryPoint 注释创建入口点。入口点是由 Hilt 管理的代码与并非由 Hilt 管理的代码之间的边界。它是代码首次进入 Hilt 所管理对象的图的位置。入口点允许 Hilt 使用它并不管理的代码提供依赖关系图中的依赖项。
@EntryPoint用于将接口标记为生成组件的入口点。这个注释必须与@InstallIn一起使用,以指示哪个组件应该拥有这个入口点。在装配组件时,Dagger将使所指示的组件扩展此标注的接口。 这东西就是假设您在安卓不支持的组件或者什么东西中,想注入字段什么的就可以使用它。
我们还是来结合实例说明一下,不然很难理解。为了说明情况,现在我们改造dog类 让它拥有一个注入的wokr字段,那怎么让Hilt提供这个work的注入(创建)呢?
此时我们运行程序,发现执行赋值“导盲”时崩溃了,因为Hilt不知道如何创造Dog下的work,没有任何组件告诉Hilt怎么做这件事。
我先用dagger2来完成这个事情,然后再说Hilt。这样也能有个对比,首先添加一个Component在di.kt
修改dog来调用注入work
好,现在再次运行程序,发现一切正常了。
那么同样的效果,用Hilt怎么实现呢?首先把原来的Dagger代码注释掉 然后改造Dog类
此时我们发现程序可以正常运行了。
注意原来提供狗狗的模块要更新下哦。
- 作者:白色风车
- 链接:https://f.appa.me/article/jetpack_hilt
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。