Android Architecture ComponentsAndroid开发经验谈Android开发

翻译Dagger 2多元绑定

2017-10-09  本文已影响48人  lanceJin

官方文档链接:https://google.github.io/dagger/multibindings.html

1.前言


Dagger通过多元绑定可以将几个对象放入一个集合中,即使它们是由不同的Module提供。集合的组装由Dagger自己完成,所以往代码中注入时,不需要直接依赖于每个对象。使用多元绑定可以实现一个插件架构,举个例子,由几个Moule分别提供插件接口的实现,核心类就可以使用一整套的插件。或者将几个Module分别提供的Service,以名字为键放入Map。

2.Set的使用


为了给可注入的Set提供元素,需为Module的方法添加@IntoSet注解:

@Module
class MyModuleA {
  @Provides @IntoSet
  static String provideOneString(DepA depA, DepB depB) {
    return "ABC";
  }
}

若想一次性提供多个元素,可增加返回值是一个Set的Module方法,并且注解为@ElementsIntoSet

@Module
class MyModuleB {
  @Provides @ElementsIntoSet
  static Set<String> provideSomeStrings(DepA depA, DepB depB) {
    return new HashSet<String>(Arrays.asList("DEF", "GHI"));
  }
}

在Component的依赖图中,可直接依赖Set:

class Bar {
  @Inject Bar(Set<String> strings) {
    assert strings.contains("ABC");
    assert strings.contains("DEF");
    assert strings.contains("GHI");
  }
}

或者通过Component向外提供Set:

@Component(modules = {MyModuleA.class, MyModuleB.class})
interface MyComponent {
  Set<String> strings();
}

@Test void testMyComponent() {
  MyComponent myComponent = DaggerMyComponent.create();
  assertThat(myComponent.strings()).containsExactly("ABC", "DEF", "GHI");
}

与其它依赖关系一样,除了提供Set<Foo>,还有Provider<Set<Foo>>Lazy<Set<Foo>>。但是,不能依赖Set<Provider<Foo>>。为了提供修饰过的Set,给@Provides注解的方法添加修饰注解:

@Module
class MyModuleC {
  @Provides @IntoSet
  @MyQualifier
  static Foo provideOneFoo(DepA depA, DepB depB) {
    return new Foo(depA, depB);
  }
}

@Module
class MyModuleD {
  @Provides
  static FooSetUser provideFooSetUser(@MyQualifier Set<Foo> foos) { ... }
}

3.Map的使用


Dagger通过多元绑定给可注入的Map提供实例,只要Map的键在编译时被知道。与Set有点不同,除了添加能返回值的Module方法,并用@IntoMap注解,还需要另一个自定义的注解来给提供的实例指定Map的键。若是给修饰过的Map提供实例,须给@IntoMap注解的方法添加修饰注解。

可以注入Map本身(Map<K, V>)或者值y用被Provider包裹的Map(Map<K, Provider<V>>)。后者可以实现集合中的值不全部初始化,比如希望每次获取一个值,或者希望每次查询Map时,即使同一个值也是新的对象。

3.1.简单的键

当Map的键是字符串、Class<?>或者装箱的基本类型时,使用dagger.multibindings包中的标准注解:

@Module
class MyModule {
  @Provides @IntoMap
  @StringKey("foo")
  static Long provideFooValue() {
    return 100L;
  }

  @Provides @IntoMap
  @ClassKey(Thing.class)
  static String provideThingValue() {
    return "value for Thing";
  }
}

@Component(modules = MyModule.class)
interface MyComponent {
  Map<String, Long> longsByString();
  Map<Class<?>, String> stringsByClass();
}

@Test void testMyComponent() {
  MyComponent myComponent = DaggerMyComponent.create();
  assertThat(myComponent.longsByString().get("foo")).isEqualTo(100L);
  assertThat(myComponent.stringsByClass().get(Thing.class))
      .isEqualTo("value for Thing");
}

