Dagger2完全解析(二),进阶使用

Dagger 2 进阶使用


[Dagger 2 完全解析(一),基本使用与原理
Dagger 2 完全解析(二), 进阶使用
Dagger 2 完全解析(三), Component 与 SubComponent
Dagger 2 完全解析(四),在Android中的使用

本系列文章是基于 Google Dagger 2.23.2 版本, Kotlin 1.3.21版本


Dagger 2 完全解析(一),基本使用与原理中介绍了 Dagger 2 基本使用,但是在实战中基本使用是远远不够的,我们还需要掌握一些其它的知识,下面由简入繁地讲解这几个概念,同时结合 Dagger 2 的编译时生成代码分析背后的原理(示例代码沿用第一篇的)。

下面开始对LazyProviderQualifierScope进行分析。

Lazy (延迟注入)

有时我们想注入的依赖在使用时再完成初始化,提高加载速度,就可以使用注入Lazy<T>。只有在调用Lazyget() 方法时才会初始化依赖实例注入依赖。

public interface Lazy<T> {
  T get();
}

A中使用:

class A {
    ...
    @Inject
    lateinit var lazyD: Lazy<D>

    fun doWork(){
        lazyD.get() // 返回D的实例
    }
}

Make app后,在DaggerAComponent中:

public final class DaggerAComponent implements AComponent {
  private final AModule aModule;

 ...
  @Override
  public void injectA(A a) {
    injectA2(a);}

  private A injectA2(A instance) {
    A_MembersInjector.injectB(instance, AModule_ProvideBFactory.provideB(aModule));
    A_MembersInjector.injectC(instance, new C());
    // DoubleCheck 是线程安全的Lazy实例初始化类
    A_MembersInjector.injectLazyD(instance, DoubleCheck.lazy(D_Factory.create()));
    return instance;
  }

...

DoubleCheck 是线程安全的Lazy实例初始化工具类

public static <P extends Provider<T>, T> Lazy<T> lazy(P provider) {
    if (provider instanceof Lazy) {
      @SuppressWarnings("unchecked")
      final Lazy<T> lazy = (Lazy<T>) provider;
      return lazy;
    }
    return new DoubleCheck<T>(checkNotNull(provider));
  }

// 在需要使用的时候调用此方法进行返回相应的实例
  @Override
  public T get() {
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get();
          instance = reentrantCheck(instance, result);
          provider = null;
        }
      }
    }
    return (T) result;
  }

Provider 注入

有时候不仅仅是注入单个实例,我们需要多个实例,这时可以使用注入Provider<T>,每次调用它的 get() 方法都会调用到 @Inject 构造函数创建新实例或者Module 的 provide 方法返回实例。

class A {
    
    @Inject
    lateinit var providerD: Provider<D>

    fun createDFactory(): MutableList<D> {
        val ds = mutableListOf<D>()
        for (i in 0..10) {
            ds.add(providerD.get())
        }
        return ds
    }
}

Make app后,在DaggerAComponent中:

public final class DaggerAComponent implements AComponent {
  private final AModule aModule;

 ...
  @Override
  public void injectA(A a) {
    injectA2(a);}

 private A injectA2(A instance) {
    A_MembersInjector.injectB(instance, AModule_ProvideBFactory.provideB(aModule));
    A_MembersInjector.injectC(instance, new C());
    A_MembersInjector.injectLazyD(instance, DoubleCheck.lazy(D_Factory.create()));
    // 注入provider ,将`Factory`传入
    A_MembersInjector.injectProviderD(instance, D_Factory.create());
    return instance;
  }
...

再来看看D_Factory

public final class D_Factory implements Factory<D> {
  private static final D_Factory INSTANCE = new D_Factory();

  // 每次调用get方法都会重新创建实例
  @Override
  public D get() {
    return new D();
  }

  public static D_Factory create() {
    return INSTANCE;
  }

  public static D newInstance() {
    return new D();
  }
}

通过上面可以看到,每次调用get方法都会重新创建实例, D_Factory实现了Factory<D>Factory<D>继承自Provider<D>

public final class D_Factory implements Factory<D> {}

public interface Factory<T> extends Provider<T> {}

Qualifier(限定符)

试想这样一种情况:在 AModule提供了两个生成B 实例的provide 方法,如果使用 Dagger 2A 中注入B实例时应该选择哪一个方法呢?

@Module
class AModule {

    @Provides
    fun provideB(): B = B()

    @Provides
    fun provideOther(): B = B()
}

这时 Dagger 2 不知道使用provideB还是provideOther提供的实例,在编译时就会出现:

[Dagger/DuplicateBindings] *.data.B is bound multiple times:

@Qualifier注解就是用来解决这个问题,使用注解来确定使用哪种 provide 方法。

@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Qualifier {}

我们可以发现@Qualifier是用来修饰注解的,所以它是不能直接作用到provide方法上。

下面是自定义的@Named注解,你也可以用自定义的其他 Qualifier 注解:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

在 provide 方法上加上@Named注解,用来区分

@Module
class AModule {

