Dagger 2学习与探索(六)

作者: akak18183 | 来源:发表于2017-08-28 00:27 被阅读0次

    上一期稍微探究了一下交叉依赖,这一期来探究一下Dagger里的@Scope标注。
    Scope是范围的意思,Dagger里面自带了一个已经可以用的范围标注:@Singleton
    我们知道,Singleton是单例的意思,那么这里的单例标注是何含义?
    先看看@Singleton的代码:

    /**
     * Identifies a type that the injector only instantiates once. Not inherited.
     *
     * @see javax.inject.Scope @Scope
     */
    @Scope
    @Documented
    @Retention(RUNTIME)
    public @interface Singleton {}
    

    注释说,标识一个类型,让注入器只初始化它一次。关于类型,我们不可避免联想到@Provides标注,也是只关注返回类型的。
    再看看@Scope标注:

    /**
     * Identifies scope annotations. A scope annotation applies to a class
     * containing an injectable constructor and governs how the injector reuses
     * instances of the type. By default, if no scope annotation is present, the
     * injector creates an instance (by injecting the type's constructor), uses
     * the instance for one injection, and then forgets it. If a scope annotation
     * is present, the injector may retain the instance for possible reuse in a
     * later injection. If multiple threads can access a scoped instance, its
     * implementation should be thread safe. The implementation of the scope
     * itself is left up to the injector.
     * 省略中间注释……
     *
     * <p>Annotating scope annotations with {@code @Scope} helps the injector
     * detect the case where a programmer used the scope annotation on a class but
     * forgot to configure the scope in the injector. A conservative injector
     * would generate an error rather than not apply a scope.
     *
     * @see javax.inject.Singleton @Singleton
     */
    @Target(ANNOTATION_TYPE)
    @Retention(RUNTIME)
    @Documented
    public @interface Scope {}
    

    这次注释还比较有用,或者说是能看懂的。大意就是,如果没有@Scope标注,注入器每次都会产生新的实例来注入,反之就会保存然后复用。另外还提到了线程安全,不过这些都是Dagger包揽的。
    到目前为止,可能你还有不少疑问,不过先来实验一下,或许疑问就迎刃而解了。

    主体代码

    现在新建一个MyApp继承Application,并在Manifest文件里面声明:

    public class MyApp extends Application {
    
      @Override
      public void onCreate() {
        super.onCreate();
      }
    }
    
      <application
        android:name=".MyApp"
    ...
    

    好了,现在我们想要做的事情就是,在MyAppMainActivity里面注入两个ClassB,来观察是否真的是单例模式。当然,这仅仅是实验,讲道理单例一般是不带构造参数的。
    另外还有一个比较常见的Dagger的应用方法,就是只创建一次Component然后到处传,好处就是复用,然后实际上@Singleton标注也局限于同一个注入器。一般而言创建的场合就是在Application里面了。
    首先将ComponentB添加对MyApp@Inject标注方法,并添加@Singleton标注:

    @Singleton
    @Component(modules = ModuleB.class)
    public interface ClassBComponent {
    
      void inject(MainActivity mainActivity);
      void inject(MyApp myApp);
    }
    

    为什么Component也要加@Singleton标注呢?这是Dagger的规定,假如@Provides里面有想要加单例标注的,那么其Component也得加,或许与生成代码的机制有关,我们遵循即可,到后面会有更多说明。
    然后对ModuleB里面的对应的ClassB的提供函数加上单例标注:

    @Module
    public class ModuleB {
    
      private int a;
      private int b;
    
      public ModuleB(int a, int b) {
        this.a = a;
        this.b = b;
      }
    
      @Provides
      @Named("a")
      int provideIntA() {
        return a;
      }
    
      @Provides
      @Named("b")
      int provideIntB() {
        return b;
      }
    
      @Provides
      ClassA provideClassA(@Named("a") int a, @Named("b") int b) {
        return new ClassA(a, b);
      }
    
      @Singleton
      @Provides
      ClassB provideClassB(ClassA classA, @Named("a") int a) {
        return new ClassB(classA, a);
      }
    }
    

    为方便看出ClassB的构造,在ClassB的构造函数里加一个Log:

    public class ClassB {
      private static final String TAG = "ClassB";
      private ClassA classA;
      private int a;
    
      public ClassB(ClassA classA, int a) {
        this.classA = classA;
        this.a = a;
        Log.d(TAG, "classB constructor called");
      }
    
      public ClassA getClassA() {
        return classA;
      }
    
      public int getA() {
        return a;
      }
    }
    

    现在对MyApp做一些变更:

    public class MyApp extends Application {
    
      private static MyApp appInstance = null;
      private static ClassBComponent classBComponent = null;
    
      @Inject ClassB classB;
    
      @Override
      public void onCreate() {
        super.onCreate();
    
        appInstance = this;
        classBComponent = DaggerClassBComponent.builder().moduleB(new ModuleB(2, 3)).build();
        classBComponent.inject(this);
      }
    
      public static MyApp getAppInstance() {
        return appInstance;
      }
    
      public static ClassBComponent getClassBComponent() {
        return classBComponent;
      }
    }
    

    然后再是MainActivity

    public class MainActivity extends AppCompatActivity {
      @Inject ClassB classB;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        MyApp.getClassBComponent().inject(this);
      }
    }
    

    运行发现构造函数确实只被调用了一次。假如把两个@Singleton去掉可以发现调用了两次,有兴趣的可以自己试一试。
    仔细想想,也许没那么神奇?毕竟是对同一个Component而言,既然刚开始就获得了所有需要的参数,生成一些单例代码也不那么奇怪。

    生成代码

    生成代码变化不如想象中那么大。首先四个工厂还是雷打不动,说明单例的实现并没有落在ClassB的工厂类上面;然后多了一个MyApp_MembersInjector原理和MainActivity_MembersInjector类似。
    真正实现单例的是DaggerClassBComponent,这也解释了为什么要在其上添加@Singleton标注:

    public final class DaggerClassBComponent implements ClassBComponent {
      private Provider<Integer> provideIntAProvider;
    
      private Provider<Integer> provideIntBProvider;
    
      private Provider<ClassA> provideClassAProvider;
    
      private Provider<ClassB> provideClassBProvider;
    
      private MembersInjector<MainActivity> mainActivityMembersInjector;
    
      private MembersInjector<MyApp> myAppMembersInjector;
    
      private DaggerClassBComponent(Builder builder) {
        assert builder != null;
        initialize(builder);
      }
    
      public static Builder builder() {
        return new Builder();
      }
    
      @SuppressWarnings("unchecked")
      private void initialize(final Builder builder) {
    
        this.provideIntAProvider = ModuleB_ProvideIntAFactory.create(builder.moduleB);
    
        this.provideIntBProvider = ModuleB_ProvideIntBFactory.create(builder.moduleB);
    
        this.provideClassAProvider =
            ModuleB_ProvideClassAFactory.create(
                builder.moduleB, provideIntAProvider, provideIntBProvider);
    
        this.provideClassBProvider =
            DoubleCheck.provider(
                ModuleB_ProvideClassBFactory.create(
                    builder.moduleB, provideClassAProvider, provideIntAProvider));
    
        this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provideClassBProvider);
    
        this.myAppMembersInjector = MyApp_MembersInjector.create(provideClassBProvider);
      }
    
      @Override
      public void inject(MainActivity mainActivity) {
        mainActivityMembersInjector.injectMembers(mainActivity);
      }
    
      @Override
      public void inject(MyApp myApp) {
        myAppMembersInjector.injectMembers(myApp);
      }
    
      public static final class Builder {
        private ModuleB moduleB;
    
        private Builder() {}
    
        public ClassBComponent build() {
          if (moduleB == null) {
            throw new IllegalStateException(ModuleB.class.getCanonicalName() + " must be set");
          }
          return new DaggerClassBComponent(this);
        }
    
        public Builder moduleB(ModuleB moduleB) {
          this.moduleB = Preconditions.checkNotNull(moduleB);
          return this;
        }
      }
    }
    

    核心代码是这里:

     this.provideClassBProvider =
            DoubleCheck.provider(
                ModuleB_ProvideClassBFactory.create(
                    builder.moduleB, provideClassAProvider, provideIntAProvider));
    

    那么DoubleCheck又是什么呢?

    /**
     * A {@link Lazy} and {@link Provider} implementation that memoizes the value returned from a
     * delegate using the double-check idiom described in Item 71 of <i>Effective Java 2</i>.
     */
    public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
      private static final Object UNINITIALIZED = new Object();
    
      private volatile Provider<T> provider;
      private volatile Object instance = UNINITIALIZED;
    
      private DoubleCheck(Provider<T> provider) {
        assert provider != null;
        this.provider = provider;
      }
    
      @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 + ". This is likely "
                    + "due to a circular dependency.");
              }
              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;
      }
    
      /** Returns a {@link Provider} that caches the value from the given delegate provider. */
      public static <T> Provider<T> provider(Provider<T> delegate) {
        checkNotNull(delegate);
        if (delegate instanceof DoubleCheck) {
          /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped
           * binding, we shouldn't cache the value again. */
          return delegate;
        }
        return new DoubleCheck<T>(delegate);
      }
    
      /** Returns a {@link Lazy} that caches the value from the given provider. */
      public static <T> Lazy<T> lazy(Provider<T> provider) {
        if (provider instanceof Lazy) {
          @SuppressWarnings("unchecked")
          final Lazy<T> lazy = (Lazy<T>) provider;
          // Avoids memoizing a value that is already memoized.
          // NOTE: There is a pathological case where Provider<P> may implement Lazy<L>, but P and L
          // are different types using covariant return on get(). Right now this is used with
          // DoubleCheck<T> exclusively, which is implemented such that P and L are always
          // the same, so it will be fine for that case.
          return lazy;
        }
        return new DoubleCheck<T>(checkNotNull(provider));
      }
    }
    

    其原理来自于Double Check的单例模式,这里就是把传入的工厂再包一层单例,至于细节有兴趣的可以自行研究。

    看Dagger的实现好像也不是那么难不是吗?其实Dagger还支持自定义@Scope,只需要仿照@Singleton的样子改个名字就行了,但其实Dagger会把它当做@Singleton来对待,或者说Dagger其实压根只认识@Scope
    你可能会觉得很疑惑。不过想一想,随便加一个@PerActivity之类的就让Dagger掌控Activity的生命周期,然后做出相应调整,那是童话故事。事实就是,Dagger只认@Scope,只当做单例处理,这也是为什么Dagger只带了这么一个标注,而且改名叫这个。
    那么是不是自定义@Scope就没有意义了呢?也不尽然。好的名字能直接起到注释的作用,比如@PerActivity就时刻提醒作者和读者这个Component生命周期应该局限在Activity之内。也就是说,其实最后真正要实现PerActivity的还是使用的人。
    学习了@Scope,下一期我们将探究Component dependencySubcomponent

    相关文章

      网友评论

        本文标题:Dagger 2学习与探索(六)

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