kotlin使用Dagger2的过程全纪录

前言

Dagger2作为依赖注入神器,相信很多朋友都听说过它的大名。只不过它的有些概念,理解起来并不是那么清晰,并且在使用的过程中,也比较迷糊。

Dagger2有Google接手开发的一个基于JSR-330标准的依赖注入框架,它会在编译期间自动生成相关代码,负责依赖对象的创建,达到解耦目的。

下面将详细介绍关于kotlin使用Dagger2的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

kotlin中配置Dagger2

在app模块的build.gradle文件中进行如下配置,关于kapt的相关知识

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
 ...
}
dependencies {
 ...
 implementation 'com.google.dagger:dagger:2.11'
 kapt 'com.google.dagger:dagger-compiler:2.11'
}

相关常用注解:

  • @Inject
  • @Component
  • @Module
  • @Provides
  • @Qualifier和@Named
  • @Scope和@Singleton

@Inject

@Inject注解只是JSR-330中定义的注解,在javax.inject包中。 这个注解本身并没有作用,它需要依赖于注入框架才具有意义,可以用来标记构造函数、属性和方法。

标记构造函数

被标记的构造函数可以有0个或多个依赖作为参数。

同一个类中最多只可以标记一个构造函数。

class People @Inject constructor(val name:String = "Tom")

注意在kotlin中这种写法是不被允许的,因为这等价于java中的多个构造方法People(String name), People() 正确的写法应该是这样:

data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
}

标记属性

被标记的属性不能是final的,kotlin中不能是val。

被注入进的属性不能用private修饰(是Dagger2不支持,而非@Inject不支持)。

 @Inject
 lateinit var people:People

标记方法

被标记的方法可以有0个或多个依赖作为参数。

方法不能是抽象的。

class HomeActivity : AppCompatActivity() {
 private lateinit var people:People
 @Inject
 fun setPeople(people:People){
 this.people = people
 }
}

这种方法注入和属性注入并没有什么本质上的不同,实现效果也基本一样。还有一种做法是@Inject标记被注入类的某个方法,该方法会在类的构造方法之后接着被调用:

data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
 init {
 println("init:$name")
 }

 @Inject
 fun hello(){
 println("hello:$name")
 }
}

class HomeActivity : AppCompatActivity() {
 @Inject
 lateinit var people:People
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_home)

 //执行相关注入操作
 ...
 println(people.toString())
 }
}

运行结果是这样的:

01-02 11:57:30.995 16601-16601/? I/System.out: init:Tom
01-02 11:57:30.995 16601-16601/? I/System.out: hello:Tom
01-02 11:57:30.995 16601-16601/? I/System.out: People(name=Tom)

@Component

可以理解为一个注射器,可以算是Dagger2中最核心的一个注解,用来标记一个接口或者抽象类。使用@Component标记的接口,会在编译时自动生成一个Dagger+类名的实现类实现依赖注入。在Component中一般可以定义两种方法:

Members-injection methods:

该方法有一个参数,表示需要注入到的类,提醒Dagger在该类中寻找需要被注入的属性(被@Inject标记)。

void inject(SomeType someType);//无返回值
SomeType injectAndReturn(SomeType someType);//返回它的参数类型

等价于:

MembersInjector<SomeType> getMembersInjector();//使用MembersInjector.injectMembers方法注入

Provision methods:

该方法没有参数,返回一个需要被注入(或被提供)的依赖。一般用于为其他Component提供依赖的时候。

SomeType getSomeType();
Provider<SomeType> getSomeTypeProvider();//可以通过Provider.get访问任意次
Lazy<SomeType> getLazySomeType();//通过Lazy.get第一次访问时创建实例,并在之后的访问中都访问同一个实例
@Component
interface HomeComponent {
 fun inject(activity: HomeActivity)
 fun injectAndReturn(activity: HomeActivity): HomeActivity
 fun getInjectors(): MembersInjector<HomeActivity>
 
 fun getPeople():People
}

事实上,了解到这里我们已经可以使用最简单的Dagger2用法,毕竟有了依赖和注射器,只需要注入就可以了,我们来看一个最简单的Dagger2实例,只使用@Inject和@Component来完成注入。

第一步:在需要被注入的类的构造方法上添加注解@Inject

class People @Inject constructor() {
 fun hello(){
  println("hello")
 }
}

第二步:编写一个注射器接口

@Component
interface HomeComponent {
 fun inject(activity: HomeActivity)
}

第三步:注入

class HomeActivity : AppCompatActivity() {
 @Inject
 lateinit var people:People
 override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_home)

  DaggerHomeComponent.builder()
    .build()
    .inject(this)//会在这句代码时执行注入的操作
  people.hello()
 }
}

03-01 14:30:23.425 3256-3256/? I/System.out: hello
//大功告成

当然,上面这种只是最简单的用法,如果需要传入一些非自定义类的实例就不适用了,毕竟你不能在第三方的类中加入@Inject注解。此时就需要用到@Module和@Provides注解。

@Module

用来标记类,为Component提供依赖,相当于告诉Component,如果需要依赖可以来找我,当然前提是在Component中配置了该Module。同时Module可以通过includes依赖其他的Module。

@Provides

用来标记Module中的方法,该方法的返回类型是你需要提供的依赖类型。

举个自己项目中的例子,我需要在presenter中创建一个pl2303对象,pl2303对象的创建又需要context和pl2303Interface,所以我们需要提供三个依赖,因为context在其他地方也要用,我们单独提出来:

@Module
class ContextModule(private var mContext: Context) {
 @Provides
 fun getContext() = mContext
}

