背景技术:在Kotlin中使用Dagger2,虽然Dagger2用法不变,但是代码自动生成插件有所不同
Dagger2是Dagger的升级版,是一个依赖注入框架,第一代由大名鼎鼎的Square公司共享出来,第二代则是由谷歌接手后推出的,现在由Google接手维护.Dagger2和Dagger1没有严格的继承关系
依赖注入(Dependency Injection简称DI):就是目标类(目标类需要进行依赖初始化的类,下面都会用目标类一词来指代)中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建,而是通过技术手段可以把其他的类的已经初始化好的实例自动注入到目标类中
依赖注入是面向对象编程的一种设计模式,其目的是为了降低程序耦合,这个耦合就是类之间的依赖引起的.
(1) java对象间的关系(依赖,关联,组合,聚合),耦合性从左到右依次增强,依赖注入是为了解决对象依赖引起的耦合
(2)依赖(Dependency ) :
含义:是类与类之间的连接,表示一个类依赖于另外一个类的定义;依赖关系仅仅描述了类与类之间的一种使用与被使用的关系;
体现:在Java体现为局部变量、方法/函数的参数或者是对静态方法的调用;
代码表现:class A 的某个方法创造了 class B 的实例或对class B的静态方法的调用或者某个方法以class B的实例为参数.
—–解耦,便于拓展
比如有个类A,他的构造函数需要传入B,C;然后代码里有10个地方实例化了A,那如果功能更改,A的构造函数改成了只有B,这个时候,你是不是要去这10个地方一个一个的改?如果是100个地方,你是不是要吐血?!如果采用dagger2,这样的需求只需要改1-2个地方,你感觉怎么样?对你有诱惑吗?
引用:作者:鱼满楼
链接:https://www.jianshu.com/p/22c397354997/
dagger2中核心点就是java注解(编译时注解),不熟请自行百度
Dagger2、ButterKnife这类依赖注入框架都已经采用了apt代码自动生成技术,其注解是停留在编译时,完全不影响性能。
(1)config.gradle
ext {
dependencies = [
//Dragger2注解
"dagger-compiler" : 'com.google.dagger:dagger-compiler:2.11',
"dagger-android" : 'com.google.dagger:dagger-android:2.11',
"dagger-android-support" : 'com.google.dagger:dagger-android-support:2.11',
"dagger-android-processor": 'com.google.dagger:dagger-android-processor:2.11',
"dagger" : "com.google.dagger:dagger:2.0.1",
"gson" : 'com.google.code.gson:gson:2.8.2',
]
}
(2) 在Moudle的build.gradle
中引入config.build
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply from: '../config.gradle'
android {
}
dependencies {
api fileTree(include: ['*.jar'], dir: 'libs')
// Dragger
api rootProject.ext.dependencies['dagger-android']
api rootProject.ext.dependencies['dagger-android-support']
// Kotlin 插件支持注解处理器,如 Dagger 或 DBFlow 。 为了让它们与 Kotlin 类一起工作,需要应用 kotlin-kapt 插件:
kapt rootProject.ext.dependencies['dagger-compiler']
kapt rootProject.ext.dependencies['dagger-android-processor']
......
}
注意:
在Kotlin中使用注解必须添加apply plugin: 'kotlin-kapt'
插件和 kapt rootProject.ext.dependencies['dagger-compiler']
依赖库,而且在组件化的过程中,每个moudle中都必须添加,不能依赖传递.即ModuleA添加了apply plugin: 'kotlin-kapt'
插件和 kapt rootProject.ext.dependencies['dagger-compiler']
依赖库,ModuleB通过了 implementation project(path: ':modulea')
,但是还需要添加添加了apply plugin: 'kotlin-kapt'
插件和 kapt rootProject.ext.dependencies['dagger-compiler']
依赖库,其他的Dagger相关库可以依赖传递
@Inject用来标注目标类中所依赖的其他类,同样用注解来标注所依赖的其他类的构造函数,那注解的名字就叫Inject.
说人话,要使用依赖注入,首先把要引用的类的构造方法通过@Inject注解,如下面的Student1和Lesson,在使用该类的地方也用声明的变量也通过@Inject注解,如MainActivity中的.
@Inject
lateinit var student1: Student
现在生产者已经产生了对象,消费者也表明了有购买的需求,但是他们却互不相识,无法进行交易.如下图.就在这个时候,这是时候Component就出现了
(1) Component是什么?有什么作用
Component也是一个注解类,一个类要想是Component,必须用@Component注解来标注该类,并且该类是接口或抽象类.Component的作用是桥梁,是纽带
(2)Component的实现原理
Component需要引用到目标类的实例,Component会查找目标类中用Inject注解标注的属性,查找到相应的属性后会接着查找该属性对应的用Inject标注的构造函数(这时候就发生联系了),剩下的工作就是初始化该属性的实例并把实例进行赋值。因此我们也可以给Component叫另外一个名字注入器(Injector)
Component注解的类,再编译之后,会生产一个以Dagger+类名的一个类,如下面的MainComponent会生成类DaggerMainComponent(补充一点,Kotlinkapt编译生成类的位置:
\build\generated\source\kapt\debug\
),我们需要在目标类MainActivity中加入下面代码
DaggerMainComponent.builder()
.build()
.inject(this)
DaggerMainComponent使用了建造者设计模式,inject方法是我们MainComponent中定义的,这样目标类就和Component建立了联系.Component会去遍历使用@Inject注解的常量,然后去查找对应的类是否有@Inject注解的构造方法,如果没有就会报异常.
(3)开始交易模式
在前面,使用@Inject注解构造方法生成对象的,我们称之为”生产者”(描述可能不准备,分析源码会发现,是用一个XXFactory类生产对象,后期再讲).使用@Inject注解成员的,我们称之为”消费者”,那么使用@Component注解的类可以理解为是”超市”,”超市”给消费者安排一个导购,把消费者的需求都罗列出来,然后把产品给到消费者.
由我们自己定义的类,我们可以使用@inject注解我们构造方式,当然构造函数分为两种:带参数和不带参数的。我们Student1和Lesson都有两个构造方法,我们分别注解无参的构造函数和带参的构造,看看有什么不同
Student1
package com.tdk.daggerdemo
import javax.inject.Inject
/**
* @Author tangdekun
* @Date 2018/8/1-15:59
* @Email tangdekun0924@gmail.com
*/
class Student1 @Inject constructor() {
var name: String? = null
var age: Int = 0
var lesson: Lesson? = null
constructor(lesson: Lesson) : this() {
this.lesson = lesson
}
override fun toString(): String {
return "name:$name,age:$age,lesson:${lesson?.name},score:${lesson?.score}"
}
}
Lesson
package com.tdk.daggerdemo
import javax.inject.Inject
/**
* @Author tangdekun
* @Date 2018/8/1-13:41
* @Email tangdekun0924@gmail.com
*/
class Lesson @Inject constructor() {
lateinit var name: String
var score: Int = 0
constructor(name: String, score: Int) : this() {
this.name = name
this.score = score
}
override fun toString(): String {
return "name:$name,score:$score"
}
}
MainComponent
package com.tdk.daggerdemo
import dagger.Component
/**
* @Author tangdekun
* @Date 2018/7/30-10:53
* @Email tangdekun0924@gmail.com
*/
@Component
interface MainComponent {
fun inject(mainActivity: MainActivity)
}
MainActivity
class MainActivity : AppCompatActivity() {
@Inject
lateinit var student1: Student1
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMainComponent.builder()
.build()
.inject(this)
student1.name = "唐德坤"
student1.age = 26
student1.lesson?.name = "英语"
student1.lesson?.score = 100
daggerdemo_tv.text = student1.toString()
LogUtils.dTag("MainActivity", student1.toString())
}
}
打印日志如下
name:唐德坤,age:26,lesson:null,score:null`
由日志可以看出,student1对象获取成功,但是student1.lesson对象没有初始化成功,获取到的是null对象。这是因为 @Inject只用在student1上,Component会去Student1中去找被 @Inject注解的构造方法,一看
Student1 @Inject constructor()
是无参构造,就给通过无参构造函数提供 了一个Student1的对象student1,但是象student1中的var lesson:Lesson
并没有赋值,所以就是初始值null
在上面的Student1
类
中,做一下小小的改动,去掉无参构造函数的@Inject
注解,然后给有参构造函数添加@Inject
注解。你也可以尝试,把两个构造函数都添加@Inject
注解,当然,一定会报错的,因为Component无法区别你需要什么样的对象。
Student1
package com.tdk.daggerdemo
import javax.inject.Inject
/**
* @Author tangdekun
* @Date 2018/8/1-15:59
* @Email tangdekun0924@gmail.com
*/
class Student1 constructor() {
var name: String? = null
var age: Int = 0
var lesson: Lesson? = null
@Inject
constructor(lesson: Lesson) : this() {
this.lesson = lesson
}
override fun toString(): String {
return "name:$name,age:$age,lesson:${lesson?.name},score:${lesson?.score}"
}
}
打印日志如下
name:唐德坤,age:26,lesson:英语,score:100`
现在结果变了,但是student1.lesson对象也初始化成功了。这是为什么了?Component在查找构造方法的时候,找到带lesson
参数的构造函数,这lesson
对象该去哪里去找了,他就到Lesson类中寻找@Inject注解的构造函数,一下子就找到了无参构造函数用@Inject产生的对象,然后就给了student1,Conponent转身就把这student1给了MainActivity,实力坑队友的
在(2)的基础上,修改Lesson类,用@Inject注解有参构造函数
Lesson
package com.tdk.daggerdemo
import javax.inject.Inject
/**
* @Author tangdekun
* @Date 2018/8/1-13:41
* @Email tangdekun0924@gmail.com
*/
class Lesson constructor() {
lateinit var name: String
var score: Int = 0
@Inject
constructor(name: String, score: Int) : this() {
this.name = name
this.score = score
}
override fun toString(): String {
return "name:$name,score:$score"
}
}
这时候就会编译不通过, 因为Component查找Lesson有参构造函数,会去找name和score中的实例,但是String类和Int类的构造方法并没有使用@inject注解,所以就会找不到name和score中的实例,然后就会报下面的错误.,告诉我们String类没有被@inject注解,也没有通过@Provide注解,所以name这个对象不能被提供.以后主要报这个异常,就要知道是用了没有被提供的对象.
java.lang.String cannot be provided without an @Inject constructor or from an @Provides-annota
ted method.
public abstract void inject(@org.jetbrains.annotations.NotNull()
^
java.lang.String is injected at
com.tdk.daggerdemo.Lesson.(name, …)
com.tdk.daggerdemo.Lesson is injected at
com.tdk.daggerdemo.Student1.(lesson)
com.tdk.daggerdemo.Student1 is injected at
com.tdk.daggerdemo.MainActivity.student1
com.tdk.daggerdemo.MainActivity is injected at
com.tdk.daggerdemo.MainComponent.inject(mainActivity)
现在问题来了?String类和Int类不是自定义,也就不能为其构造函数添加@Inject注解,但是读者依旧想使用有参构造来提供Lesson对象怎么办了?
这时候就需要@Module和@Provides闪亮登场
换了套路,先上代码
MainModule
@Module
class MainModule(val mainActivity: MainActivity) {
@Provides
fun provideLesson(): Lesson {
return Lesson("数学", 96)
}
}
MainComponent修改一下,Component后面添加了MainModule.modules = []
是一个数组,一个组件可以有多个Module
@Component(modules = [(MainModule::class)])
interface MainComponent {
fun inject(mainActivity: MainActivity)
}
MainActivity添加.mainModule(MainModule(this))
,否则也会报异常
DaggerMainComponent.builder()
.mainModule(MainModule(this))
.build()
.inject(this)
然后运行一下,发现一切变得那么美好了
args[0] = name:唐德坤,age:26,lesson:数学,score:96
读者可能有点蒙了,问题解决了,功能实现了,可是@Module是什么东东,@Provides又是干什么吃的.莫慌,且听我慢慢道来
@Module注解类,称之为对象提供类,类名最好以XXXModule
命名,他就做一件事情,生成对象,相当于”代工厂”,比如 MainModule就为我们提供了Lesson对象.
@Provides注解Module类中方法,方法最好以provideXXX
命名,返回值类型就是想要得到的对象的对应的类,即是希望通过@Inject
注解的类,返回值就是想要给对象赋的值,一如上面的provideLesson()
方法
(1) 如果需要在使用的时候配置参数,就可以把参数传入Module的构造函数中,如MainModule的构造函数传入
(val mainActivity: MainActivity)
,那么就可以在MainModule中使用mainActivity对象.其他的参数也是一样.传入的地方式MainActivity中Component实例化的时候使用builder模式传入了我们需要传入的值,如.mainModule(MainModule(this))
传入this(2)在Component中需要使用
@Component(modules = [(MainModule::class)])
引入Module,modules = []
是一个数组,一个组件可以有多个Module(3) Module中其中一个依赖又要依赖另外一个依赖,如果被@Provides标注的方法带有参数,dagger2会自动寻找本Module中其他返回值类型为参数的类型的且被@Provides标注的方法,如果本Module中找不到就会去看这个类的构造参数是否被@Inject标注了
(4)在Module的构造函数带有参数且参数被使用的情况下,所生产的Component类就没有create()方法了。
修改一下MainModule如下:
class MainModule {
@Provides
fun provideStudent(lesson: Lesson): Student {
return Student("tangdekun", 26, lesson)
}
@Provides
fun provideLesson2( @Named("yuwen")name: String, score: Int): Lesson {
return Lesson(name, score)
}
@Provides
@Named("yuwen")
fun provideName(): String {
return "语文"
}
@Provides
@Named("shuxue")
fun provideNameshuxue(): String {
return "数学"
}
@Provides
fun provideScore(): Int {
return 90
}
}
运行一下结果
args[0] = name:唐德坤,age:26,lesson:语文,score:90
provideStudent(lesson: Lesson)
有一个参数lesson,Component先在MainModule中查找返回值类型是Lesson且被@Provides注解的方法,然后就找到provideLesson2(name: String, score: Int)
,然后继续去找name和score,如果能够找到最好,找不到就会去String和Int被@Inject的构造函数,结果找不到,就会报错
参考文档:
https://www.jianshu.com/p/22c397354997/
https://www.jianshu.com/p/cd2c1c9f68d4
https://blog.csdn.net/mq2553299/article/details/77485800