Dagger通过多元绑定可以将多个依赖项放入到一个集合中,即使他们由不同的Module提供。集合的组装由Dagger自动完成,所以在往代码中注入时,不需要直接依赖于每个对象。使用多元绑定可以实现一个插件架构:由几个Module分别提供各自的服务,核心类就可以直接使用这些服务而无需手动获取。
Dagger提供了两种容器实现的多元绑定,Set和Map。
Set
使用@IntoSet和@ElementIntoSet注解为Set提供元素:
@Module
class ModuleA {
// 提供一个元素
@Provides @IntoSet
fun provideString(): String = "ABC"
}
// 多个模块可以向同一个Set中提供元素
@Module
class ModuleB {
// 提供多个元素
@Provides @ElementIntoSet
fun provideStrings(): Set<String> = setOf("DEF", "GHI")
}
多元绑定提供的Set就和其它依赖项一样,可以在依赖图中直接使用,或者通过Component向外提供,同时Scope、Quailfier、Provider包装这些同样对Set有效:
@Component(modules = [ModuleA::class, ModuleB::class])
interface MyComponent {
fun strings(): Lazy<Set<String>>
}
class Bar @Inject constructor(strings: Set<String>) {
}
需要注意的是,默认情况下Dagger会根据元素的类型将元素设置到对应的Set中,如果需要将相同类型的元素分别设置到不同的集合中时,则需要使用Qualifier对元素的provides-method和要注入的Set进行限定。
@Module
class ModuleC {
@Named("A")
@Provides @IntoSet
fun provideStringA(): String = "AAA"
@Named("B")
@Provides @IntoSet
fun provideStringB(): String = "AAA"
}
@Inject
@Named("A")
lateinit var strsA: Set<String>
@Inject
@Named("B")
lateinit var strsB: Set<String>
Map
给Map提供元素时,除了要使用@IntoMap注解标记provides-method外,还需要为提供的元素指明Key值。
预置的Key类型
Dagger默认支持一些基本的类型作为key值:Class、Int、Long、String,这些类型的Key都有与之对应的注解:@ClassKey、@IntKey、@LongKey、@StringKey,使用时直接用这些注解去标记提供的元素即可:
@Module
class ModuleD {
@Provides
@IntoMap @StringKey("foo")
fun provideFooValue(): Long = 1000L
@Provides
@IntoMap @ClassKey(Thing::class)
fun provideTingValue(): String = "value of Thing"
}
自定义Key类型
使用@MapKey元注解我们可以自定义Key类型注解,自定义的Key类型注解需要通过唯一的value成员指定key的具体值。
enum class Type {
A, B
}
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FIELD,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.CLASS
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class TypeKey(val value: Type)
@Provides
@IntoMap @TypeKey(Type.A)
fun provideTypeAValue(): String = "value of Type A"
如果需要将多个值作为键的话,则需要为@MapKey设置unwrapeValue = true,这样就可以将定义的注解中的所有成员都作为键。
使用
同样的,我们也可以像使用其它依赖项那样使用Map。默认情况下,Dagger也是根据Key值类型和元素类型来将元素设置到不同的集合中,如果需要对拥有相同键类型和值类型的元素进行区分的话,则需要使用Qualifier对元素和要注入的Map进行限定。
@MultiBinds绑定空容器
假如我们使用了@IntoMap和@IntoSet实现了多元绑定,但是因为一些原因,容器里暂时没有元素被注入(比如我提供了一个SDK,依赖使用它的开发者为这些容器里提供依赖项),此时我们可能需要注入一个空容器(开发者没有编写@IntoMap和@IntoSet会导致容器的注入位置报错)。
此时我们可以使用@MultiBinds,@MultiBinds告诉Dagger,在遇到某种容器的注入点时,如果找不到对应的@IntoMap和@IntoSet绑定,就注入一个空容器。
@Module
interface DefaultModule {
@MultiBinds
fun bindEmptyIds(): Set<String>
}
@Component()
interface AppComponent {
fun inject(client: Client)
}
class Client {
@Inject lateinit var ids: Set<String>
}
当然你也可以提供一个返回空容器的provides-method,然后用@IntoMap或@IntoSet标记它,但是这种方式相比@MultiBinds会创建provides-method的工厂类,造成不必要的开销。