pl2303Interface只有这一个地方要用:

@Module(includes = arrayOf(ContextModule::class))
class Pl2303Module(private var pl2303Interface: ActivityCallBridge.PL2303Interface) {
 @Provides
 fun providePl2303(mContext: Context): Pl2303 {
  return Pl2303(mContext, pl2303Interface)
 }
}

其中includes可以是多个,我们这里把ContextModule加进来,这样创建pl2303就只差一个pl2303Interface,这是个接口对象,不能new,从构造函数注入进来。接下来创建注射器:

@Component(modules = arrayOf(Pl2303Module::class))
interface MainPresenterComponent {
 fun inject(presenter: MainPresenter)
}

最后注入:

class MainPresenter(val view: MainContract.View) : MainContract.Presenter, ActivityCallBridge.PL2303Interface, LifecycleObserver {
 @Inject lateinit var pl2303: Pl2303
 init {
  DaggerMainPresenterComponent.builder()
    .contextModule(ContextModule(view.context))
    .pl2303Module(Pl2303Module(this))
    .build()
    .inject(this)
 }
}

如果在大型项目中,一个Component有很多的Module,那么不需要传入参数的Module是可以省略的,看一下官方的注释文档:

public static void main(String[] args) {
  OtherComponent otherComponent = ...;
  MyComponent component = DaggerMyComponent.builder()
   // required because component dependencies must be set(必须的)
   .otherComponent(otherComponent)
   // required because FlagsModule has constructor parameters(必须的)
   .flagsModule(new FlagsModule(args))
   // may be elided because a no-args constructor is visible(可以省略的)
   .myApplicationModule(new MyApplicationModule())
   .build();
  }

@Named和@Qualifier

@Named是@Qualifier的一个实现。有时候我们会需要提供几个相同类型的依赖(比如继承于同一父类),如果不做处理的话编译器就不知道我们需要的具体是哪一个依赖而报错,比如这样:

abstract class Animal
class Dog : Animal() {
 override fun toString(): String {
  return "dog"
 }
}

class Cat : Animal() {
 override fun toString(): String {
  return "cat"
 }
}

@Module
class AnimalModule {
 @Provides
 fun provideDog(): Animal = Dog()
 @Provides
 fun provideCat(): Animal = Cat()
}
data class Pet @Inject constructor(val pet: Animal)

这时候就需要标记一下来告诉编译器我们需要的是哪个依赖:

@Module
class AnimalModule {
 @Provides
 @Named("dog")
 fun provideDog(): Animal = Dog()

 @Provides
 @Named("cat")
 fun provideCat(): Animal = Cat()
}
data class Pet @Inject constructor(@Named("dog") val pet: Animal)

上面我们说了@Named只是@Qualifier的一个实现而已,所以我们也可以用@Qualifier来达到一样的效果,实际使用中也更推荐使用@Qualifier的方式,因为@Named需要手写字符串来进行标识,容易出错。

使用@Qualifier需要注意:

  • 创建一个自定义的Qualifier至少需要@Qualifier, @Retention(RUNTIME)这两个注解。
  • 可以有自己的属性。

我们可以看一下@Named的源码来加深一下理解:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
 /** The name. */
 String value() default "";
}

下面我们比葫芦画瓢来改造一下上面的例子:

@Module
class AnimalModule {
 @Provides
 @DogAnim
 fun provideDog(): Animal = Dog()
 @Provides
 @CatAnim
 fun provideCat(): Animal = Cat()
}

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class DogAnim
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CatAnim
data class Pet @Inject constructor(@CatAnim val pet: Animal)

经测试依然是可以运行的。

Pet(pet=cat)

@Scope和@Singleton

A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type

@Scope是用来标记包含可注入构造函数的类或者提供注入依赖对象的类,简单来说,可以用来标记包含@Inject构造函数的类或者@Module类。

@Scope是用来管理依赖的生命周期的。它和@Qualifier一样是用来自定义注解的,而@Singleton和@Named类似,是@Scope的默认实现。

如果一个注射器和创建依赖对象的地方没有标记@Scope,那么每次注入时都会创建一个新的对象,如果标记了@Scope,则在规定的生命周期内会使用同一个对象,特别注意是在规定的生命周期内单例,并不是全局单例,或者可以理解为在@Component内单例。

还是借助上面的例子:

data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
 init {
  println("init:$name")
 }

 @Inject
 fun hello(){
  println("hello:$name")
 }
}

class HomeActivity : AppCompatActivity() {
 @Inject
 lateinit var people: People
 @Inject
 lateinit var people_2: People
 override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_home)
  DaggerHomeComponent.builder()
    .build()
    .inject(this)
  println("people===people_2:${people===people_2}")
 }
}

运行结果:

people===people_2:false

说明确实是两个不同的对象,接下来我们改造一下:

@Singleton
data class People constructor(val name: String) {
 ...//和之前一样
}

@Singleton
@Component(modules = arrayOf(AnimalModule::class))
interface HomeComponent {
 fun inject(activity: HomeActivity)
}
...//HomeActivity代码和之前一样

再次看下运行结果:

people===people_2:true

说明这次两次都是访问的同一个对象。上面提到这只是一个局部单例,那么怎么实现一个全局单例呢,很简单,只要保证标记的Component在全局只初始化一次即可,比如在Application中初始化,篇幅限制代码就不贴了,有兴趣的骚年可以自己实践一下。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。

代码技巧

转载请关注公众号:代码技巧 回复:授权

本文链接地址:https://www.oudahe.com/p/49730/