kotlin使用Dagger2的过程全纪录
前言
Dagger2作为依赖注入神器,相信很多朋友都听说过它的大名。只不过它的有些概念,理解起来并不是那么清晰,并且在使用的过程中,也比较迷糊。
Dagger2有Google接手开发的一个基于JSR-330标准的依赖注入框架,它会在编译期间自动生成相关代码,负责依赖对象的创建,达到解耦目的。
下面将详细介绍关于kotlin使用Dagger2的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。
kotlin中配置Dagger2
在app模块的build.gradle文件中进行如下配置,关于kapt的相关知识。
applyplugin:'com.android.application' applyplugin:'kotlin-android' applyplugin:'kotlin-android-extensions' applyplugin:'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个或多个依赖作为参数。
同一个类中最多只可以标记一个构造函数。
classPeople@Injectconstructor(valname:String="Tom")
注意在kotlin中这种写法是不被允许的,因为这等价于java中的多个构造方法People(Stringname),People()正确的写法应该是这样:
dataclassPeopleconstructor(valname:String){ @Inject constructor():this("Tom") }
标记属性
被标记的属性不能是final的,kotlin中不能是val。
被注入进的属性不能用private修饰(是Dagger2不支持,而非@Inject不支持)。
@Inject lateinitvarpeople:People
标记方法
被标记的方法可以有0个或多个依赖作为参数。
方法不能是抽象的。
classHomeActivity:AppCompatActivity(){ privatelateinitvarpeople:People @Inject funsetPeople(people:People){ this.people=people } }
这种方法注入和属性注入并没有什么本质上的不同,实现效果也基本一样。还有一种做法是@Inject标记被注入类的某个方法,该方法会在类的构造方法之后接着被调用:
dataclassPeopleconstructor(valname:String){ @Inject constructor():this("Tom") init{ println("init:$name") } @Inject funhello(){ println("hello:$name") } } classHomeActivity:AppCompatActivity(){ @Inject lateinitvarpeople:People overridefunonCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) //执行相关注入操作 ... println(people.toString()) } }
运行结果是这样的:
01-0211:57:30.99516601-16601/?I/System.out:init:Tom 01-0211:57:30.99516601-16601/?I/System.out:hello:Tom 01-0211:57:30.99516601-16601/?I/System.out:People(name=Tom)
@Component
可以理解为一个注射器,可以算是Dagger2中最核心的一个注解,用来标记一个接口或者抽象类。使用@Component标记的接口,会在编译时自动生成一个Dagger+类名的实现类实现依赖注入。在Component中一般可以定义两种方法:
Members-injectionmethods:
该方法有一个参数,表示需要注入到的类,提醒Dagger在该类中寻找需要被注入的属性(被@Inject标记)。
voidinject(SomeTypesomeType);//无返回值 SomeTypeinjectAndReturn(SomeTypesomeType);//返回它的参数类型
等价于:
MembersInjectorgetMembersInjector();//使用MembersInjector.injectMembers方法注入
Provisionmethods:
该方法没有参数,返回一个需要被注入(或被提供)的依赖。一般用于为其他Component提供依赖的时候。
SomeTypegetSomeType(); ProvidergetSomeTypeProvider();//可以通过Provider.get访问任意次 Lazy getLazySomeType();//通过Lazy.get第一次访问时创建实例,并在之后的访问中都访问同一个实例
@Component interfaceHomeComponent{ funinject(activity:HomeActivity) funinjectAndReturn(activity:HomeActivity):HomeActivity fungetInjectors():MembersInjectorfungetPeople():People }
事实上,了解到这里我们已经可以使用最简单的Dagger2用法,毕竟有了依赖和注射器,只需要注入就可以了,我们来看一个最简单的Dagger2实例,只使用@Inject和@Component来完成注入。
第一步:在需要被注入的类的构造方法上添加注解@Inject
classPeople@Injectconstructor(){ funhello(){ println("hello") } }
第二步:编写一个注射器接口
@Component interfaceHomeComponent{ funinject(activity:HomeActivity) }
第三步:注入
classHomeActivity:AppCompatActivity(){ @Inject lateinitvarpeople:People overridefunonCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) DaggerHomeComponent.builder() .build() .inject(this)//会在这句代码时执行注入的操作 people.hello() } } 03-0114:30:23.4253256-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 classContextModule(privatevarmContext:Context){ @Provides fungetContext()=mContext }
pl2303Interface只有这一个地方要用:
@Module(includes=arrayOf(ContextModule::class)) classPl2303Module(privatevarpl2303Interface:ActivityCallBridge.PL2303Interface){ @Provides funprovidePl2303(mContext:Context):Pl2303{ returnPl2303(mContext,pl2303Interface) } }
其中includes可以是多个,我们这里把ContextModule加进来,这样创建pl2303就只差一个pl2303Interface,这是个接口对象,不能new,从构造函数注入进来。接下来创建注射器:
@Component(modules=arrayOf(Pl2303Module::class)) interfaceMainPresenterComponent{ funinject(presenter:MainPresenter) }
最后注入:
classMainPresenter(valview:MainContract.View):MainContract.Presenter,ActivityCallBridge.PL2303Interface,LifecycleObserver{ @Injectlateinitvarpl2303:Pl2303 init{ DaggerMainPresenterComponent.builder() .contextModule(ContextModule(view.context)) .pl2303Module(Pl2303Module(this)) .build() .inject(this) } }
如果在大型项目中,一个Component有很多的Module,那么不需要传入参数的Module是可以省略的,看一下官方的注释文档:
publicstaticvoidmain(String[]args){ OtherComponentotherComponent=...; MyComponentcomponent=DaggerMyComponent.builder() //requiredbecausecomponentdependenciesmustbeset(必须的) .otherComponent(otherComponent) //requiredbecauseFlagsModulehasconstructorparameters(必须的) .flagsModule(newFlagsModule(args)) //maybeelidedbecauseano-argsconstructorisvisible(可以省略的) .myApplicationModule(newMyApplicationModule()) .build(); }
@Named和@Qualifier
@Named是@Qualifier的一个实现。有时候我们会需要提供几个相同类型的依赖(比如继承于同一父类),如果不做处理的话编译器就不知道我们需要的具体是哪一个依赖而报错,比如这样:
abstractclassAnimal classDog:Animal(){ overridefuntoString():String{ return"dog" } } classCat:Animal(){ overridefuntoString():String{ return"cat" } } @Module classAnimalModule{ @Provides funprovideDog():Animal=Dog() @Provides funprovideCat():Animal=Cat() } dataclassPet@Injectconstructor(valpet:Animal)
这时候就需要标记一下来告诉编译器我们需要的是哪个依赖:
@Module classAnimalModule{ @Provides @Named("dog") funprovideDog():Animal=Dog() @Provides @Named("cat") funprovideCat():Animal=Cat() } dataclassPet@Injectconstructor(@Named("dog")valpet:Animal)
上面我们说了@Named只是@Qualifier的一个实现而已,所以我们也可以用@Qualifier来达到一样的效果,实际使用中也更推荐使用@Qualifier的方式,因为@Named需要手写字符串来进行标识,容易出错。
使用@Qualifier需要注意:
- 创建一个自定义的Qualifier至少需要@Qualifier,@Retention(RUNTIME)这两个注解。
- 可以有自己的属性。
我们可以看一下@Named的源码来加深一下理解:
@Qualifier @Documented @Retention(RUNTIME) public@interfaceNamed{ /**Thename.*/ Stringvalue()default""; }
下面我们比葫芦画瓢来改造一下上面的例子:
@Module classAnimalModule{ @Provides @DogAnim funprovideDog():Animal=Dog() @Provides @CatAnim funprovideCat():Animal=Cat() } @Qualifier @Retention(AnnotationRetention.RUNTIME) annotationclassDogAnim @Qualifier @Retention(AnnotationRetention.RUNTIME) annotationclassCatAnim dataclassPet@Injectconstructor(@CatAnimvalpet:Animal)
经测试依然是可以运行的。
Pet(pet=cat)
@Scope和@Singleton
Ascopeannotationappliestoaclasscontaininganinjectableconstructorandgovernshowtheinjectorreusesinstancesofthetype
@Scope是用来标记包含可注入构造函数的类或者提供注入依赖对象的类,简单来说,可以用来标记包含@Inject构造函数的类或者@Module类。
@Scope是用来管理依赖的生命周期的。它和@Qualifier一样是用来自定义注解的,而@Singleton和@Named类似,是@Scope的默认实现。
如果一个注射器和创建依赖对象的地方没有标记@Scope,那么每次注入时都会创建一个新的对象,如果标记了@Scope,则在规定的生命周期内会使用同一个对象,特别注意是在规定的生命周期内单例,并不是全局单例,或者可以理解为在@Component内单例。
还是借助上面的例子:
dataclassPeopleconstructor(valname:String){ @Inject constructor():this("Tom") init{ println("init:$name") } @Inject funhello(){ println("hello:$name") } } classHomeActivity:AppCompatActivity(){ @Inject lateinitvarpeople:People @Inject lateinitvarpeople_2:People overridefunonCreate(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 dataclassPeopleconstructor(valname:String){ ...//和之前一样 } @Singleton @Component(modules=arrayOf(AnimalModule::class)) interfaceHomeComponent{ funinject(activity:HomeActivity) } ...//HomeActivity代码和之前一样
再次看下运行结果:
people===people_2:true
说明这次两次都是访问的同一个对象。上面提到这只是一个局部单例,那么怎么实现一个全局单例呢,很简单,只要保证标记的Component在全局只初始化一次即可,比如在Application中初始化,篇幅限制代码就不贴了,有兴趣的骚年可以自己实践一下。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。