Jetpack学习之 Hilt

id ‘dagger.hilt.android.plugin’

}

dependencies {

implementation ‘com.google.dagger:hilt-android:2.28-alpha’

kapt ‘com.google.dagger:hilt-android-compiler:2.28-alpha’

}

在 project的 build.gradle 中加入:

classpath ‘com.google.dagger:hilt-android-gradle-plugin:2.28-alpha’

2.2 一个简单的例子


Hilt 需要 AndroidManifest 使用带有 @HiltAndroidApp 注解的 Application 类,所以我们的 Application需要这样:

@HiltAndroidApp

class HiltApp : Application() {

}

然后在 AndroidManifest 文件中声明:

<application

android:name=“.HiltApp”

假如我们要在 MainActivity 中注入一个 User 对象, 我们首先编写一个 User 类,User类有两个属性 nameage

诶,这个时候有同学就会问了:我通过 @Inject 声明一个User,Hilt就能给我创建一个 User 对象,那这个User对象里面的name和age是啥?答案是:编译会报错,因为我们自己都不知道这两个参数是啥,Hilt怎么可能会知道,所以这两个属性也要通过依赖注入的方式来注入。

为了简化这个问题,我定义一个默认的无参构造函数,反正创建之后,里面的值也是可以修改的嘛。

data class User(var name: String, var age: Int) {

// 定义一个默认的无参构造函数,并使用 @Inject 注解修饰

@Inject

constructor() : this(“Rikka”, 23)

}

接着我们在 MainActivity 中声明一个 User,通过 @Inject 来修饰,并且MainActivity 需要通过 @AndroidEntryPoint 修饰:

// 1

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {

// 2

@Inject

lateinit var user: User

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

Log.d(TAG, “user name: u s e r . n a m e a g e : {user.name} age: user.nameage:{user.age}”)

}

}

Logcat 打印结果如下:

在这里插入图片描述

代码解析:

注释1: 为 MainActivity 修饰 @AndroidEntryPoint,该注解表明 该类为需要进行依赖注入的 Android类,是Dagger针对Android场景化的地方。当我们类中需要进行依赖注入,我们为该类加入这个注解,它会帮助创建一个单独的 Hilt组件。它不能修饰Abstract,它只能修饰:

  • ComponentActivity

  • (Support)Fragment

  • View

  • Service

  • BroadcastReceiver

注释2:我们需要注入一个 User,所以我们给它加一个 @Inject 注解,告诉 Hilt。因为Kotlin的语法问题,这里不得不声明 lateinit(而这里Koin的写法更加优雅),接下来步骤大概是这样的:

  1. Hilt 会去找 User 这个类的构造函数,以此来创建一个对象

  2. Hilt 发现 有两个个构造函数,而无参构造函数被 @Inject 声明

  3. Hilt 会去调用被 @Inject 的构造函数,创建一个 User("Rikka", 23) 对象

  4. 返回这个对象, MainActivity 实现外部帮忙创建 User对象,实现 User 的依赖注入。

Inject 的中文翻译是 “注入、注射”,所以可以形象的认为, @Inject 修饰的变量是被外界通过针筒注入进来的。

@Inject 可以修饰

  • 构造函数 Constructors

  • 变量 Fields

  • 方法 Methods

构造函数是最先被注解的,然后再是变量和方法。所以它修饰构造函数和修饰变量,其实是不同的作用。但为了便于理解,我们可以把它看成是一个插眼工具,便于Hilt去寻找要注入的地方。

我们上面的 User 类是无参构造函数,这次假设我们要有参数的呢?其实就是参数也要注入嘛,这就是套娃来的,来看看我们给User新增一个 属性:Clothes

class Clothes @Inject constructor() {

}

class User @Inject constructor(var clothes: Clothes){

}

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {

Log.d(TAG, “user clothes:${user.clothes}”)

}

打印结果:

在这里插入图片描述

PS:大家不要太拘泥于有参构造函数的创建,我认为注入的作用是创建出一个对象,这个对象里面的内容可以后续再传入,它更多的体现、或者我们需要注意的是 “分离关注点”