若键是枚举类型或具体的参数化类,则声明一个@MapKey注解的注解类型,它的唯一成员类型是键的类型:

enum MyEnum {
  ABC, DEF;
}

@MapKey
@interface MyEnumKey {
  MyEnum value();
}

@MapKey
@interface MyNumberClassKey {
  Class<? extends Number> value();
}

@Module
class MyModule {
  @Provides @IntoMap
  @MyEnumKey(MyEnum.ABC)
  static String provideABCValue() {
    return "value for ABC";
  }

  @Provides @IntoMap
  @MyNumberClassKey(BigDecimal.class)
  static String provideBigDecimalValue() {
    return "value for BigDecimal";
  }
}

@Component(modules = MyModule.class)
interface MyComponent {
  Map<MyEnum, String> myEnumStringMap();
  Map<Class<? extends Number>, String> stringsByNumberClass();
}

@Test void testMyComponent() {
  MyComponent myComponent = DaggerMyComponent.create();
  assertThat(myComponent.myEnumStringMap().get(MyEnum.ABC)).isEqualTo("value for ABC");
  assertThat(myComponent.stringsByNumberClass.get(BigDecimal.class))
      .isEqualTo("value for BigDecimal");
}

自定义的注解可以是任何名字,它的唯一成员可以是除数组外的任何有效的注解成员类型。

3.2.复杂的键

如果Map的键需要不只单个注解成员才能表达,可通过给自定义注解的@MapKey注解设置unwrapValue = false,来使用整个注解(包括多个注解成员)作为键。此时,自定义的注解可以拥有数组成员。

@MapKey(unwrapValue = false)
@interface MyKey {
  String name();
  Class<?> implementingClass();
  int[] thresholds();
}

@Module
class MyModule {
  @Provides @IntoMap
  @MyKey(name = "abc", implementingClass = Abc.class, thresholds = {1, 5, 10})
  static String provideAbc1510Value() {
    return "foo";
  }
}

@Component(modules = MyModule.class)
interface MyComponent {
  Map<MyKey, String> myKeyStringMap();
}

使用@AutoAnnotation创建注解实例。如果使用复杂的键,需要在运行时给自定义的注解创建实例,并传入Map的get(Object)方法中。最简单的办法是,添加静态方法返回自定义注解的实例,并使用@AutoAnnotation注解此方法。

class MyComponentTest {
  @Test void testMyComponent() {
    MyComponent myComponent = DaggerMyComponent.create();
    assertThat(myComponent.myKeyStringMap()
        .get(createMyKey("abc", Abc.class, new int[] {1, 5, 10}))
        .isEqualTo("foo");
  }

  @AutoAnnotation
  static MyKey createMyKey(String name, Class<?> implementingClass, int[] thresholds) {
    return new AutoAnnotation_MyComponentTest_createMyKey(name, implementingClass, thresholds);
  }
}
3.3.编译时不知道键

Map只在编译时已知键且用注解表示出来的情况下,才会工作。如果不满足这些限制,就不能直接使用,但是可以通过Set存储一系列的对象,然后转换成普通的Map。

@Module
class MyModule {
  @Provides @IntoSet
  static Map.Entry<Foo, Bar> entryOne(...) {
    Foo key = ...;
    Bar value = ...;
    return new SimpleImmutableEntry(key, value);
  }

