前面文章中讲到Componnet继承和依赖的三种方式时说到了在父Componnet中Module中指定SubComponent,这种方式主要用来实现Activity-Multibindings,现在就来了解下什么是Activity-Multibindings
先说说什么是Multibindings
简单的说就是将多个对象放到一个集合中,让依赖需求方可以通过一个集合找到需要的依赖而不用绑定具体的依赖
Set multibindings
在@Module中使用 @IntoSet或者 @ElementsIntoSet注解
@Module
class MyModuleA {
//提供一个元素
@Provides @IntoSet
static String provideOneString(DepA depA, DepB depB) {
return "ABC";
}
}
@Module
class MyModuleB {
//一次提供多个元素
@Provides @ElementsIntoSet
static Set provideSomeStrings(DepA depA, DepB depB) {
return new HashSet(Arrays.asList("DEF", "GHI"));
}
}
//dagger可以提供一个Set
class Bar {
@Inject Bar(Set strings) {
assert strings.contains("ABC");
assert strings.contains("DEF");
assert strings.contains("GHI");
}
}
//或者从Componnet中获取Set
@Component(modules = {MyModuleA.class, MyModuleB.class})
interface MyComponent {
Set strings();
}
@Test void testMyComponent() {
MyComponent myComponent = DaggerMyComponent.create();
assertThat(myComponent.strings()).containsExactly("ABC", "DEF", "GHI");
}
Map multibindings
只介绍最简单的方式,里面用到@IntoMap(标记依赖是要添加到Map中)和@MapKey(定义Map的key)
@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 longsByString();
Map, 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");
}
还有其他复杂点的用法
- 使用enum作为MapKey
- 使用复杂的MapKey
- 将set转换为map的用法
- SubComponent将元素绑定到父Componnet中的集合中
具体见官方文档multibindings
Activities Subcomponents Multibinding
之前注入的过程基本是这样
getAppCompoent().getActivityComponent2().inject(this);
DaggerActivityComponent1.builder()
.appComponent(getAppCompoent())
.build().inject(this);
都需要获取到父Componnet的实例才能完成注入,也就是被注入的Activity需要了解注射器是如何组织的,这不符合依赖注入的核心原则:一个类不应该知道如何实现依赖注入
使用Activities Subcomponents Multibinding可以解决上述问题
先看看如何使用
step1
先定义一个ActivityInjector接口,可以用来完成对Activity的注入
public interface ActivityInjector extends MembersInjector {
}
step2
定义一个ActivityModule,用来传入一个activity实例并作为依赖提供
@Module
public abstract class ActivityModule {
protected final T activity;
public ActivityModule(T activity) {
this.activity = activity;
}
@Provides
@ActivityScope
public T provideActivity() {
return activity;
}
}
step3
定义通用的ActivityComponentBuilder接口,用来传入ActivityModule并构建ActivityInjector实例
public interface ActivityComponentBuilder {
ActivityComponentBuilder activityModule(M activityModule);
C build();
}
step4
定义注入Activity的SubComponent
@ActivityScope
@Subcomponent(modules = ActivityComponent3.SubcomponentActivityModule.class)
public interface ActivityComponent3 extends ActivityInjector {
@Subcomponent.Builder
interface Builder extends ActivityComponentBuilder {
}
@Module
class SubcomponentActivityModule extends ActivityModule {
public SubcomponentActivityModule(SubComponentActivity3 activity) {
super(activity);
}
}
}
step5
定义一个module,将ActivityComponentBuilder作为一个元素添加到Map中,使用Activity的类型作为MapKey
@Module(subcomponents = ActivityComponent3.class)
public abstract class ActivitisMultibindingModule {
@Binds
@IntoMap
@ActivityKey(SubComponentActivity3.class)
public abstract ActivityComponentBuilder
bindSubComponentActivityBuilder(ActivityComponent3.Builder builder);
}
step6
定义HasActivityComponentBuilder接口,传入MapKey获取ActivityComponentBuilder,这里的MapKey是Activity的Class类型
public interface HasActivityComponentBuilder {
ActivityComponentBuilder getActivityComponentBuilder(Class extends Activity> activityClass);
}
step7
Application中实现HasActivityComponentBuilder接口,从注入到Application中的Map
以MapKey获取ActivityComponentBuilder
public class RealApplication extends BaseApplication implements HasActivityInjector, HasActivityComponentBuilder {
private static final String TAG = "RealApplication";
@Inject
Map, Provider> activityComponentBuilders;
//只是为了拿到Application
public static HasActivityComponentBuilder get(Context context) {
return ((HasActivityComponentBuilder) context.getApplicationContext());
}
//用activityClass去找对应的ActivityComponentBuilder
@Override
public ActivityComponentBuilder getActivityComponentBuilder(Class extends Activity> activityClass) {
return activityComponentBuilders.get(activityClass).get();
}
}
step8
在Activity中完成注入
private void injectMembers() {
RealApplication.get(this).getActivityComponentBuilder(SubComponentActivity3.class)
.activityModule(new ActivityComponent3.SubcomponentActivityModule(this))
.build()
.injectMembers(this);
}
个人理解的Activities Subcomponents Multibinding
实质上是将Activity的Subcomponnet/Subcomponent.Builder作为Map的value,使用时不从最底层的Component一级级的构建Component,而是用自身的Class作为key来获取Subcomponnet/Subcomponent.Builder,最终完成注入
优点
可以看到在最后一步的注入过程中,Activity只是获取Application-传入自身作为Module的参数,完成注入;整个过程都没有Componnet的身影,这就是Activities Subcomponents Multibinding的好处了
而且以上步骤不仅适用于Activity,对Fragment,View,ViewModel等都可以用同样的套路去屏蔽上层对如何完成注入的了解
缺点
当然有得有舍
- 整个实现过程多了很多步骤
- ActivityComponentBuilder是一个接口,导致只能将当前Activity实例作为参数,想要扩展其他参数作为依赖就没有办法了
对于第一个缺点,仅仅为了不去了解依赖注入框架的结构就多写那么多代码,有些得不偿失,不过所幸这些都是套路,满眼都是模板代码,所以google推出了dagger.android来简化android中四大组件以及Fragment中的Subcomponents Multibinding
下一章就来介绍dagger.android
参考资料
[Android]在Dagger 2中Activities和Subcomponents的多绑定(翻译)
相关文章
dagger2从入门到放弃-概念
dagger2从入门到放弃-最基础的用法介绍
dagger2从入门到放弃-Component的继承体系、局部单例
dagger2从入门到放弃-ActivityMultibindings
dagger2从入门到放弃-dagger.android
dagger2从入门到放弃-其他用法
dagger2从入门到放弃-多模块项目下dagger的使用
dagger2从入门到放弃-为何放弃
示例代码
DaggerInAction
欢迎star
master分支上最新的代码可能会比当前文章的示例代码稍微复杂点,提交记录里包含了每一步的迭代过程,可以顺藤摸瓜