    @Named("b")
    @Provides
    fun provideB(): B = B()

    @Named("other")
    @Provides
    fun provideOther(): B = B()
}

还需要在Inject 注入的地方加上@field:Named注解:

class A {
    @Inject
    @field:Named("other")
    lateinit var b: B
 	...
}

**tips: ** 在kotlin中使用@Name给属性注入时需要添加@field,不然注解不生效。

这样在依赖注入时,Dagger 2 就会使用provideB方法提供的实例,所以Qualifier(限定符)的作用相当于起了个区分的别名。

public final class DaggerAComponent implements AComponent {
  private final AModule aModule;
  ...

  @Override
  public void injectA(A a) {
    injectA2(a);}

  private A injectA2(A instance) {
    // 使用的是 provide方法
    A_MembersInjector.injectB(instance, AModule_ProvideBFactory.provideB(aModule));
    ...
    return instance;
  }
  ...
}

当然啦,我们也可以自定义注解进行区分,比如:

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
annotation class Other

然后在provideOtherlateinit var b: B上加上@Other即可。

Scope(作用域)

Scope 是用来确定注入的实例的生命周期的,如果没有使用 Scope 注解,Component 每次调用 Module 中的 provide 方法或 Inject 构造函数生成的工厂时都会创建一个新的实例,而使用 Scope 后可以复用之前的依赖实例。下面先介绍 Scope 的基本概念与原理,再分析 Singleton、Reusable 等作用域。

Scope 基本概念

先介绍 Scope 的用法,@Scope是元注解,是用来标注自定义注解的,如下:

@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Scope {}

**Scope 注解只能标注目标类、@provide 方法和 Component。**Scope 注解要生效的话,需要同时标注在 Component 和提供依赖实例的Module 或目标类上。Module 中 provide 方法中的 Scope 注解必须和 与之绑定的 Component 的 Scope 注解一样,否则作用域不同会导致编译时会报错。

那么 Scope 注解又是如何产生作用的呢,怎么保证生成的依赖实例的生命周期呢?

在 Dagger 2 官方文档中我找到一句话,非常清楚地描述了@Scope的原理:

When a binding uses a scope annotation, that means that the component object holds a reference to the bound object until the component object itself is garbage-collected.

当 Component 与 Module、目标类(需要被注入依赖)使用 Scope 注解绑定时,意味着 Component 对象持有绑定的依赖实例的一个引用直到 Component 对象本身被回收。也就是作用域的原理,其实是让生成的依赖实例的生命周期与 Component 绑定,Scope 注解并不能保证生命周期,要想保证赖实例的生命周期,需要确保 Component 的生命周期。

下面以@AScope为例,看 Scope 注解背后的代码:

先定义AScope

@Scope
@Retention(RUNTIME)
@Target(FIELD, FUNCTION, CLASS)
annotation class AScope

使用Module方式

AModule中:

@Module
class AModule {
    @AScope
    @Provides
    fun provideB(): B = B()
}

AComponent

@Component(modules = [AModule::class])
@AScope
interface AComponent {
    fun injectA(a: A)
}

这样生成的 B实例就与 AComponent 绑定了。下面看编译时生成的代码:

public final class DaggerAComponent implements AComponent {
  private Provider<B> provideBProvider;

  private DaggerAComponent(AModule aModuleParam) {
    initialize(aModuleParam);
  }

  ...

  @SuppressWarnings("unchecked")
  private void initialize(final AModule aModuleParam) {
    this.provideOtherProvider = DoubleCheck.provider(AModule_ProvideBFactory.create(aModuleParam));
  }

  @Override
  public void injectA(A a) {
    injectA2(a);}

  private A injectA2(A instance) {
    // provideOtherProvider 不再由Factory创建,而是DobuleCheck
    A_MembersInjector.injectB(instance, provideBProvider.get());
    return instance;
  }
 ...
}

从上面 DaggerAComponent的代码可以看出使用了 AScope 作用域后,provideBProviderAModule_ProvideCarFactory.create()变为了DoubleCheck.provider(AModule_ProvideBFactory.create())。而 DoubleCheck 包装的意义在于持有了 B的实例,而且只会生成一次实例,也就是说:没有用 MyScope 作用域之前,DaggerAComponent 每次注入依赖都会新建一个 B实例,而用 AScope 作用之后,每次注入依赖都只会返回第一次生成的实例。通过生成的代码可以发现这和Lazy的方式很相似。

注解到目标类方式

class A {
    @AScope
    @Inject
    lateinit var b: B
}


@AScope
class B  @Inject constructor()


@AScope
@Component(modules = [AModule::class])
interface AComponent {
    fun injectA(a: A)
}

@Module
class AModule {
}

使用这种方式生成的代码:

public final class DaggerAComponent implements AComponent {
  private Provider<B> bProvider;

  private DaggerAComponent() {

    initialize();
  }
 ...

  @SuppressWarnings("unchecked")
  private void initialize() {
     // 此处使用的是Factory,不是AModule_ProvideBFactory
    this.bProvider = DoubleCheck.provider(B_Factory.create());
  }

