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’
Hilt 需要 AndroidManifest
使用带有 @HiltAndroidApp
注解的 Application 类,所以我们的 Application需要这样:
@HiltAndroidApp
class HiltApp : Application() {
…
}
然后在 AndroidManifest 文件中声明:
<application
android:name=“.HiltApp”
…
假如我们要在 MainActivity
中注入一个 User
对象, 我们首先编写一个 User 类,User类有两个属性 name
和 age
。
诶,这个时候有同学就会问了:我通过 @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的写法更加优雅),接下来步骤大概是这样的:
-
Hilt 会去找
User
这个类的构造函数,以此来创建一个对象 -
Hilt 发现 有两个个构造函数,而无参构造函数被
@Inject
声明 -
Hilt 会去调用被
@Inject
的构造函数,创建一个User("Rikka", 23)
对象 -
返回这个对象, 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:大家不要太拘泥于有参构造函数的创建,我认为注入的作用是创建出一个对象,这个对象里面的内容可以后续再传入,它更多的体现、或者我们需要注意的是 “分离关注点”
因为接口没有构造函数,所以当我们想要依赖一些接口时,该怎么办。
我们来下面的示例,我们写一个 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
}
这个时候发现运行,编译也会报错:
提示我们被绑定多次了。
这是因为 Doctor
和 Programmer
都是相同类型,当他们一起被 Binds 注解,那 Hilt 不知道要去绑定哪一个。
这个时候就需要使用 @Qualifier
注解来帮助我们了,它就是为了这种 相同类型 依赖注入而产生的:
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindDoctor
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindProgrammer
我们创建了新的注解 BindDoctor
和 BindProgrammer
,他们都被 @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()
}
}
打印结果如下所示:
这下我们就实现了具体某个实例的注入啦。
假设一些类不是由我们自己写的,而是由第三方库导入的。比如 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.1 @InstallIn 注解
我们之前看到了 @InstallIn
这个注解,它的作用是用来表明 Module 作用的地方,它的参数时 xxxComponent
格式,前面xx代表作用域。
因为 Hilt 是Dagger的Android场景化,所以它能作用的地方和我们Android息息相关,有下面几处:
-
Application ->
ApplicationComponent
-
ViewModel ->
ActivityRetainedComponent
-
Activity ->
ActivityComponent
-
Fragment ->
FragmentComponent
-
View ->
ViewComponent
-
Service ->
ServiceComponent
-
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
所对应,表示 在这个作用域内单例,来看看对应关系:
-
Application ->
ApplicationComponent
->@Singleton
-
ViewModel ->
ActivityRetainedComponent
->@ActivityRetainedScoped
-
Activity ->
ActivityComponent
->@ActivityScoped
-
Fragment ->
FragmentComponent
->@FragmentScoped
-
View ->
ViewComponent
->@ViewScoped
-
Service ->
ServiceComponent
->@ServiceScoped
-
View Annotation with
@WithFragmentBindings
->ViewWithFragemntComponent
->@ViewScoped
使用 @xxScoped
来替代 @InstallIn(xxxComponent::class)
声明组件为单例。
因为 Application 是作用于全局,所以它的注解是 @Singleton
,比较好理解。
2.5.3 作用域的包含关系
作用域也有自己的包含关系,比如被 @ActivityScoped
声明的组件,可以在 Fragment 或者 View 中使用,他们的具体包含关系如下图所示:
我介绍过 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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合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)]
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!