有关Dagger2的一些事(一)

作者: LSteven | 来源:发表于2018-03-23 12:59 被阅读73次

    这是为Dagger2的详细分析第一篇,以做记录。
    此篇介绍以下内容:

    • @Inject
    • @Component
    • @Module
    • @Scope
    • @SubComponent
    • denpendency
    • @Lazy
    • @Binds
    • @IntoSet
    • @IntoMap

    此篇不会从头开始介绍dagger2,假设读者已经对dagger2有一定的了解哈

    基本用法--没有Module

    1. 构造函数上标注@Inject
    public class A {
        @Inject
        public A() {}
    
    }
    
    1. 定义Component作为连接器
    @Component
    public interface ActivityComponent {
         void inject(MainActivity MainActivity);
    }
    
    1. 要进行注入的Activity
    public class MainActivity extends Activity implements LifecycleOwner
    {
        
        @Inject
        A a;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            ...
            DaggerActivityComponent.builder().build().inject(this);
        }
    }
    
    
    1.png

    我们详细看生成了什么:
    对于ActivityComponent,dagger会帮你生成DaggerxxxComponent
    对于MainActivity,dagger会帮你生成xxxMemberInjector

    public final class DaggerActivityComponent implements ActivityComponent {
      private MembersInjector<MainActivity> mainActivityMembersInjector;
    
      private DaggerActivityComponent(Builder builder) {
        assert builder != null;
        initialize(builder);
      }
    
      public static Builder builder() {
        return new Builder();
      }
    
      public static ActivityComponent create() {
        return builder().build();
      }
    
      @SuppressWarnings("unchecked")
      private void initialize(final Builder builder) {
    
        this.mainActivityMembersInjector = MainActivity_MembersInjector.create(A_Factory.create());
      }
    
      @Override
      public void inject(MainActivity MainActivity) {
        mainActivityMembersInjector.injectMembers(MainActivity);
      }
    
      public static final class Builder {
        private Builder() {}
    
        public ActivityComponent build() {
          return new DaggerActivityComponent(this);
        }
      }
    }
    

    两行关键代码:

    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(A_Factory.create());
    mainActivityMembersInjector.injectMembers(MainActivity);
    

    Factory是真正提供数据的工厂,Injector包含Factory,所以Injector可以向变量注入实例。

    @Module

    1. 不在构造函数上标注@Inject,而是新生成一个Module
    @Module
    public class ActivityModule {
    
        @Provides
        public A provideA(){
            return new A();
        }
    }
    

    这是什么意思呢,就是需要实例时不会再从构造函数中去获取,而是通过providexxx获取。

    1. 定义Component作为连接器,注意注解参数与前面的区别
    @Component(modules = ActivityModule.class)
    public interface ActivityComponent {
         void inject(MainActivity MainActivity);
    }
    

    MainActivity中:

    // 构造桥梁对象
    DaggerActivityComponent.builder()
            .activityModule(new ActivityModule())
            .build()
            .inject(this);
    

    注意这里.activityModule(new xxx)不写也没事,dagger会自动帮我们构建,当然如果ActivityModule的构造函数需要传递参数就需要我们手动写了。

    2.png

    注入过程

    • 查找Module中是否存在创建该类的方法。
    • 若存在创建类方法,查看该方法是否存在参数
      • 若存在参数,则按从步骤1开始依次初始化每个参数
      • 若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
    • 若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
      • 若存在参数,则从步骤1开始依次初始化每个参数
      • 若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

    @Scope

    一个很具有疑惑性的注解。

    1. 创建注解
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UserScope {
    }
    
    1. 在Module或构造函数上标注
    @Module  
    public class UserModule {  
        ...
        @Provides  
        @UserScope
        User providesUser() {  
            return new A();  
        }  
      
    }  
    

    or

    @UserScope 
    public class User {  
        @Inject  
        public User() {  
        } 
    }  
    
    1. 在使用该作用于的Component上标注
    @UserScope
    @Component(modules = {UserModule.class})
    public interface UserComponent {
       ...
    }
    

    先说一下这个注解到底是什么意思。经常看到什么@Singleton|@PerActivity|PerFragment。那些背后都是靠一个@Scope完成的。那这个作用域是由谁控制的, 其实就是靠Component控制的。
    即:Component活多久,这个注解生成的实例就活多久。

    我们以常见的PerActivity为例,为什么它能做到跟Activity一个生命周期,就是因为你每新生成一个Activity,你的DaggerxxxComponent都会由你重新.build生成。
    假设我们在Application中生成一个component,并且在每个Activity里通过getApplication().getComponent()去获取Component,那么不好意思,你的实例就和Application同生死了。

    所以这个是怎么做到的,很简单,背后利用了DoubleCheck

    DoubleCheck:

    Provider外面包一层DoubleCheck,每次get时看当前Component下有没有已经生成的实例,有的话就直接返回。

    
    
      @SuppressWarnings("unchecked") // cast only happens when result comes from the provider
      @Override
      public T get() {
        Object result = instance;
        if (result == UNINITIALIZED) {
          synchronized (this) {
            result = instance;
            if (result == UNINITIALIZED) {
              result = provider.get();
              /* Get the current instance and test to see if the call to provider.get() has resulted
               * in a recursive call.  If it returns the same instance, we'll allow it, but if the
               * instances differ, throw. */
              Object currentInstance = instance;
              if (currentInstance != UNINITIALIZED && currentInstance != result) {
                throw new IllegalStateException("Scoped provider was invoked recursively returning "
                    + "different results: " + currentInstance + " & " + result);
              }
              instance = result;
              /* Null out the reference to the provider. We are never going to need it again, so we
               * can make it eligible for GC. */
              provider = null;
            }
          }
        }
        return (T) result;
      }
    

    很简单就实现了作用域的控制。我的理解就是这么简单。

    dependency

    @Component(modules = ActivityModule.class)
    public interface AComponent {
         A provideA();
    }
    
    @Component(dependencies = AComponent.class)
    public interface BComponent {
    
        void inject(MainActivity MainActivity);
      
    }
    
    DaggerBComponent.builder().activityComponent(DaggerAComponent.builder().activityModule(new ActivityModule()).build()).build().inject(this);
    

    先看上面代码,有两个ComponentB是我们常见的写法,但是它@Component(dependencies = ActivityComponent.class)依赖于A。

    A只暴露了一个接口A provideA();

    所以最终实现什么效果呢,B可以使用A提供的provideA去注入A的实例。

    4.png
    • 父Component中要显式的写出需要暴露可提供给子Component的依赖;

    • 子Component在注解中使用dependencies=来连接父Component;

    @SubComponent

    
    @Component(modules = ActivityModule.class)
    public interface ActivityComponent {
         AnotherComponent anotherComponent();
    }
    
    @Subcomponent
    public interface AnotherComponent {
    
        void inject(MainActivity MainActivity);
    
    }
    
    DaggerActivityComponent.builder().build().anotherComponent(DaggerAnotherComponent.builder().build()).inject(this);
    
    

    我们会看到,它跟denpendency不同,后者会生成两个DaggerxxxComponent,但它只会生成一个。

    
    
    public final class DaggerActivityComponent implements ActivityComponent {
      ...
    
    
      @SuppressWarnings("unchecked")
      private void initialize(final Builder builder) {
    
        this.provideAProvider = ActivityModule_ProvideAFactory.create(builder.activityModule);
      }
    
      @Override
      public AnotherComponent anotherComponent() {
        return new AnotherComponentImpl();
      }
    
      public static final class Builder {
        private ActivityModule activityModule;
    
        private Builder() {}
    
        public ActivityComponent build() {
          if (activityModule == null) {
            this.activityModule = new ActivityModule();
          }
          return new DaggerActivityComponent(this);
        }
    
        public Builder activityModule(ActivityModule activityModule) {
          this.activityModule = Preconditions.checkNotNull(activityModule);
          return this;
        }
      }
    
      private final class AnotherComponentImpl implements AnotherComponent {
        private MembersInjector<MainActivity> mainActivityMembersInjector;
    
        private AnotherComponentImpl() {
          initialize();
        }
    
        @SuppressWarnings("unchecked")
        private void initialize() {
    
          this.mainActivityMembersInjector =
              MainActivity_MembersInjector.create(DaggerActivityComponent.this.provideAProvider);
        }
    
        @Override
        public void inject(MainActivity MainActivity) {
          mainActivityMembersInjector.injectMembers(MainActivity);
        }
      }
    }
    
    

    先说一个事实,一个Component要不只能是@Component,要不只能是@SubComponent

    然后看上面代码,最终是由标注了@SubComponent的AnotherComponent去进行了真正的注入。这里@SubComponent可以继承它的父类提供的所有实例。我试了一下,如果只有AnotherComponent存在是无法完成注入的,也就是说他是不独立的,它必须依附于父类。

    所以dependencysubComponent的区别:

    Component Dependencies - Use this when:

    you want to keep two components independent.
    you want to explicitly show what dependencies from one component is used by the other

    Subcomponents - Use this when:
    you want to keep two component cohesive
    you may not care to explicitly show what dependencies from one component is used by the other

    前者更独立,组件只是提供provide给另一个借用一下。

    同时注意:

    两个拥有依赖关系的 Component 是不能有相同 @Scope 注解的!使用@SubComponent 则可以使用相同的@Scope注解。 (很重要!!!!)

    @Lazy & @Provider

    Activity中,我们可以这么声明变量。

    @Inject
    Lazy<A> a;
    Provider<A> b;
    

    只有真正去aLazy.get() 才会进行注入。

    实现也很简单,继续DoubleCheck包裹Provider,然后真正get的时候才会从Factory中获取。不过他跟前面的scope不大一样,是在对instance实例赋值的时候进行包裹的。

    其中Lazy(懒加载)的作用好比component初始化了一个present对象,然后放到一个池子里,需要的时候就get它,所以你每次get的时候拿到的对象都是同一个;并且当你第一次去get时,它才会去初始化这个实例.

      @Override
      public void injectMembers(MainActivity instance) {
        if (instance == null) {
          throw new NullPointerException("Cannot inject members into a null reference");
        }
        instance.a = DoubleCheck.lazy(bAndAProvider);
        instance.b = bAndAProvider;
      }
    

    而对于b直接赋予provider的值,后面要get时就从provider.get获取。

    procider(强制加载)的作用:

    1:同上当你第一次去get时,它才会去初始化这个实例

    2:后面当你去get这个实例时,依旧是从Provider中获取,所以跟基本用法是一样的。

    所以Provider与Lazy的区别在于,Lazy延迟加载之后每次的调用都是同一个对象,而Provider只是延迟加载,至于调用的对象是不是同一个就要看@scope有没有进行约束了。

    @Binds

    现在我们假设有个接口叫IinterfaceA

    UserAUserB分别实现了这个接口。

    然后我们在Activity里这么写:

     @Inject
     IinterfaceA a;
    

    我们希望实现IinterfaceA a = new UserA()

    我们不能直接在UserA的构造函数上标注@Inject因为我们要注入的是接口不是实现。所以按已有的知识体系那就用Module咯:

      @Provides
      public IinterfaceA providesA(){
        return new UserA();
      }
    

    而如果用@Binds这个新的特性,我们可以这么写。注意abstract

    @Module
    public abstract class AModule {
    
      @Binds
      public abstract IinterfaceA bindA(UserA userA);
    }
    
    

    这个意思是如果你想要注入到IinterfaceA,请找UserA

    其实这两货看起来差不多,那为什么要出现@Binds。后来看有个解释挺不错:

    @Provides methods are instance methods and they need an instance of our module in order to be invoked. If our Module is abstract and contains @Binds methods, dagger will not instantiate our module and instead directly use the Provider of our injected parameter (LoginPresenter in the above case).

    对于Provides而言,Module是会被实例化的,因为要去创建UserA。但是对于@Binds而言,不需要实例化Module就可以做到。

    所以@Binds解决了我们面向接口编程的需求。

    我的理解,@Binds是完全可以被替代的,就看你自己的需求了。

    @IntoSet & @ElementsIntoSet

    如果我们在Activity里:

    
    @Inject
    Set<String> a
    
    

    这个可以通过@IntoSet & @ElementsIntoSet实现实例一一注入到集合中,看个例子

    
     @Provides @IntoSet
     String providexxx() {
        return "ABC";
     }
    
     @Provides @IntoSet
     String providekkk() {
        return "Acc";
     }
     
     @Provides @ElementsIntoSet
     Set<String> provideSomeStrings() {
        return new HashSet<String>(Arrays.asList("DEF", "GHI"));
     }
        
    

    这样a就包含<"ABC","Acc","DEF","GHI">

    其实到这里基本的套路都差不多,直接上编译出来的源码:

       //(int individualProviderSize, int collectionProviderSize)
    
       this.setOfStringProvider =
            SetFactory.<String>builder(2, 1)
                .addProvider(providexxxProvider)
                .addProvider(providekkkProvider)
                .addCollectionProvider(provideSomeStringsProvider)
                .build();
    
    
      @Override
      public Set<T> get() {
        int size = individualProviders.size();
        // Profiling revealed that this method was a CPU-consuming hotspot in some applications, so
        // these loops were changed to use c-style for.  Versus enhanced for-each loops, C-style for is
        // faster for ArrayLists, at least through Java 8.
    
        List<Collection<T>> providedCollections =
            new ArrayList<Collection<T>>(collectionProviders.size());
        for (int i = 0, c = collectionProviders.size(); i < c; i++) {
          Collection<T> providedCollection = collectionProviders.get(i).get();
          size += providedCollection.size();
          providedCollections.add(providedCollection);
        }
    
        Set<T> providedValues = newHashSetWithExpectedSize(size);
        for (int i = 0, c = individualProviders.size(); i < c; i++) {
          providedValues.add(checkNotNull(individualProviders.get(i).get()));
        }
        for (int i = 0, c = providedCollections.size(); i < c; i++) {
          for (T element : providedCollections.get(i)) {
            providedValues.add(checkNotNull(element));
          }
        }
    
        return unmodifiableSet(providedValues);
      }
    
    

    我们看到,individualProviderSize对应@IntoSet的个数,collectionProviderSize对应@ElementsIntoSet的个数。
    get时会通过individualProvidercollectionProvider提供的数据注入到instance中形成集合。

    @IntoMap

    IntoSet一个性质,只不过多了Key的约束。上例子:

    
    
    @Provides
    @IntoMap // 指定该@Provides方法向Map提供元素
    @StringKey("A") // 指定该元素在Map中所对应的的Key
    String providexxx() {
        return "ABC";
    
    }
    
    @Provides
    @IntoMap // 指定该@Provides方法向Map提供元素
    @ClassKey(MainActivity.class)
    String providekkk() {
        return "Acc";
    
    }
        
    

    StringKey代表以字符串作为key,ClassKey代表以类作为key。
    目前dagger2预定义的有:

    • ClassKey
    • IntKey
    • LongKey
    • StringKey

    编译后的代码不贴了,跟set原理差不多

    自定义key类型

    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";
      }
    }
    
    

    这里定义了自定义两种key:ABC,DEF

    Map的高级用法请见下节。

    相关文章

      网友评论

        本文标题:有关Dagger2的一些事(一)

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