  @Override
  public void injectA(A a) {
    injectA2(a);}

  private A injectA2(A instance) {
    A_MembersInjector.injectB(instance, bProvider.get());
    return instance;
  }
  ...
}

Scope 作用域的本质:Component 间接持有依赖实例的引用,把实例的作用域与 Component 绑定

Singleton

在了解作用域的原理后,再来理解 Dagger 2 提供的自带作用域就容易了。@Singleton顾名思义保证单例,那么它又是如何实现的呢,实现了单例模式那样只返回一个实例吗?

把上面例子中@AScope换成@Singleton,发现生成的 DaggerAComponent 和其他类没有变化。也只是用DoubleCheck包装了工厂而已,并没有什么特殊实现。所以 Singleton 作用域可以保证一个 Component 中的单例,但是如果产生多个 Component 实例,那么实例的单例就无法保证了。

所以在网上一些例子中,有看到AppComponent使用 Singleton 作用域,保证绑定的依赖实例的单例。它生效的原因是AppComponent只会在 Application 中创建一次,由AppComponent的单例来保证绑定的依赖实例的单例。

**注意:Component 可以同时被多个 Scope 标记。**即 Component 可以和多个 Scope 的 Moudle 或目标类绑定。

Reusable

上文中的自定义的@AScope@Singleton都可以使得绑定的 Component 缓存依赖的实例,但是与之绑定 Component 必须有相同的 Scope 标记。假如我只想单纯缓存依赖的实例,可以复用之前的实例,不想关心与之绑定是什么 Component,应该怎么办呢?。

这时就可以使用@Reusable作用域,**Reusable 作用域不关心绑定的 Component,Reusable 作用域只需要标记目标类或 provide 方法,不用标记 Component。**下面先看看使用 Reusable 作用域后,生成的 DaggerAComponent 的变化:

public final class DaggerAComponent implements AComponent {
  private Provider<B> provideBProvider;

  private DaggerAComponent(AModule aModuleParam) {
    initialize(aModuleParam);
  }

  @SuppressWarnings("unchecked")
  private void initialize(final AModule aModuleParam) {
    this.provideBProvider = SingleCheck.provider(AModule_ProvideBFactory.create(aModuleParam));
  }

  @Override
  public void injectA(A a) {
    injectA2(a);
  }

  private A injectA2(A instance) {
    A_MembersInjector.injectB(instance, provideBProvider.get());
    return instance;
  }
 ...
}

从上面代码可以看出使用@Reusable作用域后,利用到 Reusable 实例的 Component 会间接持有实例的引用。但是这里是用SingleCheck而不是DoubleCheck,在多线程情况下可能会生成多个实例。因为@Reusable作用域目的只是可以复用之前的实例,并不需要严格地保证实例的唯一,所以使用 SingleCheck 就足够了。

Releasable references(可释放引用)

相关内容在新版Dagger2已经废除。

Binding Instances

通过前面作用域的讲解,可以清楚 Component 可以间接持有 Module 或 Inject 目标类构造函数提供的依赖实例,除了这两种方式,Component 还可以在创建 Component 的时候绑定依赖实例,用以注入。这就是@BindsInstance注解的作用,只能在 Component.Builder 中使用。

在 Android 中使用 Dagger 2 时,activity 实例经常也需要作为依赖实例用以注入,在之前只能使用 Module:

@Module
class MainActivityModule {
    private val mainActivity: MainActivity

    constructor(mainActivity: MainActivity) {
        this.mainActivity = mainActivity
    }

    @Provides
    fun provideMainActivity(): MainActivity {
        return this.mainActivity
    }
}

而使用@BindsInstance的话会更加简单:

@Component
interface MainActivityComponent {

    fun injectMainActivity(activity: MainActivity)

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun activity(activity: Activity): Builder

        fun build(): MainActivityComponent
    }
}

注意在调用build()创建 Component 之前,所有@BindsInstance方法必须先调用。上面例子中 MainActivityComponent还可以注入 Activity 类型的依赖,但是不能注入 MainActivity,因为 Dagger 2 是使用具体类型作为依据的(也就是只能使用@Inject Activity activity而不是@Inject MainActivity activity)。

如果@BindsInstance方法的参数可能为 null,需要再用@Nullable标记,同时标注 Inject 的地方也需要用@Nullable标记。这时 Builder 也可以不调用@BindsInstance方法,这样 Component 会默认设置 instance 为 null。

总结

  • Lazy 可以延时注入,Provider 可以创建多个实例
  • Qualifier 限定符用来解决同一个实例不同方法提供冲突的问题,可以依赖实例起个别名用来区分,或者自定义注解
  • Scope 作用域的本质是 Component 会持有与之绑定的依赖实例的引用,要想确保实例的生命周期,关键在于控制 Component 的生命周期。
  • 推荐优先使用@BindsInstance方法,相对于写一个带有构造函数带有参数的 Module。
posted @ 2019-06-24 23:25  jxiaow  阅读(171)  评论(0编辑  收藏  举报