好用的依赖注入框架-Hilt

// MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var person: Person

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
person.say()
}
}

MainActivity中并没有直接给person赋值,而是使用了@Inject进行注解,然后就直接使用了,这是因为Hilt在背后给这个字段去初始化赋值了。为了能够让Hilt去生成一个Person对象进行注入,我们还要给Person的构造方法上也加上@Inject,这样就完成了一个最简单的依赖注入了。

@AndroidEntryPoint注入的入口点

还有一点就是在MainActivity上还使用了@AndroidEntryPoint,这个注解表示当前的Activity是一个注入的入口点,可以进行注入。Hilt并不是在哪都能进行注入的,而是有着特定的入口点,并且入口点必须得通过@AndroidEntryPoint注释。其中入口点有6个,Application,Activity,Fragment,View,Service,BroadcastReceiver。但是Application这个入口点不用使用@AndroidEntryPoint注解,因为它已经有了@HiltAndroidApp,所以可以直接注入。

// App.kt
@HiltAndroidApp
class App:Application() {
@Inject
lateinit var person: Person

override fun onCreate() {
super.onCreate()
person.say()
}
}

这几个入口点中,ViewFragment有些特殊,其他的入口点只要注解为@AndroidEntryPoint后,即可在其中进行Hilt的注入。但是对于Fragment而言,若是想在Fragment中使用Hilt的注入,除了在Fragment上使用@AndroidEntryPoint外,还要在其宿主Activity上也加上这个注解才行。而对于View而言,若是在View中使用Hilt的注入,首先在View上使用@AndroidEntryPoint,然后若是View用在Activity上,则Activity上也要加该注解。若是View用在Fragment上,则Fragment和Fragment的宿主Activity都要加上这个注解

可以看到Hilt的使用是比较简单的,首先将类的构造方法使用@Inject注解,这表明该类可以被Hilt自动创建并注入到相应的地方,然后就是在入口点中使用@Inject进行注入即可。

@HiltViewModel 注入ViewModel

ViewModel的注入和普通对象一样,首先给构造方法加@Inject,但是比普通对象多出来的是还要在它上面加入@HiltViewModel。并且注入的地方不能使用@Inject,而是和普通的使用ViewModel保持一致,使用ViewModelProvider去获取ViewModel。

注意这里不能使用@Inject去注入ViewModel,否则获取到的ViewModel只是一个普通对象,它在Activity销毁的时候也会被回收,而无法做到如ViewModel那样的在配置改变的时候依旧保存下来。

// MainAcitivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

private lateinit var viewModel: ViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
println(viewModel)
}
}

// MyViewModel.kt
@HiltViewModel
class MyViewModel @Inject constructor() : ViewModel()

如是嫌弃ViewModelProvider方式获取的太麻烦,则可以使用Activity-ktx的获取方式:

// build.gradle中加入依赖
implementation ‘androidx.activity:activity-ktx:1.2.3’

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

private val viewModel:MyViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

// 若是想要在Fragment中也这样使用,需要加入Fragment-ktx的依赖:
implementation ‘androidx.fragment:fragment-ktx:1.3.4’

对于无法在构造方法上加@Inject的类如系统类三方库中的类等,是不能直接进行注入的,要通过安装模块的方式去添加依赖。

@Module @InstallIn 声明一个模块

模块也就是Module,是一个类文件,它包含了很多的方法,这些方法就是用来提供注入对象的。

模块必须使用@Module来进行注解,说明当前类是一个Hilt模块,可以用来提供依赖。并且同时还要使用@InstallIn注解,该注解接收一个数组类型的参数,表示安装在哪个组件上。

组件代表着一个作用范围,安装在该组件上的模块所提供的依赖方法,只能在当前组件范围内才能进行注入。而且不同的组件对应着不同的生命周期,安装在它上面的模块只会在其生命周期内存在。

// NetModule.kt
@InstallIn(SingletonComponent::class)
@Module
object NetModule {

@Provides
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(“https://xxxx.com/”)
.build()
}
}