2.3 实现接口实例注入


因为接口没有构造函数,所以当我们想要依赖一些接口时,该怎么办。

我们来下面的示例,我们写一个 Profession 接口,代表职业:

interface Profession {

fun doJob()

}

假设我们有两个实现接口的类:医生类和程序猿类:

class Doctor : Profession{

override fun doJob() {

Log.d(“Doctor”, “doctor do job”)

}

}

class Programmer : Profession{

override fun doJob() {

Log.d(“Programmer”, “programmer do job”)

}

}

这个时候我给 User 类添加一个职业的属性,并希望它能够自动注入:

class User @Inject constructor(var clothes: Clothes){

@Inject

lateinit var profession: Profession

}

因为 Profession 是一个接口,它有两个实现类,所以这样 Hilt 并不能知道我们要实例化哪个具体的实现类,所以编译的时候就会报错。

而 Hilt 也解决这种问题,首先我们要在每个实现类上注入构造函数:

class Doctor @Inject constructor() : Profession{

}

class Programmer @Inject constructor() : Profession{

}

接着我们需要实现一个和该接口有关的 XXXModule类,它被 @Module 修饰,这个和 Dagger 中的一样,代表它会为接口提供一个创建实例的工厂,同时需要加上 @InstallIn 注解,用来声明它是被安装到哪个组件中,该注解后面会说到。 代码如下:

@Module

@InstallIn(ActivityComponent::class)

abstract class ProfessionModule { // 1

// 2

@Binds

abstract fun bindDoctor(doctor: Doctor): Profession

}

注释1: 我们写出来的类是一个抽象类,因为我们不需要具体的实现它,而且它没有具体的命名规则,因为我们也不会在代码中直接调用它,但是为了便于理解,我们起名一般叫 接口名 + Module

注释2: 我们假设该Module提供一个 Doctor 的职业,那我们需要定义一个 抽象方法 来获取一个Doctor类。 并且 该方法需要被 @Binds 注解修饰。这样就能被 Hilt 识别。

这样一来,我们就实现了接口的一个实例化的注入,我们来实验一下,在 User 中去展示它:

class User @Inject constructor(var clothes: Clothes){

@Inject

lateinit var profession: Profession

fun showMyself() {

profession.doJob()

}

}

// MainActivity

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {

@Inject

lateinit var user: User

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

user.showMyself()

}

}

打印结果为:

在这里插入图片描述

可以看到我们的 Doctor 成功的注入了。

OK,我们了解了接口某一个实现类的注入 (Doctor),那假设这个时候,另外一个类需要注入接口的另一个实现类 Programmer,那我们是不是也得按照同样的做法,在 Module类中添加呢?

@Module

@InstallIn(ActivityComponent::class)

abstract class ProfessionModule {

@Binds

abstract fun bindDoctor(doctor: Doctor): Profession

@Binds

abstract fun bindProgrammer(programmer: Programmer): Profession

}

这个时候发现运行,编译也会报错:

在这里插入图片描述

提示我们被绑定多次了。

这是因为 DoctorProgrammer 都是相同类型,当他们一起被 Binds 注解,那 Hilt 不知道要去绑定哪一个。

这个时候就需要使用 @Qualifier 注解来帮助我们了,它就是为了这种 相同类型 依赖注入而产生的:

@Qualifier

@Retention(AnnotationRetention.BINARY)

annotation class BindDoctor

@Qualifier

@Retention(AnnotationRetention.BINARY)

annotation class BindProgrammer

我们创建了新的注解 BindDoctorBindProgrammer,他们都被 @Qualifier 修饰,表示他们用来作用在同种类型上, @Retention 选择使用 BINARY类型,表明该注解保留到编译后,但无法通过反射来得到,是比较适合的注解。

接下来,我们要将这些注解作用在被 Binds 注解的抽象方法上:

@Module

@InstallIn(ActivityComponent::class)

abstract class ProfessionModule {

@BindDoctor

@Binds

abstract fun bindDoctor(doctor: Doctor): Profession

@BindProgrammer

@Binds

abstract fun bindProgrammer(programmer: Programmer): Profession

}

