Dagger 2从浅到深(六)

xiaoxiao2021-02-27  397

Dagger系列:

Dagger 2从浅到深(一)Dagger 2从浅到深(二)Dagger 2从浅到深(三)Dagger 2从浅到深(四)Dagger 2从浅到深(五)Dagger 2从浅到深(六)Dagger 2从浅到深(七)Dagger 2应用于Android的完美扩展库-dagger.android

Demo地址:

DaggerLearnKotlin-Dagger-2-Retrofit-Android-Architecture-Components

之前用到的,都是基于单个实例的依赖注入。强大的Dagger也支持多个元素的依赖注入,将注入的元素聚合到Set或者Map中,以便应用程序代码可以注入Set/Map,而不依赖于单独的绑定。

多个元素绑定并注入到Set

普通Set注入

将多个元素注入到Set中,不仅支持单个元素注入到Set中,同时支持子Set<T>注入到Set中。

将单个元素注入到Set:

@Module() public class FruitModule { @Provides @IntoSet public BananaBean providerBanana() { return new BananaBean("特朗普香蕉"); } }

@Module() public class DrinkModule {

@Provides @IntoSet public BananaBean providerBanana() { return new BananaBean("巴拿马香蕉"); }

}

将子Set<T>注入到Set:

@Module() public class FruitModule { @Provides @ElementsIntoSet public Set<BananaBean> providerBananaSet() { Set<BananaBean> set = new HashSet<>(); set.add(new BananaBean("布什香蕉")); set.add(new BananaBean("约翰逊香蕉")); return set; } }

在Component中,表明注入到Set的实例的提供Module。声明setBanana()方法,用来提供集合Set<BananaBean>

@Component(modules = {FruitModule.class, DrinkModule.class}) public interface FruitComponent { void inject(FruitActivity activity); Set<BananaBean> setBanana(); }

声明类SetBananaBean,其成员变量为Set,同时@Inject注解的构造函数的参数类型为Set。

public class SetBananaBean { Set<BananaBean> set; @Inject public SetBananaBean(Set<BananaBean> set) { this.set = set; } @Override public String toString() { return "SetBananaBean{" + "set=" + set + '}'; } }

在FruitActivity类中

mSetBanana为通过依赖注入的Set<BananaBean> 实例;mSetBananaBean为依赖注入的SetBananaBean实例,其成员变量为Set<BananaBean>

setBanana为FruitComponent所提供的Set<BananaBean> 实例

public class FruitActivity extends AppCompatActivity {

*** @Inject Set<BananaBean> mSetBanana; @Inject SetBananaBean mSetBananaBean; @Override protected void onCreate(Bundle savedInstanceState) { FruitComponent fruitComponent = DaggerFruitComponent.builder() .build(); fruitComponent.inject(this); super.onCreate(savedInstanceState); setContentView(getLayoutId()); *** Set<BananaBean> setBanana = fruitComponent.setBanana(); // [BananaBean{name='布什香蕉'}, BananaBean{name='约翰逊香蕉'}, BananaBean{name='特朗普香蕉'}, BananaBean{name='巴拿马香蕉'}] Log.d("test", "setBanana: " + setBanana.toString()); // [BananaBean{name='巴拿马香蕉'}, BananaBean{name='布什香蕉'}, BananaBean{name='约翰逊香蕉'}, BananaBean{name='特朗普香蕉'}] Log.d("test", "mSetBanana: " + mSetBanana.toString()); // [BananaBean{name='巴拿马香蕉'}, BananaBean{name='布什香蕉'}, BananaBean{name='约翰逊香蕉'}, BananaBean{name='特朗普香蕉'}] Log.d("test", "mSetBananaBean: " + mSetBananaBean.toString()); } ***

}

从Log这里,我们可以看出,在该Component中,不仅可以提供Set的依赖注入,还可以给其他依赖注入的实例提供数据源。当然,还可以使用Component获取注入的Set实例。

注意:

与任何其他绑定一样,除了依赖注入Set<Foo>之外,还可以依赖注入Provider<Set<Foo>>或Lazy<Set<Foo >>,但是不能依赖Se<Provider<Foo>>。

特定Set注入

如果要为一个特定的集合注入数据,应对每个提供数据的Provides方法进行@Qualifier注解限定,从而避免了“依赖迷失”。在依赖注入该特定的Set时,也应使用相同的@Qualifier注解限定。

@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) { ... } } public class FruitActivity extends AppCompatActivity { *** @Inject @MyQualifier Set<BananaBean> mProvinceSetBanana; *** }

多个元素绑定并注入到Map #

Dagger不仅可以将绑定的多个元素依赖注入到Set,还可以将绑定的多个元素依赖注入到Map。与依赖注入Set不同的是,依赖注入Map时,必须在编译时指定Map的Key,那么Dagger向MapMap注入相应的元素。

对于向Map提供元素的@Provides方法,需要使用@IntoMap,同时指定该元素的Key(例如@StringKey(“foo”)、@ClassKey(Thing.class))。

在Dagger中,Map的Key可以分为简单的和组合的两种情况,对于简单的或者组合的Key是怎么区分呢?下面以实例来理解。

简单的Key

所谓简单的Key,是指Map的Key的数据类型为单一的某一种数据类型,也就是说Key的数据类型必须一致,通常我们使用Map也就是这么使用的。

如果注入的Map的Key的类型为String、Class<?>等,在dagger.multibindings中的提供了一套标准的注解:

ClassKeyIntKeyLongKeyStringKey

我们先以StringKey、ClassKey注解为例注入Map:

在Module中定义提供元素的@Provides方法

@Module() public class DrinkModule { @Provides @IntoMap // 指定该@Provides方法向Map提供元素 @StringKey("A") // 指定该元素在Map中所对应的的Key public AppleBean providerApple() { return new AppleBean("A苹果"); } @Provides @IntoMap @ClassKey(DrinkActivity.class) public AppleBean providerAppleMap() { return new AppleBean("北京苹果"); } }

在Component中,定义提供注入的Map实例的方法

@Component(modules = {DrinkModule.class}) public interface DrinkComponent { void inject(DrinkActivity activity); Map<String, AppleBean> appleByString(); Map<Class<?>, AppleBean> appleByClass(); }

在DrinkActivity类中,获取依赖的Map实例有两种方式:

使用@Inject,注入Map

调用Component定义的方法

public class DrinkActivity extends AppCompatActivity { *** @Inject Map<String, AppleBean> appleByString; @Inject Map<Class<?>, AppleBean> appleByClass; @Override protected void onCreate(Bundle savedInstanceState) { DrinkComponent drinkComponent = DaggerDrinkComponent.builder() .build(); drinkComponent.inject(this); *** // 依赖注入Map // appleByString: {A=AppleBean{name='A苹果'}} Log.d("test", "appleByString: " + appleByString); // appleByClass: {class advanced.todo.com.daggerlearn.activity.DrinkActivity=AppleBean{name='北京苹果'}} Log.d("test", "appleByClass: " + appleByClass); // 组件提供Map实例 // appleByString: {A=AppleBean{name='A苹果'}} Log.d("test", "appleByString: " + drinkComponent.appleByString()); // appleByClass: {class advanced.todo.com.daggerlearn.activity.DrinkActivity=AppleBean{name='北京苹果'}} Log.d("test", "appleByClass:" + drinkComponent.appleByClass()); } *** }

自定义Key

在实际开发过程中,Map的Key的数据类型千变万化,Google大神也不能一一提供,只是针对常用的Key提供了注解。那么问题就来了,dagger.multibindings定义的Key的注解毕竟有限,如果注入到的Map的Key的数据类型为Apple,该怎么办呢?

@Documented @Target(METHOD) @Retention(RUNTIME) @MapKey public @interface StringKey { String value(); }

先看下,StringKey的源码,StringKey的value类型为String,应该是指定了Key的数据类型为String。而StringKey又被@MapKey注解,是不是表明该注解是Map的Key的注解呢?不妨试一下,我们自定义一个AppleKey:

@Documented @Target(METHOD) @Retention(RUNTIME) @MapKey public @interface AppleKey { AppleBean value(); }

可是,编译器直接报错了:“Invalid type ‘AppleBean’ for annotation member”,其意思是类型为“AppleBean”的注释成员无效,也就是这种定义方式可行,为什么呢?

在 section 9.6.1 of the JLS中,明确指定了在注释类型中声明的方法的返回类型,如果不满足指定的返回类型,那么编译时会报错:

基本数据类型StringClass枚举类型注解类型以上数据类型的数组

如果注入Map的Key为枚举或者具体参数化类,应自定义Map的key的数据类型的注解,并使用@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"); }

对于自定义的Key的数据类型的注解,其数据类型可以为除数组以外的任何有效的注解的成员类型.当然,名称可以随意定义。

组合的Map的Key

组合的Map的Key,是指Map的Key由多个数据类型的成员组成。这与我们所熟知的Map<K, V>是相悖的,因为Key的数据类型是指定的K,怎么还会出现多种数据类型呢?

我们先以一个例子,看看组合的Key到底是个什么东东?

@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(); } 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); } }

这里把官方的例子搬了过来,但是在创建的时候,一直编译未通过,也找不到啥原因,好想哭的感觉。这个东东好坑爹,先放这里,知道有这个功能就好了。

编译时,Map的Key未指定

在编译时,注入的Map的Key是指定的,也就是已知的,同时在注解中标明,此时Map的多元素绑定才能正常执行。如果Map的Key不满足这些约束条件,那么将无法创建一个多对多的Map。但是,可以通过设置多个绑定来绑定一组对象,然后将其转换为一个非多对多的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; } }

值得注意的是,这种情况下,Dagger并不会自动注入Map<Foo,Provider<Bar>>,也就意味着不会提供一个含有Provider值得Map。如果想要获取一个含有Provider的Map,需要在Map.Entry对象中包含Provider值,那么所获取的Map也就含有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 ...; } }

声明多重绑定

可以声明一个多重绑定的Set或者Map,其方法是在Module中,添加一个由@Meeibinds注解的抽象方法,该方法的返回值为声明的Set或者Map.

对于注入的Set或者Map不一定必须使用@Meeibinds注解,可以使用@IntoSet,@ElementsIntoSet或@IntoMap等任何一种方式注解。如果它们为空,必须声明至少一个。

@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方法。

替代方法:@ElementsIntoSet返回一个空集

仅对于空Set,作为替代,您可以添加返回空集合的@ElementsIntoSet方法

@Module class MyEmptySetModule { @Provides @ElementsIntoSet static Set<Foo> primeEmptyFooSet() { return Collections.emptySet(); } }
转载请注明原文地址: https://www.6miu.com/read-4031.html

最新回复(0)