  @Provides @IntoSet
  static Map.Entry<Foo, Bar> entryTwo(...) {
    Foo key = ...;
    Bar value = ...;
    return new SimpleImmutableEntry(key, value);
  }
}

@Module
class MyMapModule {
  @Provides
  static Map<Foo, Bar> fooBarMap(Set<Map.Entry<Foo, Bar>> entries) {
    Map<Foo, Bar> fooBarMap = new LinkedHashMap<>(entries.size());
    for (Map.Entry<Foo, Bar> entry : entries) {
      fooBarMap.put(entry.getKey(), entry.getValue());
    }
    return fooBarMap;
  }
}

通过这种方法,若想普通Map中含有Provider包裹的值,构建Set的Map.Entry对象也应该包含Provider。

@Module
class MyModule {
  @Provides @IntoSet
  static Map.Entry<Foo, Provider<Bar>> entry(
      Provider<BarSubclass> barSubclassProvider) {
    Foo key = ...;
    return new SimpleImmutableEntry(key, barSubclassProvider);
  }
}

@Module
class MyProviderMapModule {
  @Provides
  static Map<Foo, Provider<Bar>> fooBarProviderMap(
      Set<Map.Entry<Foo, Provider<Bar>>> entries) {
    return ...;
  }
}

4.声明多元绑定


通过给Module增加抽象的、被@Multibinds注解的方法,并返回希望声明的Set或Map。若Set或Map已经被@IntoSet@ElementsIntoSet@IntoMap中至少一个注解,那么不应该使用@Multibinds注解,除非它们是空集合。

@Module
abstract class MyModule {
  @Multibinds abstract Set<Foo> aSet();
  @Multibinds @MyQualifier abstract Set<Foo> aQualifiedSet();
  @Multibinds abstract Map<String, Foo> aMap();
  @Multibinds @MyQualifier abstract Map<String, Foo> aQualifiedMap();
}

提供的Set或Map可以被声明很多次都不会出错。Dagger不会实现或调用任何@Multibinds注解的方法。

5.返回空Set


作为一种针对空Set的备选的解决方案,添加@ElementsIntoSet注解的返回空Set的方法。

@Module
class MyEmptySetModule {
  @Provides @ElementsIntoSet
  static Set<Foo> primeEmptyFooSet() {
    return Collections.emptySet();
  }
}

6.继承Subcomponent的多元绑定


Subcomponent可以依赖从它父Component那继承到的Set和Map,就像可以依赖它父Component的其它关系一样。但是,Subcomponent可以通过给Module添加适当的@Provides注解的方法,来给继承到的Set和Map添加元素。此时,Set和Map不同于注入时,除了含有Subcomponent定义的值或Map.Entry,同时还包含父Component定义的。

@Component(modules = ParentModule.class)
interface ParentComponent {
  Set<String> strings();
  Map<String, String> stringMap();
  ChildComponent childComponent();
}

@Module
class ParentModule {
  @Provides @IntoSet
  static String string1() {
    "parent string 1";
  }

  @Provides @IntoSet
  static String string2() {
    "parent string 2";
  }

  @Provides @IntoMap
  @StringKey("a")
  static String stringA() {
    "parent string A";
  }

  @Provides @IntoMap
  @StringKey("b")
  static String stringB() {
    "parent string B";
  }
}

@Subcomponent(modules = ChildModule.class)
interface ChildComponent {
  Set<String> strings();
  Map<String, String> stringMap();
}

@Module
class ChildModule {
  @Provides @IntoSet
  static String string3() {
    "child string 3";
  }

  @Provides @IntoSet
  static String string4() {
    "child string 4";
  }

  @Provides @IntoMap
  @StringKey("c")
  static String stringC() {
    "child string C";
  }

  @Provides @IntoMap
  @StringKey("d")
  static String stringD() {
    "child string D";
  }
}

@Test void testMultibindings() {
  ParentComponent parentComponent = DaggerParentComponent.create();
  assertThat(parentComponent.strings()).containsExactly(
      "parent string 1", "parent string 2");
  assertThat(parentComponent.stringMap().keySet()).containsExactly("a", "b");

  ChildComponent childComponent = parentComponent.childComponent();
  assertThat(childComponent.strings()).containsExactly(
      "parent string 1", "parent string 2", "child string 3", "child string 4");
  assertThat(childComponent.stringMap().keySet()).containsExactly(
      "a", "b", "c", "d");
}

7.总结


使用多元绑定时,会让集合出现的莫名其妙,对其中的元素也很难掌握清楚。建议根据个人的情况进行使用,但跨Module供值的确是优势。

上一篇下一篇

猜你喜欢

热点阅读