依赖注入(六)—— Dagger中依赖项在Component间的复用

2,362 阅读4分钟

引言

应用结构

一个应用程序通常会自然的呈现出上图这样的层次结构,其中每个层次模块都有适用于当前模块的依赖项,这些依赖项的生命周期也可能需要和对应的模块生命周期所绑定,比如OKHttp客户端的实例需要和整个application生命周期保持一致,而ViewModel只需要与对应View的生命周期保持一致。另外,这个层次结构中,低层的模块可能需要访问高层模块的依赖项,同层模块之间的依赖项不共享,高层模块也不需要知道低层模块的依赖项。

在Dagger的使用中,因为我们需要使依赖项与使用这个依赖的模块的生命周期保持同步,因此每个模块都会创建属于自己的Component,但这样做会使得底层模块的Component中包含大量重复的上层模块中的依赖,造成不必要的浪费,那么有没有一种方式,可以让低层模块的Component可以直接复用上层模块的Component中的依赖项呢?

答案是有的,Dagger为我们提供了两种方式来实现这种复用:SubComponentComponent依赖

SubComponent

SubComponent即子组件,子组件可以被声明在另一个Component中,然后SubComponent就能继承该Component的所有依赖项。

我们可以使用@SubComponent注解替换某个Component接口的@Component注解,就可以将该Component声明为一个SubComponent。

@Subcomponent
interface MainComponent {
    fun inject(activity: MainActivity)
}

接着有两种方式可以在父Component中声明SubComponent:

  1. 直接在父Component中声明一个获取SubComponent的接口,这种方式适用于SubComponent不需要显式配置的情况:

    @Component(
        modules = [CommonModule::class]
    )
    interface AppComponent {
        fun inject(activity: MyApplication)
        fun getMainComponent(): MainComponent
    }
    

    使用是直接在父Component的实例上调用获取方法即可:

    val mainComponent = MyApplication.instance.component.getMainComponent()
    mainComponent.inject(activity)
    
  2. 第二种方式是先为SubComponent提供Builder,然后将此SubComponent在父Component的Module中指定为subcomponents,最后在父Component中编写提供SubComponent建造器对象的接口。这种方式适用于SubComponent需要进行显式配置时:

    @Subcomponent(modules = [PreferenceModule::class])
    interface MainComponent {
        fun inject(activity: MainActivity)
        // 1. 为SubComponent提供Builder
        @Subcomponent.Builder
        interface Builder {
            fun setPrefModule(prefModule: PreferenceModule): Builder
            fun build(): MainComponent
        }
    }
    
    // 2. 在父Component的Module中指定为subcomponents
    @Module(subcomponents = [MainComponent::class])
    class SubPageModule { }
    
    @Component(
        // 别忘了引入上面的module
        modules = [CommonModule::class, SubPageModule::class],
    )
    interface AppComponent {
        fun inject(activity: MyApplication)
        // 3. 在父Component中编写提供SubComponent建造器对象的接口
        fun mainComponentBuilder(): MainComponent.Builder
    }
    

    对于这种方式定义的SubComponent,我们又有两种方式使用:一是直接在父Component的实例上调用SubComponent的Builder的获取接口:

    val mainComponent = MyApplication.instance.component
        .mainComponentBuilder()
        .setPrefModule(PreferenceModule(this, "main"))
        .build()
    

    二是将SubComponent的Builder直接注入到父模块中,子模块再通过父模块访问:

    class MyApplication : Application() {
        @Inject
        lateinit var mainComponentBuilder: MainComponent.Builder
        override fun onCreate() {
            super.onCreate()
            DaggerAppComponent.create().inject(this)
        }
        ...
    }
    
    val mainComponent = MyApplication.instance
        .mainComponentBuilder
        .setPrefModule(PreferenceModule(this, "main"))
        .build()
    

Component依赖

我们还可以为Component指定其依赖的Component,从而让当前Component可以使用所依赖Component中的所有显式暴露的依赖项。定义方式很简单,只需要在定义Component时将所依赖的Component的class传给@Component注解的dependencies参数,并在被依赖的Component接口中为依赖项编写接口即可。

@Singleton
@Component(modules = [CommonModule::class])
interface AppComponent {
    fun inject(activity: MyApplication)
    fun gson(): Gson // MainComponent只能使用这种显式暴露出来的依赖项
}

@MyScope
@Component(
    modules = [PreferenceModule::class],
    dependencies = [AppComponent::class]
)
interface MainComponent {
    fun inject(activity: MainActivity)
}

⚠️ 注意,Component与其所依赖的Component不能有相同的Scope。

使用时直接通过DaggerXXComponent来构造对应的Component实例,需要手动将所依赖的实例传递进去。

val mainComponent = DaggerMainComponent.builder()
    // 手动的传递依赖的Component实例
    .appComponent(MyApplication.instance.component)
    .preferenceModule(PreferenceModule(this, "main"))
    .build()

这种方式虽然看起来比较简单,声明依赖的方式也是从上往下这种比较自然的方式,但实际却并不好用,首先是被依赖的Component必须要为复用的依赖编写显示的暴露接口;其次是当有多层Component依赖时,最上层Component共享给最下层Component的依赖项,要在每一层都提供显示的暴露接口,举个🌰:
假如Component1依赖Component2Component2依赖Component3,现在Component1想使用Component3提供的OKHttp客户端,我们就需要在Component3Component2中都编写暴露OKHttp的接口,是不是想想就觉得很麻烦?