如上例,就是声明了一个NetModule模块,用来提供Retrofit的依赖,并且安装在SingletonComponent组件上,SingletonComponent组件的作用范围是全局,因此在所有的地方都能使用该模块所提供依赖注入,也就是对Retrofit的注入。

在这个模块中,有个@Provide标注的方法,该注解表明这个方法是是用来提供依赖的。注意它的返回值是Retrofit,表明需要注入Retrofit实例的时候,就会通过这个方法去生成一个实例对象进行注入。在Module中,Module的类名,方法名都是随意定的,Hilt只关心返回值。下面就是可以直接在Activity中注入Retrofit了:

// MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var retrofit: Retrofit

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(retrofit.hashCode())
}
}

组件Component

当声明一个module的时候,必须要安装在组件上,这代表当前module可以给哪些类进行注入。在Hilt中,一共有种组件,下面将逐一介绍组件的生命周期和作用范围。

SingletonComponent

SingletonComponent是针对Application的组件,安装在它上面的module会与Application的生命周期保持一致,在Application#onCreate的时候创建,在Application#onDestroy的时候销毁。并且该module所提供的依赖,在整个程序中都是可以使用的。

下面声明了两个模块,都是安装在SingletonComponent上的,但是一个是普通类,一个是单例类:

@Module
@InstallIn(SingletonComponent::class)
class NormalModule {…}

@Module
@InstallIn(SingletonComponent::class)
object SingletonModule {…}

这两种类型的模块有什么区别呢?使用class关键字的类是一个普通对象,因此会存在创建和销毁。它存在的范围也就是前面所说的组件的生命周期。例如在SingletonSomponent组件上,会在Application#onCreate的时候去new出一个NormalModule的实例对象,在Application#onDestroy的时候回收这个对象。

注意他是每个组件都会生成一个Module对象实例,例如若是这个module安装在ActivityComponent上的时候,会在每个Activity#onCreate的时候去创建一个module实例,也就是说,每个Activity对应的module都是独立的对象

而使用object关键字的话,声明出来的module是一个单例对象,因此不会存在创建销毁过程。提供依赖的时候,用的都是同一个单例对象。

ActivityRetainedComponent

ActivityRetainedComponent是针对Activity的组件,因此它的生命周期是Activity#onCreateActivity#onDestroy,但是它又比这个范围长一些。也就是当配置更改的时候,如旋转屏幕导致的Activity重建的时候,该组件并不会销毁,而是真正结束一个Activity的时候才会去销毁。简单来说就是生命周期与ViewModel是一致的。

安装在ActivityRetainedComponent组件上的module提供的依赖,可以在ViewModel中,Activity中,Fragment中,以及View中注入。

ActivityComponent

ActivityComponent组件的生命周期也是与Activity一致,但是,它是跟Activity完全一致的。只要Activity销毁,对应的组件也会销毁。

安装在ActivityComponent上的module提供的依赖,可以在Activity中,Fragment中,以及View中注入

ViewModelComponent

ViewModelComponent组件和ActivityRetainedComponent是一样的,声明周期也是与ViewModel一致。唯一的区别是,安装在它上面的模块提供的依赖只能在ViewModel中使用

FragmentComponent

FragmentComponent组件是针对于Fragment的,安装在它上面的组件在Fragment#onAttach的时候创建,在Fragment#onDestroy的时候销毁。

安装在它上面的module提供的依赖,只能在Fragment中注用

ViewComponent

ViewComponent组件是针对于View的,在View创建的时候创建,在视图销毁的时候销毁。并且安装在它上面的module提供的依赖只能在View中使用

ViewWithFragmentComponent

ViewWithFragmentComponent也是针对View的,但是注入的时候不仅要求在View上加入@AndroidEntryPoint,还要加上@WithFragmentBindings。安装在它上面的模块的生命周期也是与ViewComponent一样的。其中提供的依赖只能用在View上而且这个View还只能用在Fragment中,不能用在Activity中

