// 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()
}
}
这几个入口点中,View
和Fragment
有些特殊,其他的入口点只要注解为@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#onCreate
到Activity#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销毁 | 可使用的入口点 |
---|---|---|---|
SingletonComponent | Application#onCreate | Application#onDestroy | 全部 |
ActivityRetainedComponent | Activity#onCreate | Activity#onDestroy | ViewModel,Activity,Fragment,View |
ActivityComponent | Activity#onCreate | Activity#onDestroy | Activity,Fragment,View |
ViewModelComponent | Activity#onCreate | Activity#onDestroy | ViewModel |
FragmentComponent | Fragment#onAttach | Fragment#onDestroy | Fragment |
ViewComponent | View创建 | View销毁 | View |
ViewWithFragmentComponent | View创建 | View销毁 | View |
ServiceComponent | Service#onCreate | Service#onDestroy | Service |
作用范围很好理解,是用来缩小注入的范围,以避免滥用注入。那么生命周期又有什么用呢?比如在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的作用域
组件 | 生命周期 | 生命周期 | 作用域 |
---|---|---|---|
SingletonComponent | Application#onCreate | Application#onDestroy | Singleton |
ActivityRetainedComponent | Activity#onCreate | Activity#onDestroy | ActivityRetainedScope |
ActivityComponent | Activity#onCreate | Activity#onDestroy | ActivityScoped |
ViewModelComponent | Activity#onCreate | Activity#onDestroy | ViewModelScoped |
FragmentComponent | Fragment#onAttach | Fragment#onDestroy | FragmentScoped |
ViewComponent | View创建 | View销毁 | ViewScoped |
ViewWithFragmentComponent | View创建 | View销毁 | ViewScoped |
ServiceComponent | Service#onCreate | Service#onDestroy | ServiceScoped |
在定义模块的时候,使用@InstallIn
可以限定当前模块安装在哪个组件上。其中@InstallIn
的参数是个数组,也就是说,我们可以将这个模块安装在多个组件上。使用多个组件可以将作用范围进行扩大,比如使用FragmentComponent
和ServiceComponent
,就可以使模块中的依赖在Fragment
和Service
中使用了。但是,使用了多组件的话,因为组件的生命周期和作用范围不同,因此是不能声明作用域注解的。
当然若是两个组件的生命周期是一样的,比如ViewComponent
和ViewWithFragmentComponent
,则还是可以使用作用域注解@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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/7c1151c55ab5ee74e753187735f2e52b.jpeg)
总结
【Android 详细知识点思维脑图(技能树)】
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
现在高级工程师还是比较缺少的**,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-rcPvQHh2-1711952836831)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。