继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

【Kotlin中使用Dagger2】进阶提升篇(二)

雷宇
关注TA
已关注
手记 10
粉丝 1.7万
获赞 251
概述

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

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

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

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

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

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

代码如下:

  • 父级 层面代码
    /*
        父级 Component
     */
    @Component(modules = [(ParentModule::class)])
    interface ParentComponent {

        fun addSub(module: ChildModule):ChildComponent
    }
    /*
        父级 Module
     */
    @Module
    class ParentModule {

        @Provides
        fun provideParentService(service: ParentServiceImpl):ParentService{
            return service
        }
    }
    /*
        父级 Service
     */
    interface ParentService {
        fun getParentInfo():String
    }
    /*
        父级 接口实现类
     */
    class ParentServiceImpl @Inject constructor():ParentService {

        override fun getParentInfo(): String {
            return "Parent info"
        }

    }
  • 子级 层面代码
    /*
        子级 Component
     */
    @Subcomponent(modules = [(ChildModule::class)])
    interface ChildComponent {

        fun inject(activity: SubActivity)
    }
    /*
        子级 Module
     */
    @Module
    class ChildModule {

        @Provides
        fun provideSubService(service: ChildServiceImpl): ChildService {
            return service
        }
    }
    /*
        子级 Service
     */
    interface ChildService {
        fun getChildInfo():String
    }
    /*
        子级 接口实现类
     */
    class ChildServiceImpl @Inject constructor(): ChildService {

        override fun getChildInfo(): String {
            return "Sub info"
        }

    }

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

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

    class SubActivity:BaseActivity() {

        @Inject
        lateinit var mParentService:ParentService

        @Inject
        lateinit var mChildService:ChildService

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_sub)

            initInjection()

            mSubBtn.setOnClickListener {
                toast(mParentService.getParentInfo())

                toast(mChildService.getChildInfo())
            }
        }

        private fun initInjection() {
            DaggerParentComponent.builder().parentModule(ParentModule()).build().addSub(ChildModule()).inject(this)
        }
}

可以看到,我们首先是初始化了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是如何定义注解的:

    package javax.inject;

    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;

    /**
     * Identifies a type that the injector only instantiates once. Not inherited.
     *
     * @see javax.inject.Scope @Scope
     */
    @Scope
    @Documented
    @Retention(RUNTIME)
    public @interface Singleton {}

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

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

    /*
        Application级别Component
     */
    @Singleton
    @Component(modules = [(ApplicationModule::class)])
    interface ApplicationComponent {

        fun context():Context
    }

    /*
        Application级别Module
     */
    @Module
    class ApplicationModule(private val context:MainApplication) {

        @Singleton
        @Provides
        fun provideContext():Context{
            return this.context
        }
    }

我们既用@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,所以写法上看着有点不太一样)

    import java.lang.annotation.Documented
    import java.lang.annotation.Retention
    import javax.inject.Scope
    import java.lang.annotation.RetentionPolicy.RUNTIME

    @Scope
    @Documented
    @Retention(RUNTIME)
    annotation class ActivityScope

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

    /*
        Activity级别Component
     */
    @ActivityScope
    @Component(dependencies = [(ApplicationComponent::class)],modules =     [(ActivityModule::class)])
    interface ActivityComponent {

        fun activity():Activity
        fun context(): Context
    }

    /*
        Activity级别Module
     */
    @Module
    class ActivityModule(private val activity: Activity) {

        @ActivityScope
        @Provides
        fun provideActivity(): Activity {
            return this.activity
        }
    }

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

    import java.lang.annotation.Documented
    import java.lang.annotation.Retention
    import javax.inject.Scope
    import java.lang.annotation.RetentionPolicy.RUNTIME

    @Scope
    @Documented
    @Retention(RUNTIME)
    annotation class PerModelScope

    /*
        业务级Component
     */
    @PerModelScope
    @Component(dependencies = [ActivityComponent::class],modules = [(MainModule::class)])
    interface MainComponent {
        fun inject(activity:MainActivity)
    }

    /*
        业务级Module
     */
    @Module
    class MainModule {

        @PerModelScope
        @Provides
        fun provideMainService(service: MainServiceImpl):MainService{
           return service
        }
    }

我们定义了一个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》

打开App,阅读手记
11人推荐
发表评论
随时随地看视频慕课网APP