查看: 9612|回复: 0

[手机开发] 【Kotlin中使用Dagger2】进阶提升篇(二)

发表于 2018-1-31 08:00:00
概述

在前面的文章中,我们介绍了使用Dagger2实现依赖注入,以及Component组织结构相关知识。具体如下:
【Kotlin中使用Dagger2】基础入门篇(一)
【Kotlin中使用Dagger2】基础入门篇(二)
【Kotlin中使用Dagger2】进阶提升篇(一)

这一小节,我们继续前一小节的内容,介绍一下@Subcomponent以及Scope的使用。

本节内容 @Subcomponent @Subcomponentg和Dependencies @Scope和@Singleton 自定义Scope @Subcomponent

之前小节中,我们使用依赖(dependencies),让Component之间产生了关联。除了使用依赖之外,我们也可以使用@Subcomponent来让Component之间建立联系。

我们这里简单作一个自定义,把“被依赖方"称为“父级”“依赖方”称为“子级”。使用@Subcomponent建立联系,有如下几步:

“子级Component”使用@Subcomponent进行标注,不再需要dependencies; “父级Component“需要把“子级Component”追加到当前Component中,同时传入“子级Component”需要的Module;

代码如下:

父级 层面代码
  1. /*
  2. 父级 Component
  3. */
  4. @Component(modules = [(ParentModule::class)])
  5. interface ParentComponent {
  6. fun addSub(module: ChildModule):ChildComponent
  7. }
复制代码
  1. /*
  2. 父级 Module
  3. */
  4. @Module
  5. class ParentModule {
  6. @Provides
  7. fun provideParentService(service: ParentServiceImpl):ParentService{
  8. return service
  9. }
  10. }
复制代码
  1. /*
  2. 父级 Service
  3. */
  4. interface ParentService {
  5. fun getParentInfo():String
  6. }
复制代码
  1. /*
  2. 父级 接口实现类
  3. */
  4. class ParentServiceImpl @Inject constructor():ParentService {
  5. override fun getParentInfo(): String {
  6. return "Parent info"
  7. }
  8. }
复制代码
子级 层面代码
  1. /*
  2. 子级 Component
  3. */
  4. @Subcomponent(modules = [(ChildModule::class)])
  5. interface ChildComponent {
  6. fun inject(activity: SubActivity)
  7. }
复制代码
  1. /*
  2. 子级 Module
  3. */
  4. @Module
  5. class ChildModule {
  6. @Provides
  7. fun provideSubService(service: ChildServiceImpl): ChildService {
  8. return service
  9. }
  10. }
复制代码
  1. /*
  2. 子级 Service
  3. */
  4. interface ChildService {
  5. fun getChildInfo():String
  6. }
复制代码
  1. /*
  2. 子级 接口实现类
  3. */
  4. class ChildServiceImpl @Inject constructor(): ChildService {
  5. override fun getChildInfo(): String {
  6. return "Sub info"
  7. }
  8. }
复制代码

可以看到,ChildComponent使用@ Subcomponent标注了,去除了dependencies属性;同时ParentComponent中提供一个方法,追加ChildComponent到当前Component中,方法参数为ChildComponent对应的Module(ChildModule)。

接下来,我们看下调用层会有什么样的变化:

  1. class SubActivity:BaseActivity() {
  2. @Inject
  3. lateinit var mParentService:ParentService
  4. @Inject
  5. lateinit var mChildService:ChildService
  6. override fun onCreate(savedInstanceState: Bundle?) {
  7. super.onCreate(savedInstanceState)
  8. setContentView(R.layout.activity_sub)
  9. initInjection()
  10. mSubBtn.setOnClickListener {
  11. toast(mParentService.getParentInfo())
  12. toast(mChildService.getChildInfo())
  13. }
  14. }
  15. private fun initInjection() {
  16. DaggerParentComponent.builder().parentModule(ParentModule()).build().addSub(ChildModule()).inject(this)
  17. }
  18. }
