Jetpack学习之 Hilt,android音频播放器

本文详细介绍了Hilt在Android中的应用,包括添加依赖、使用@HiltAndroidApp和@AndroidEntryPoint注解,以及如何进行接口实例注入、第三方库注入和作用域管理。重点讲解了@InstallIn、@Inject、@Qualifier和@Singleton注解的作用和使用场景。
摘要由CSDN通过智能技术生成

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)

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

七大模块学习资料:如NDK模块开发、Android框架体系架构…

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。

由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。

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

1nk8in7-1712589527840)]

[外链图片转存中…(img-ffEJiRxl-1712589527840)]

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

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

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

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

[外链图片转存中…(img-O7Gn8ewd-1712589527840)]

七大模块学习资料:如NDK模块开发、Android框架体系架构…

[外链图片转存中…(img-X6OT7WZj-1712589527841)]

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。

由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值