引言
一个应用程序通常会自然的呈现出上图这样的层次结构,其中每个层次模块都有适用于当前模块的依赖项,这些依赖项的生命周期也可能需要和对应的模块生命周期所绑定,比如OKHttp客户端的实例需要和整个application生命周期保持一致,而ViewModel
只需要与对应View
的生命周期保持一致。另外,这个层次结构中,低层的模块可能需要访问高层模块的依赖项,同层模块之间的依赖项不共享,高层模块也不需要知道低层模块的依赖项。
在Dagger的使用中,因为我们需要使依赖项与使用这个依赖的模块的生命周期保持同步,因此每个模块都会创建属于自己的Component
,但这样做会使得底层模块的Component
中包含大量重复的上层模块中的依赖,造成不必要的浪费,那么有没有一种方式,可以让低层模块的Component
可以直接复用上层模块的Component
中的依赖项呢?
答案是有的,Dagger为我们提供了两种方式来实现这种复用:SubComponent
与Component依赖
。
SubComponent
SubComponent即子组件,子组件可以被声明在另一个Component中,然后SubComponent就能继承该Component的所有依赖项。
我们可以使用@SubComponent
注解替换某个Component
接口的@Component
注解,就可以将该Component声明为一个SubComponent。
@Subcomponent
interface MainComponent {
fun inject(activity: MainActivity)
}
接着有两种方式可以在父Component中声明SubComponent:
-
直接在父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)
-
第二种方式是先为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
依赖Component2
,Component2
依赖Component3
,现在Component1
想使用Component3
提供的OKHttp客户端,我们就需要在Component3
和Component2
中都编写暴露OKHttp的接口,是不是想想就觉得很麻烦?