复制代码

可以看到,我们首先是初始化了DaggerParentComponent,通过调用addSub方法进行追加,返回值ChildComponent直接注入到当前Activity。

大家可以运行一下代码,会发现成员变量mParentService和mChildService都不为空,会不会觉得有些奇怪,因为我们之前说过,被依赖的Component需要暴露接口,才能提供服务,但是现在我们并没有暴露ParentService的服务,它依然是可以注入成功。下面我们就来看一下,使用@Subcomponent注解dependencies属性的区别。

@Subcomponent和dependencies

我们介绍了两种建立Component之间联系的方式。

但是使用@Subcomponent,我们需要“父级”知道“子级”的存在(因为需要添加追加方法),如果它们分别在不同的模块(Android多模块化),而且“父级”处于被依赖层(子模块引用父模块),@Subcomponent就达不到我们的目的。所以,他们之间还是有一些差别,具体如下:

dependencies适合于全局通用性依赖;@Subcomponent同类业务通性继承; dependencies更单纯的属于依赖性质;@Subcomponent更适合”继承“性质; dependencies需要”父级“提供服务能力;@Subcomponent不需要”父级“提供服务,但是需要追加”子级“方法; ”Dagger2注册“方式有所区别,具体可查看对应代码; @Scope和@Singleton

在开发过程中,随着项目越来越大,Component的数量越来越多,Component的组织结构就会越来越复杂,为了让大家使用Component结构更加清晰,Dagger2提供了作用域,在不同层次定义不同的作用域,可以让Component结构一目了然。

Dagger2提供了一个注解 @Scope(本身是Java提供的,咱们这是统一用Dagger2来说明),用来定义作用域注解,请注意,它并不是一个作用域的注解,而是用来 定义作用域注解的注解 ,不能直接使用。而@Singleton就是它的一种实现方式,从名称就能看出来,它是用来标注单例。

我们首先来看一下@Singleton的源码,看一下@Scope是如何定义注解的:

  1. package javax.inject;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.Retention;
  4. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  5. /**
  6. * Identifies a type that the injector only instantiates once. Not inherited.
  7. *
  8. * @see javax.inject.Scope @Scope
  9. */
  10. @Scope
  11. @Documented
  12. @Retention(RUNTIME)
  13. public @interface Singleton {}
复制代码

可以看到,定义了一注解,这个注解是使用@Scope来标注。

接下来,我们来使用一下@Singleton,既然它是表示”单例“,那我们可以用它来标注一下ApplicationComponent,同时可以标注Module对应的提供单例实例的方法。代码如下:

  1. /*
  2. Application级别Component
  3. */
  4. @Singleton
  5. @Component(modules = [(ApplicationModule::class)])
  6. interface ApplicationComponent {
  7. fun context():Context
  8. }
  9. /*
  10. Application级别Module
  11. */
  12. @Module
  13. class ApplicationModule(private val context:MainApplication) {
  14. @Singleton
  15. @Provides
  16. fun provideContext():Context{
  17. return this.context
  18. }
  19. }
复制代码

我们既用@Singleton标注了Component,也用它标注了工厂方法,这就是作用域的使用方式。这样的话,一目了然就能知道,ApplicationComponent与其ApplicationModule是使用单例的形式创建实例,提供的也是与单例相关的实例。

重点说明:很多网上的资料都介绍说,使用了@Singleton,自动就能创建了单例,这是极其错误的。大家也看到了它的源码,它只是用来表示一个作用范围,并没有实质上实现依赖注入的能力(咱们之前没有使用作用域,同样可以实现依赖注入)。同样,我们可以看下,ApplicationComponent中context是如何以单例的形式存在的,是不是我们在Application中直接初始Component传入的,而我们的Application肯定是只会调用一次,所以完成了单例context。对于其它的单例形式,大家也可以以单例来实现。