ServiceComponent

ServiceComponent组件是针对Service的,依附于它的module在Service#onCreate的时候创建,在Service#onDestroy的时候销毁。并且安装在它上面的module只能用在Service中

七种组件的module生命周期以及使用范围,仅适用class关键字的module
组件module创建module销毁可使用的入口点
SingletonComponentApplication#onCreateApplication#onDestroy全部
ActivityRetainedComponentActivity#onCreateActivity#onDestroyViewModel,Activity,Fragment,View
ActivityComponentActivity#onCreateActivity#onDestroyActivity,Fragment,View
ViewModelComponentActivity#onCreateActivity#onDestroyViewModel
FragmentComponentFragment#onAttachFragment#onDestroyFragment
ViewComponentView创建View销毁View
ViewWithFragmentComponentView创建View销毁View
ServiceComponentService#onCreateService#onDestroyService

作用范围很好理解,是用来缩小注入的范围,以避免滥用注入。那么生命周期又有什么用呢?比如在MainActivity中有三个Fragment,这三个Fragment想要共享一个对象Person,那么该怎么实现呢?

第一种方法是定义在Activity中,然后通过Fragment拿到Activity,进而拿到这个Person对象。第二个方法是将Person对象放到Activity的ViewModel中,然后在Fragment中也去获取这个ViewModel,进而拿到Person对象。

最后一种方式就是利用Hilt生命周期的特性:

@Module
@InstallIn(ActivityComponent::class)
class ShareModule {
private val person = Person()

@Provides
fun providePerson(): Person {
return this.person
}

}

首先我们知道,ActivityComponent上的module的在Activity#onCreate的时候创建,在Activity#onDestroy的时候销毁。因此它是与Activity对应的,而这个module提供的依赖是ShareModule中的内部对象。因此,只要Activity没有销毁,这个module也就是同一个对象,进而注入的依赖person也都是同一个对象,从而实现Fragment共享同一个对象。这时候只要在Fragment中这样使用就行了:

@AndroidEntryPoint
class MyFragment : Fragment(){
@Inject
lateinit var person: Person

}

作用域

除了使用上述的方式可以实现在Activity的生命周期内共享某个依赖对象外,Hilt还提供了一个作用域的概念。在某个作用域内,提供的依赖对象也是唯一的,将上例中的ShareModule改造一下:

@Module
@InstallIn(ActivityComponent::class)
object ShareModule {

@ActivityScoped
@Provides
fun providePerson(): Person {
return Person()
}

}

改造后的module也能实现和前面那样的效果,在Activity的生命周期内提供相同的Person对象。而只是简单的加了一个@ActivityScoped注解,这样,在Activity的生命周期范围内,拿到的依赖对象仍然是同一个,即使将module类使用object关键字声明成了单例类。

可以看到,使用作用域注解可以实现基于组件生命周期内提供单一对象的功能,这样的话,就可以直接将module定义为单例类就行了,若是java中的话定义成静态方法即可,这样可以用来避免频繁创建对象导致的开销。

另外注意一点就是,作用域注解必须与组件注解保持一致,比如在ActivityComponent只能使用ActivityScoped作用域,作用域注解的提供依赖的方法,在组件的生命周期内提供的是同一个依赖对象。

作用域注解不只是在module中使用,直接在类上面加上也是可以的,使用下面的代码也可以实现上述的效果:

@ActivityScoped
class Person @Inject constructor() {…}

对应Component的作用域
组件生命周期生命周期作用域
SingletonComponentApplication#onCreateApplication#onDestroySingleton
ActivityRetainedComponentActivity#onCreateActivity#onDestroyActivityRetainedScope
ActivityComponentActivity#onCreateActivity#onDestroyActivityScoped
ViewModelComponentActivity#onCreateActivity#onDestroyViewModelScoped
FragmentComponentFragment#onAttachFragment#onDestroyFragmentScoped
ViewComponentView创建View销毁ViewScoped
ViewWithFragmentComponentView创建View销毁ViewScoped
ServiceComponentService#onCreateService#onDestroyServiceScoped