最后,在 User 中声明使用哪一个类型的注入:

class User @Inject constructor(var clothes: Clothes){

@BindProgrammer // 这次注入一个 Programmer

@Inject

lateinit var profession: Profession

fun showMyself() {

profession.doJob()

}

}

打印结果如下所示:

在这里插入图片描述

这下我们就实现了具体某个实例的注入啦。

2.4 实现第三方依赖注入


假设一些类不是由我们自己写的,而是由第三方库导入的。比如 OkHttp ,我们在使用网络请求的时候,需要使用它,为了分离关注点,我们需要对他进行依赖注入。

但是 OkHttp 是我们不能修改的类,所以我们不能在它的构造函数上加入 @Inject, 这个时候该怎么办呢?

Dagger 中也有类似的场景,我们需要 @Providers 来帮助我们。除此之外,我们也需要 @Module 注解来声明一个 Module 类, 基于上面的例子,我们可以把这种 Module 类看成是一个工厂:

@Module

@InstallIn(ApplicationComponent::class)

class NetModule { // 1

// 2

@Provides

fun provideOkHttpClient(): OkHttpClient {

// 3

return OkHttpClient.Builder().build()

}

}

注释1: 声明一个 NetModule,提供网络库相关的组件,并没有和上面例子一样声明为抽象函数,这是因为里面的都有具体的实现方法。

注释2: 编写一个 provideOkHttpClient() 方法,返回一个 OkHttpClient对象。 声明一个 @Providers 注解,表示这个提供的依赖对象,是第三方的类或者系统类,我们因为不能直接更改其构造函数,所以得加上这个注解。

注释3:new 一个对象,并返回。

这样,我们就能在我们代码中直接使用了:

@Inject

lateinit var okHttpClient: OkHttpClient

@Providers 的本质是什么?

第三方类因为其只读性,Hilt不能找到其构造函数,所以需要我们自己手动的创建,创建的方法被 @Providers 修饰, Hilt 找到这个方法,并提供由我们手动创建的对象。

所以 @Providers 的本质,是由我们自己创建对象, Hilt 帮我们注入。

现在大家都不会直接使用 OkHttp,而是使用 Retrofit,所以我们来提供一个 Retrofit 把:

@Provides

fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {

return Retrofit.Builder()

.addConverterFactory(GsonConverterFactory.create())

.client(okHttpClient)

.build()

}

因为 Retrofit的创建需要依赖一个 OkHttpClient 对象,所以我们需要创建一个,但是我们也可以注入一个,因为我们之前已经有 provideOkHttpClient,所以它就能提供一个实例,我们不用在担心什么了。

2.5 Hilt 的内置组件和作用域


2.5.1 @InstallIn 注解

我们之前看到了 @InstallIn 这个注解,它的作用是用来表明 Module 作用的地方,它的参数时 xxxComponent格式,前面xx代表作用域。

因为 Hilt 是Dagger的Android场景化,所以它能作用的地方和我们Android息息相关,有下面几处:

  1. Application -> ApplicationComponent

  2. ViewModel -> ActivityRetainedComponent

  3. Activity -> ActivityComponent

  4. Fragment -> FragmentComponent

  5. View -> ViewComponent

  6. Service -> ServiceComponent

  7. View Annotation with @WithFragmentBindings -> ViewWithFragemntComponent

除了最后一个,别的作用域还是挺常见的。他们都要通过 @InstallIn 注入。

比如我们ProfessionModule是声明成 @InstallIn(ActivityComponent::class),这就说明只有在 Activity 的代码中可以使用它,而在 Service 中是不能使用的。

NetModule 则是声明成 InstallIn(ApplicationComponent::class)的,这说明在全局都可以使用它提供的 OkHttpClient 和 Retrofit 对象。

2.5.2 使注入对象单例

像 Retrofit 、 OkHttpClient 这样的全局都需要使用到的对象,我们希望它的作用域是全局,并且单例的。

