美文网首页
基于Dagger2.38.1版本全面理解注解-前言和部分注解

基于Dagger2.38.1版本全面理解注解-前言和部分注解

作者: 我爱田Hebe | 来源:发表于2022-08-23 10:09 被阅读0次

    前言

    这里主要针对@MapKey、@Scope、@Qualifier注解还有@IntoSet,@IntoMap和@ElementsIntoSet的理解,因为这几个注解是贯穿始终的,和其他注解或多或少存在关联,如果不提前讲解,后面关联上无法更好的理解。

    @MapKey、@Scope、@Qualifier三个注解仅仅可用于修饰注解。MapKey是Dagger2自定义的,Scope和Qualifier是javax.inject下的注解。

    这里包括后面的章节中注解的先后顺序都是经过我精挑细选的,务必保证后面说到的注解关联的注解先被说到。

    那个啥,为什么还是那么...咳咳,如同作者的发型那么柔顺!!!

    @Scope

    我们直译过来表示范围,这个注解是用于修饰注解的注解。

    @Scope修饰的注解具体使用规则

    1. 节点使用@Scope修饰的注解最多只允许出现一个;

    2. 使用@Module或@ProducerModule修饰的module节点不允许使用Scope修饰的注解修饰;

    3. 绑定方法bindingMethod节点(module节点中的方法,使用@Provides 、@Produces 、@Binds 、@Multibinds、@BindsOptionalOf修饰)。其中@Provides和@Binds修饰的方法允许同时使用@Scope修饰的注解修饰,@Produces、@Multibinds和@BindsOptionalOf修饰的方法不允许同时使用@Scope修饰的注解修饰;

    4. @Inject或@AssistedInject修饰的构造函数不允许使用同时使用@Scope修饰的注解修饰;并且@AssistedInject修饰的构造函数所在的父级节点不允许使用使用@Scope修饰的注解修饰,但是使用@Inject修饰的构造函数所在父级节点允许使用@Scope修饰的注解修饰;

    5. @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(){}
    } 
    

    已上面的实例解说如下:

    1. 当前@Singleton和@Provides修饰的bindingMethod方法返回的A的实例化既可以在当前Component作用域中使用,也可以在Subcomponent1,Subcomponent2,Subcomponent3和SubcomponentX(当然了,仅限于Subcompoonent中的实例化需要的A实例化对象的情况下)使用;

    2. 当前@Singleton修饰的C类(因为MembersInjector类是待实例化的对象,所以对象中@Inject修饰的变量类型(或普通方法返回类型))实例化既可以在当前Component作用域中使用也可以在Subcomponent1,Subcomponent2,Subcomponent3和SubcomponentX中使用;

    3. 那如果在Subcomponent1中定义了@Singleton修饰的节点,那么该节点的实例对象是不能再父级fathercomponent中使用的。

    为什么有好几种@Scope修饰的注解,有什么不同?

    Dagger当前2.38.1版本中源码中有几个Scope修饰的注解,@Reusable、@PerGeneratedFile、@PerComponentImplementation、@ProductionScope;还有javax.inject包下有一个@Singleton;

    1. @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双重校验(单例 + 校验必须同一实例),其实对我们来说好像并没有多大影响;

    1. @Reusable其他情况和@Singleton用法都是在作用域中DoubleCheck双重校验;

    2. @PerGeneratedFile表示将当前修饰的节点及其往下关联的节点和当前所属的currentcomponent生成的代码写在同一个文件中,没有实际意义;

    3. @PerComponentImplementation:Dagger内部处理代码,相当于一个A类,但是这个A可能包含自己,那么我们将另外开辟一个用于生成子类A的实例,需要用到@PerComponentImplementation;

    4. @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修饰的注解使用规则如下

    1. 节点上只允许出现一个@Qualifier修饰注解的注解;

    2. componentMethod方法只有在返回类型是subcomponent节点或subcomponent.creator节点情况下才允许使用Qualifier修饰的注解修饰;

    3. bindingMethod方法可以被Qualifier修饰的注解修饰;

    4. 被Inject或AssistedInject修饰的构造方法不允许使用Qualifier修饰的注解修饰,但是构造函数的参数可以;

    5. @Assisted修饰的参数节点不能被Qualifier注解修饰的注解修饰;

    6. MembersInjector的成员注入T类型不允许使用Qualifier修饰的注解修饰;

    7. 一个节点允许使用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用于修饰方法,规则如下**:

    1. 不允许同时使用,一个方法上仅仅可以使用前@IntoSet、@IntoMap和@ElementsIntoSet中的一个;

    2. 必须在bindingMethod绑定方法上使用,并且只支持@Provides、@Produces或@Binds三种类型的bindingMethod方法;

    3. 如果使用了@ElementsIntoSet,那么当前bindingMethod方法返回类型必须是Set< T>;

    4. @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修饰的注解,满足如下规则:

    1. 如果MapKey.unwrapValue() = true的情况下(默认情况下就是true),被修饰的注解有且仅有一个方法,并且该方法返回类型不能是数组类型;
    • e.g.ClassKey注解
    1. 如果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

    1. @MapKey和@IntoMap是情侣关系,必须在一起!!!

    相关文章

      网友评论

          本文标题:基于Dagger2.38.1版本全面理解注解-前言和部分注解

          本文链接:https://www.haomeiwen.com/subject/vmzwgrtx.html