@ActivityScoped
data class User(var id: Int, var name: String, var mood: String) {
…
}
@AndroidEntryPoint
class MyTextView(context: Context?, attrs: AttributeSet?) : TextView(context, attrs) {
@Inject
lateinit var user: User
override fun onAttachedToWindow() {
super.onAttachedToWindow()
// 直接使用 MainActivity的
text = user.name
}
}
这里就能直接使用到 MainActivity的user对象。相当于 MainActivity 中注入后 User 后,又在代码的运行中将该数据分享到了 MyTextView 中。
在我们的代码中,就有许多需要数据共享的地方,比如 OkHttp / Retrofit
的单例,一些数据Bean等。
那这里有一个问题:假如我的数据不需要被共享,只在一处用,那我还要给它做依赖注入吗?
答案是:根据设计原则的扩展性,我们不能在一开始就断定一个类之后的迭代中是否会被各种类使用、继承,既然我们保证不了其以后不被共享,那我们就可以在一开始设计时,给它使用依赖注入的形式被创建,这样便于以后的迭代。
了解了 依赖注入 是什么之后,我们再来了解 Hilt,反正我们知道他是一种得到对象的手段。
Hilt 是基于 Dagger2 的针对 Android场景定制化 的框架。
这有点像什么? RxAndroid 是 RxJava 的Android平台定制化扩展。Andorid虽然由Java、Kotlin构成,但是它有很多平台的特性,比如它有 Java开发 所不知道的 Context
等。
Dagger框架虽然很出名,在国外也很流行,但是在国内使用其的App少之又少,列举一些缺点:
-
上手难,众多Android应用框架中,Dagger必定是最难学的那一档;
-
它就是一个复杂框架,没有针对任何平台,所以对于所有平台来说会难用;
-
在Android Studio4.0版本以前,无法追踪Dagger的依赖关系(就类比IDE无法通过快捷键知道一个接口有哪些实现类)
-
开发者不知道为啥要做依赖注入
对于第三点,Android Studio4.1已经支持了该功能,但是4.1有许多Bug,很多开发者都没有升级 = =。
Hilt的出现解决前两点问题,因为 Hilt 是 Dagger 针对Android平台的场景化框架,比如Dagger需要我们手动声明注入的地方,而Android声明的地方不都在 onCreate()
吗,所以Hilt就帮我们做了,除此之外还做了很多事情,这样一来,相较于Dagger,我们能用更少代码、更容易、更轻松的配置依赖注入。
至于 Koin
和 Hilt
的对比,两者配置的代码都比较少, 所以比较的层次更多在性能方面,我因为没有学习过Koin,所以这里就不做比较,可以看看这篇文章: 全方面分析 Hilt 和 Koin 性能
Daager
、Koin
、Hilt
,Google为什么致力于让开发者使用DI框架,可以看下Android开发者文档的这篇文章:传送门
Google认为移动端应用的架构设计,最重要的 Separation of concerns(分离关注点)。上网找解释,其实它就是 模块解耦。下面是Google官方推荐的Android应用架构图:
该图不多做说明,郭神的文章中就已经分析了该图片,Jetpack完全就是为该图架构服务。
Activity / Fragment是由系统所创建的,所以我们可以不用关心如何去创建。 ViewModel 层由 JetPack
来提供,所以我们也不用关心其创建。但是该有谁来创建 Repository
呢?
该架构下,Activity是不知道 Repository 存在的, 而 ViewModel 显然不能来创建 Repository,因为它只是依赖。
如果我们将Repostirory 设置成单例类,那每处地方都能够引用它,这样的处理显得有些不好。
这个问题比较棘手,但是如果我们使用 依赖注入框架,就能灵活的解决这个问题了。
===========================================================================
在 app 的 build.gradle
中加入:
plugins {
…
id ‘kotlin-kapt’
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
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
总结
现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。
我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。
Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。
如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。
附
(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)
9)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-gpmk1ipx-1711187585409)]
总结
现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。
我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。
Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。
如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。
附
(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)
[外链图片转存中…(img-fMjF1rDT-1711187585410)]