但是 Hilt 提供的 @Inject 对象并不是单例的,每次 注入时都会重新生成一个新的实例,这就说明,假设我们要使用 Retrofit 来做网络请求, @Providers 每次提供的都是不一样的,这样对性能来说很不友好,而且不符合常规的逻辑设定。

按照以往,我们可以通过给 NetModule 声明一个 @Singleton 注解,来让这个类实现单例,来解决这个问题。

Hilt 也有自己的解决方案,那就是使用 @xxxScope 注解,它和上面的 xxxComponent所对应,表示 在这个作用域内单例,来看看对应关系:

  1. Application -> ApplicationComponent -> @Singleton

  2. ViewModel -> ActivityRetainedComponent -> @ActivityRetainedScoped

  3. Activity -> ActivityComponent -> @ActivityScoped

  4. Fragment -> FragmentComponent -> @FragmentScoped

  5. View -> ViewComponent -> @ViewScoped

  6. Service -> ServiceComponent -> @ServiceScoped

  7. View Annotation with @WithFragmentBindings -> ViewWithFragemntComponent -> @ViewScoped

使用 @xxScoped 来替代 @InstallIn(xxxComponent::class) 声明组件为单例。

因为 Application 是作用于全局,所以它的注解是 @Singleton,比较好理解。

2.5.3 作用域的包含关系

作用域也有自己的包含关系,比如被 @ActivityScoped声明的组件,可以在 Fragment 或者 View 中使用,他们的具体包含关系如下图所示:

在这里插入图片描述

2.6 Hilt 预置的 Qualifier


我介绍过 Hilt 是 Dagger针对Android的场景化,所以它低层做了很多事情,使得在Android上更好的使用。除了上面介绍过的那些注解外,还有很多别的东西,可以让我们去探索,同时也了解了Dagger本身。

Context 上下文是Android 独特的存在,它代表着 Application、Activity、Service的一些fwk层的东西。

而我们的代码中经常会需要 Context 来创建一些东西:

class A @Inject constructor(context: Context) {

}

但是我们知道,它是系统类,我们无法注入 Context。那我们可以通过使用 @Providers 来创建吗?

@Providers

fun provideContext() {

???

}

很明显,Context 是由AMS来创建的,我们无法直接创建一个上下文出来。这个问题该如何解决呢?

答案是:我们不用解决,Hilt 为我们提供了它自己预置的注解 @ApplicationContext@ActivityContext,我们直接使用,Hilt会帮我们注入上下文。

class A @Inject constructor(@ApplicaitonContext context: Context)

而现在没有 ServiceContext,可能是用的比较少吧?

@ApplicationContext 提供的类型是 Application, 而不是我们自己的 App 自定义的 Application,加入我们要使用自己的该怎么办呢?答案是也很简单:

@Providers

fun provideApplicaiton(application: Application): MyApplication {

return applicaiton as MyApplication

}

直接在 Module 中提供一个,并强转就 OK啦。

注意

ApplicationContext 的作用域是全局, 所以它修饰的类的作用只能是 @InstallIn(Applicaiton)@Singleton,其他的也同理。

3 小结
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后看一下学习需要的所有知识点的思维导图。在刚刚那份学习笔记里包含了下面知识点所有内容!文章里已经展示了部分!如果你正愁这块不知道如何学习或者想提升学习这块知识的学习效率,那么这份学习笔记绝对是你的秘密武器!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

年进入阿里一直到现在。**

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-gmNiXI7Y-1712658510433)]

[外链图片转存中…(img-nXXHzbbT-1712658510434)]

[外链图片转存中…(img-POdZF7yF-1712658510434)]

[外链图片转存中…(img-rNQAx95v-1712658510434)]

[外链图片转存中…(img-jSXVb0Oo-1712658510435)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后看一下学习需要的所有知识点的思维导图。在刚刚那份学习笔记里包含了下面知识点所有内容!文章里已经展示了部分!如果你正愁这块不知道如何学习或者想提升学习这块知识的学习效率,那么这份学习笔记绝对是你的秘密武器!

[外链图片转存中…(img-vvmvdYDc-1712658510435)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值