前言
这里主要针对@MapKey、@Scope、@Qualifier注解还有@IntoSet,@IntoMap和@ElementsIntoSet的理解,因为这几个注解是贯穿始终的,和其他注解或多或少存在关联,如果不提前讲解,后面关联上无法更好的理解。
@MapKey、@Scope、@Qualifier三个注解仅仅可用于修饰注解。MapKey是Dagger2自定义的,Scope和Qualifier是javax.inject下的注解。
这里包括后面的章节中注解的先后顺序都是经过我精挑细选的,务必保证后面说到的注解关联的注解先被说到。
那个啥,为什么还是那么...咳咳,如同作者的发型那么柔顺!!!
@Scope
我们直译过来表示范围,这个注解是用于修饰注解的注解。
@Scope修饰的注解具体使用规则:
-
节点使用@Scope修饰的注解最多只允许出现一个;
-
使用@Module或@ProducerModule修饰的module节点不允许使用Scope修饰的注解修饰;
-
绑定方法bindingMethod节点(module节点中的方法,使用@Provides 、@Produces 、@Binds 、@Multibinds、@BindsOptionalOf修饰)。其中@Provides和@Binds修饰的方法允许同时使用@Scope修饰的注解修饰,@Produces、@Multibinds和@BindsOptionalOf修饰的方法不允许同时使用@Scope修饰的注解修饰;
-
@Inject或@AssistedInject修饰的构造函数不允许使用同时使用@Scope修饰的注解修饰;并且@AssistedInject修饰的构造函数所在的父级节点不允许使用使用@Scope修饰的注解修饰,但是使用@Inject修饰的构造函数所在父级节点允许使用@Scope修饰的注解修饰;
-
@BindsInstance修饰的方法或方法参数不允许同时使用@Scope修饰;
Scope表示一个作用域,表示当前Scope修饰的注解修饰的节点所属的currentcomponent节点及其往下的subcomponent节点都可以使用该节点,我们以@Singleton为例:
Module节点,里面包含Subcomponent1,Subcomponent2,Subcomponent3几个subcomponent节点,Component节点中关联Module节点,Component节点的入口方法inject的作用是实例化MembersInjector类:
@Module(subcomponents = Subcomponent1.class,Subcomponent2.class,Subcomponent3.class)
public class Module{
@Provides
@Singleton
public A bindingMethod(B b){
return new A(b);
}
}
@Component(modules = Module.class)
public calss Component{
inject(MembersInjector m);
public SubcomponentX getSubcomponentX();//这个也是一个Subcomponent节点
}
public class MembersInjector{
@Inject
public C C;
}
@Singleton
public class C {
@Inject
public C(){}
}
已上面的实例解说如下:
-
当前@Singleton和@Provides修饰的bindingMethod方法返回的A的实例化既可以在当前Component作用域中使用,也可以在Subcomponent1,Subcomponent2,Subcomponent3和SubcomponentX(当然了,仅限于Subcompoonent中的实例化需要的A实例化对象的情况下)使用;
-
当前@Singleton修饰的C类(因为MembersInjector类是待实例化的对象,所以对象中@Inject修饰的变量类型(或普通方法返回类型))实例化既可以在当前Component作用域中使用也可以在Subcomponent1,Subcomponent2,Subcomponent3和SubcomponentX中使用;
-
那如果在Subcomponent1中定义了@Singleton修饰的节点,那么该节点的实例对象是不能再父级fathercomponent中使用的。
为什么有好几种@Scope修饰的注解,有什么不同?
Dagger当前2.38.1版本中源码中有几个Scope修饰的注解,@Reusable、@PerGeneratedFile、@PerComponentImplementation、@ProductionScope;还有javax.inject包下有一个@Singleton;
- @Reusable:@Reusable和@Binds同时修饰一个module节点中的bindingMethod方法,那么当前bindingMethod方法参数类型A节点如果没有使用@Scope修饰的注解,那么直接使用SingleCheck;如果A节点使用了@Scope修饰的注解,那么使用DoubleCheck;
-
SingleCheck和DoubleCheck的区别,SingleCheck单例获取A对象;DoubleCheck不仅仅获取A对象,并且校验当前A对象的实例化始终都是同一个实例;
@Binds @Reusable // to avoid parsing options more than once CompilerOptions bindCompilerOptions( ProcessingEnvironmentCompilerOptions processingEnvironmentCompilerOptions );
-
如上代码案例,ProcessingEnvironmentCompilerOptions如果没有使用@Scope修饰的注解修饰,那么是SingleCheck单例,否则是DoubleCheck双重校验(单例 + 校验必须同一实例),其实对我们来说好像并没有多大影响;
-
@Reusable其他情况和@Singleton用法都是在作用域中DoubleCheck双重校验;
-
@PerGeneratedFile表示将当前修饰的节点及其往下关联的节点和当前所属的currentcomponent生成的代码写在同一个文件中,没有实际意义;
-
@PerComponentImplementation:Dagger内部处理代码,相当于一个A类,但是这个A可能包含自己,那么我们将另外开辟一个用于生成子类A的实例,需要用到@PerComponentImplementation;
-
@ProductionScope:如果使用了@ProductionComponent或@ProductionSubcomponent修饰的component节点,那么Dagger为我们生成一个@Module修饰的module节点,该module节点中有monitor方法:
@Provides @ProductionScope static ProductionComponentMonitor monitor(Provider<component节点类型> component,Provider<Set<ProductionComponentMonitor.Factory>> factories){ return Monitors.createMonitorForComponent(component, factories); }
- ProductionScope实际作用还是当前component的作用域,这里使用monitor方法的目的在于整个@ProductionComponent或@ProductionSubcomponent修饰的component节点的监控。
@Qualifier
直译过来是预选的意思。@Qualifier修饰的注解使用规则如下:
-
节点上只允许出现一个@Qualifier修饰注解的注解;
-
componentMethod方法只有在返回类型是subcomponent节点或subcomponent.creator节点情况下才允许使用Qualifier修饰的注解修饰;
-
bindingMethod方法可以被Qualifier修饰的注解修饰;
-
被Inject或AssistedInject修饰的构造方法不允许使用Qualifier修饰的注解修饰,但是构造函数的参数可以;
-
@Assisted修饰的参数节点不能被Qualifier注解修饰的注解修饰;
-
MembersInjector的成员注入T类型不允许使用Qualifier修饰的注解修饰;
-
一个节点允许使用Qualifier修饰,那么当前节点类型(如果是方法,则表示方法返回类型)的构造函数允许使用AssistedInject修饰。
@Qualifier修饰的注解主要是为了实例化过程中成功匹配到正确的参数,我们以案例说话:
@Retention(RUNTIME)
@Qualifier
public @interface ProcessingOptions {}
public final class ExternalBindingGraphPlugins {
@Inject
ExternalBindingGraphPlugins(...,
@ProcessingOptions Map<String, String> processingOptions) {
}
}
@Module
interface ProcessingEnvironmentModule{
@Provides
@ProcessingOptions
static Map<String, String> processingOptions(XProcessingEnv xProcessingEnv) {
return xProcessingEnv.getOptions();
}
@Provides
static Map<String, String> getMap(String str) {
return new Map<String,String>();
}
}
如上图所示,ExternalBindingGraphPlugins对象的实例化需要 Map<String, String>
对象,这里有两个Map<String,String>,那么需要靠@ProcessingOptions精确匹配。
- 在容易产生类型混淆的情况下会用到@Qualifier修饰的注解,这个也非常容易理解。
@IntoSet、@IntoMap和@ElementsIntoSet
** @IntoSet、@IntoMap和@ElementsIntoSet用于修饰方法,规则如下**:
-
不允许同时使用,一个方法上仅仅可以使用前@IntoSet、@IntoMap和@ElementsIntoSet中的一个;
-
必须在bindingMethod绑定方法上使用,并且只支持@Provides、@Produces或@Binds三种类型的bindingMethod方法;
-
如果使用了@ElementsIntoSet,那么当前bindingMethod方法返回类型必须是Set< T>;
-
@IntoMap 和@MapKey修饰的注解一定是成对出现的;
@IntoSet
收集同一种类型对象,下面给个案例了解下:
@Module
public interface BindingMethodValidatorsModule {
//下面的都是该方法参数
@Provides
static ImmutableMap<ClassName, BindingMethodValidator> indexValidators(
Set<BindingMethodValidator> validators) {
return uniqueIndex(validators, BindingMethodValidator::methodAnnotation);
}
@Binds
@IntoSet
BindingMethodValidator provides(ProvidesMethodValidator validator);
@Binds
@IntoSet
BindingMethodValidator produces(ProducesMethodValidator validator);
@Binds
@IntoSet
BindingMethodValidator binds(BindsMethodValidator validator);
@Binds
@IntoSet
BindingMethodValidator multibinds(MultibindsMethodValidator validator);
@Binds
@IntoSet
BindingMethodValidator bindsOptionalOf(BindsOptionalOfMethodValidator validator);
}
- @Binds修饰的bindingMethod方法有且仅有一个参数,并且参数类型一定是方法返回类型或返回类型子类。
如上所示,Set< BindingMethodValidator> validators
用于收集所有的BindingMethodValidator
类型对象;
延伸:这里的@Binds当然可以改成@Provides,e.g. :
@Provides
@IntoSet
static BindingMethodValidator provides(XX xx){
return new ProvidesMethodValidator(xx);
}
@ElementsIntoSet
和@IntoSet用法基本一致, @ElementsIntoSet返回类型必须是Set< T>,如上我们可以改成使用@ElementsIntoSet修饰:
@Module
public interface BindingMethodValidatorsModule {
//下面的都是该方法参数
@Provides
static ImmutableMap<ClassName, BindingMethodValidator> indexValidators(
Set<BindingMethodValidator> validators) {
return uniqueIndex(validators, BindingMethodValidator::methodAnnotation);
}
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> provides(ProvidesMethodValidator validator);
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> produces(ProducesMethodValidator validator);
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> binds(BindsMethodValidator validator);
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> multibinds(MultibindsMethodValidator validator);
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> bindsOptionalOf(BindsOptionalOfMethodValidator validator);
}
- 还有一个,@ElementsIntoSet可以返回空绑定,但是@IntoSet不会。基本@IntoSet用的比较多,@ElementsIntoSet感觉像是一个附带产品。
@IntoMap
@IntoMap当然也是收集同一类对象,但是@IntoMap和@MapKey修饰的注解是成双成对出现的。why?@IntoMap是为了将当前类型T收集到Map<K,T>中,那么必须由@MapKey修饰的注解提供K。
再来举个例子:
AndroidInjectionModule类:
@Beta
@Module
public abstract class AndroidInjectionModule {
@Multibinds
abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
activityInjectorFactories();
...
}
MainActivityModule_ContributeMainActivity类:
@Module(subcomponents = MainActivityModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class MainActivityModule_ContributeMainActivity {
private MainActivityModule_ContributeMainActivity() {}
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
MainActivitySubcomponent.Builder builder);
@Subcomponent(modules = FragmentBuildersModule.class)
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
}
多提一句,这里的MainActivityModule_ContributeMainActivity是通过如下代码生成出来的:
@Suppress("unused")
@Module
abstract class MainActivityModule {
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeMainActivity(): MainActivity
}
AndroidInjectionModule类的activityInjectorFactories方法用于收集用于收集Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
,MainActivityModule_ContributeMainActivity类中的bindAndroidInjectorFactory方法@ActivityKey(MainActivity.class)
提供K:MainActivity.class
,V:MainActivitySubcomponent.Builder
,符合被收集的条件。
@MapKey
当前使用@Mapkey修饰的注解,满足如下规则:
- 如果MapKey.unwrapValue() = true的情况下(默认情况下就是true),被修饰的注解有且仅有一个方法,并且该方法返回类型不能是数组类型;
- e.g.ClassKey注解
- 如果MapKey.unwrapValue() = false,那就麻烦了,被修饰的注解方法不但不限制,而且我们的项目中必须引入了
com.google.auto.value.AutoAnnotation
依赖,这是什么骚操作???
-
(1)引入AutoAnnotation的目的是对MapKey修饰的注解的所有方法返回类型作为变量生成一个新的类T,相当于MapKey.unwrapValue() = true的情况下的
@MapKey(T.class)
; -
(2)所以
MapKey.unwrapValue() = false
可以使用MapKey.unwrapValue() = true
替代,而且MapKey.unwrapValue() = true
更容易理解、实现也更加稳健。非必要不要使用MapKey.unwrapValue() = false
;
- @MapKey和@IntoMap是情侣关系,必须在一起!!!
网友评论