在定义模块的时候,使用@InstallIn可以限定当前模块安装在哪个组件上。其中@InstallIn的参数是个数组,也就是说,我们可以将这个模块安装在多个组件上。使用多个组件可以将作用范围进行扩大,比如使用FragmentComponentServiceComponent,就可以使模块中的依赖在FragmentService中使用了。但是,使用了多组件的话,因为组件的生命周期和作用范围不同,因此是不能声明作用域注解的

当然若是两个组件的生命周期是一样的,比如ViewComponentViewWithFragmentComponent,则还是可以使用作用域注解@ViewScoped的,但是这没有什么意义,因为ViewComonent的范围是包含了Fragment的。

@Provide和@Binds

@Provide前面有说过了,是在Module中用来修饰方法的,被它修饰的方法代表着提供依赖的方法,当需要该类型的依赖对象时,就会调用对应返回值的方法去注入依赖。

在Module中,类名和方法名都是没有意义的,可以随便起名(当然为了可读性还是不要随便起名),至于提供什么依赖完全看函数的返回值类型。若是返回值类型是一个接口呢?。

举个例子,比如有个接口Human,两个子类Man和Woman

interface Human {
fun sex(): String
}

class Man @Inject constructor() : Human {
override fun sex(): String {
return “男”
}
}

class Woman @Inject constructor() : Human {
override fun sex(): String {
return “女”
}
}

若是想要在Activity中注入该怎么处理呢,简单,构造方法已经加入注解了,然后直接注入即可:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

@Inject lateinit var man: Man

@Inject lateinit var woman: Woman

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(“Man:${man.sex()}, Woman: ${woman.sex()}”)
}
}

那我要是想要在Activity中注入Human而不是具体的子类型,该怎么办呢?首先我们知道,Hilt注入的时候是根据类型来查找依赖关系的。顺序是先从当前组件上的Module上查找返回值类型为这个类型的方法,找不到后再去看这个类型的类上的构造方法上是否有@Inject注解,有的话就直接生成一个对象注入了。

而若是注入类型为Human的,因为Human是个接口,是没有构造方法的,因此也是没法去@Inejct的。因此,若是想注入一个接口类型,必须要为它提供一个module。

@Module
@InstallIn(ActivityComponent::class)
object HumanModule {

@Provides
fun provideMan():Human {
return Man()
}
}

ActivityComponent上安装这个Module后,就可以在Activity中使用Human注入了。另外这里注意一点,Module一般是用来提供不可以直接注入的对象,也就是三方库系统类那样的无法在构造方法添加@Inject的类,对于我们自己的类,如上面的Man对象,则不要在provideMan方法中直接去new一个对象,而是应该使用注入的方式,如下:

@Module
@InstallIn(ActivityComponent::class)
object HumanModule {

@Provides
fun provideMan(man:Man):Human {
// 方法参数中的参数,会被Hilt直接注入
// 所以若是提供依赖的方法含有参数的话,参数必须是能够被注入的,否则会报错
return man
}
}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

@Inject lateinit var man: Human

@Inject lateinit var woman: Human

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(“Man:${man.sex()}, Woman: ${woman.sex()}”)
}
}

这样,在Activity中虽然可以注入Human了,但是注入的两个对象man和woman实际类型都是Man。若是想要它们不一样该怎么处理呢?

@Module
@InstallIn(ActivityComponent::class)
object HumanModule {

@Provides
fun provideMan(man:Man):Human {
return man
}

@Provides
fun provideWoman(woman: Woman):Human {
return woman
}
}

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

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

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

img

img

img

img

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

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

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

总结

【Android 详细知识点思维脑图(技能树)】

image

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

现在高级工程师还是比较缺少的**,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-rcPvQHh2-1711952836831)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值