在之前的代码中, 如果只是给Application级别添加了作用域,编译是会报错的,因为咱们的ActivityComponent依赖于它,而ActivityComponent是没有作用域,这个时候会报出错误((unscoped) cannot depend on scoped components),很明显,没有scope的component不能依赖于有scope的component。那我们的ActivityComponent应该给一个什么样的Scope呢,Dagger2中只提供了一个@Singleton这样的默认的作用域,咱们的ActivityComponent总不能也给一个单例吧,这明显是不符合逻辑的。

自定义Scope

为了让ActivityComponent也有作用域,我们需要自定义一个,如何自定义?把@Singleton拷出来改一下名称不就行了。

我们先来定义一下ActivityScope,对应咱们的ActivityComponent级别。(@Singleton源码是Java,我们自定义使用的Kotlin,所以写法上看着有点不太一样)

  1. import java.lang.annotation.Documented
  2. import java.lang.annotation.Retention
  3. import javax.inject.Scope
  4. import java.lang.annotation.RetentionPolicy.RUNTIME
  5. @Scope
  6. @Documented
  7. @Retention(RUNTIME)
  8. annotation class ActivityScope
复制代码

同样的,我们来使用一下:

  1. /*
  2. Activity级别Component
  3. */
  4. @ActivityScope
  5. @Component(dependencies = [(ApplicationComponent::class)],modules = [(ActivityModule::class)])
  6. interface ActivityComponent {
  7. fun activity():Activity
  8. fun context(): Context
  9. }
  10. /*
  11. Activity级别Module
  12. */
  13. @Module
  14. class ActivityModule(private val activity: Activity) {
  15. @ActivityScope
  16. @Provides
  17. fun provideActivity(): Activity {
  18. return this.activity
  19. }
  20. }
复制代码

既然定义了Activity级别,那业务级也是需要定义的,这些大家都是按自己的需要自定义,也可以定义多个,用于不同的作用范围,不用完全按照我们的定义来,理解它最重要。

  1. import java.lang.annotation.Documented
  2. import java.lang.annotation.Retention
  3. import javax.inject.Scope
  4. import java.lang.annotation.RetentionPolicy.RUNTIME
  5. @Scope
  6. @Documented
  7. @Retention(RUNTIME)
  8. annotation class PerModelScope
  9. /*
  10. 业务级Component
  11. */
  12. @PerModelScope
  13. @Component(dependencies = [ActivityComponent::class],modules = [(MainModule::class)])
  14. interface MainComponent {
  15. fun inject(activity:MainActivity)
  16. }
  17. /*
  18. 业务级Module
  19. */
  20. @Module
  21. class MainModule {
  22. @PerModelScope
  23. @Provides
  24. fun provideMainService(service: MainServiceImpl):MainService{
  25. return service
  26. }
  27. }
复制代码

我们定义了一个PerModelScope作用域,使用在了业务层次,大家也可针对不同的业务定义不同的作用域,比如用户相关的UserScope,订单相关的OrderScope等等

通过我们的代码层次,大家可以看到,Scope到底有什么用?

管理Component层次结构,明确地显示Component的作用范围; 管理Component与Module之间的匹配关系,提供代码的可读性; 说白啦,”Scope“就是用来看的,并没有依赖注入的实质功能,为了大家的代码更加优雅,建议使用Scope明确作用域;

最后,作用域在@Subcomponentg和Dependencies使用时有一点区别:

使用Dependencies的Component之间不能有相同 @Scope 注解的;使用@SubComponent
则可以使用相同的@Scope注解 小结

这一小节我们介绍了使用@SubComponent建立Component之间的联系,它主要用于”继承“性质的Component之间使用。同时介绍了作用域Scope,主要用于组织Component结构层次,及与Module之间的匹配关系,它并没有实质的依赖注入能力。

更多精彩应用《Kotlin打造完整电商APP》



回复

使用道具 举报