Dagger 2从浅到深
Posted 行云间
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dagger 2从浅到深相关的知识,希望对你有一定的参考价值。
Dagger系列:
- Dagger 2从浅到深(一)
- Dagger 2从浅到深(二)
- Dagger 2从浅到深(三)
- Dagger 2从浅到深(四)
- Dagger 2从浅到深(五)
- Dagger 2从浅到深(六)
- Dagger 2从浅到深(七)
- Dagger 2应用于Android的完美扩展库-dagger.android
Demo地址:
之前用到的,都是基于单个实例的依赖注入。强大的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中的提供了一套标准的注解:
- ClassKey
- IntKey
- LongKey
- StringKey
我们先以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中,明确指定了在注释类型中声明的方法的返回类型,如果不满足指定的返回类型,那么编译时会报错:
- 基本数据类型
- String
- Class
- 枚举类型
- 注解类型
- 以上数据类型的数组
如果注入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();
}
}
以上是关于Dagger 2从浅到深的主要内容,如果未能解决你的问题,请参考以下文章