Android-Rxjava&retrofit&daggerAndroid技术知识Android开发经验谈

有关Dagger2的一些事(一)

2018-03-23  本文已影响73人  LSteven

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

此篇不会从头开始介绍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

注入过程

@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

@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预定义的有:

编译后的代码不贴了,跟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的高级用法请见下节。

上一篇下一篇

猜你喜欢

热点阅读