Dagger 2学习与探索(六)

上一期稍微探究了一下交叉依赖,这一期来探究一下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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容