上一期稍微探究了一下交叉依赖,这一期来探究一下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"
...
好了,现在我们想要做的事情就是,在MyApp
与MainActivity
里面注入两个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 dependency
与Subcomponent
。