Scope(作用域)
Scope是用来确定注入的实例的生命周期的,如果没有使用Scope注解,Component每次调用Module中的provide方法或者Inject构造函数生成的工厂时都会创建一个新的实例,而使用Scope后可以复用之前的依赖实例。下面先介绍Scope的基本概念与原理,再分析Singleton、Reusable等作用域。
Scope基本概念
先介绍Scope的用法,@Scope是元注解,是用来标记自定义注解的,如下:
1
2
3
4
|
@Documented
@Retention(RUNTIME)
@Scope
public @interface MyScope {}
|
MyScope就是一个Scope注解,Scope注解只能标注目标类、@provide方法和component。Scope注解要生效的话,需要同时标注在Component和提供依赖的示例的Module或者目标类上。Module中provide方法中的Scope注解必须和与之绑定的Component的Scope注解一样,否者作用域不同会导致编译时报错。例如,CarModule中的provide方法的Scope是MyScope的话,ManComponent的Scope必须是MyScope这样的作用域才会生效,而且不能是@Singleton或者其它Scope注解,不然编译时Dagger2会报错。
那么 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 的生命周期。
下面以@MyScope为例,看 Scope 注解背后的代码:
1
2
3
4
5
6
7
8
9
|
@Module
public class CarModule {
@Provides
@MyScope
static Car provideCar() {
return new Car();
}
}
|
1
2
3
4
5
|
@MyScope
@Component(modules = CarModule.class)
public interface ManComponent {
void injectMan(Man man);
}
|
这样生成的 Car 实例就与 ManComponent 绑定了。下面看编译时生成的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public final class DaggerManComponent implements ManComponent {
private Provider<Car> provideCarProvider;
private MembersInjector<Man> manMembersInjector;
...
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.provideCarProvider = DoubleCheck.provider(CarModule_ProvideCarFactory.create());
this.manMembersInjector = Man_MembersInjector.create(provideCarProvider);
}
...
}
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; // instance 就是依赖实例的引用
...
@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;
}
...
}
|
从上面 DaggerManComponent 的代码可以看出使用了 MyScope 作用域后,provideCarProvider由CarModule_ProvideCarFactory.create()变为了DoubleCheck.provider(CarModule_ProvideCarFactory.create())。而 DoubleCheck 包装的意义在于持有了 Car 的实例,而且只会生成一次实例,也就是说:没有用 MyScope 作用域之前,DaggerManComponent 每次注入依赖都会新建一个 Car 实例,而用 MyScope 作用之后,每次注入依赖都只会返回第一次生成的实例。DaggerManComponent 持有 provideCarProvider 引用,provideCarProvider 又持有 instance(即 Car 实例)的引用,所以生成 Car 对象实例的生命周期就和 Component 一致了,作用域就生效了。
Scope 作用域的本质:Component 间接持有依赖实例的引用,把实例的作用域与 Component 绑定,它们不是同年同月同日生,但是同年同月死。
Singleton Scope
把上面例子中@MyScope换成@Singleton,发现生成的 DaggerManComponent 和其他类没有变化。也只是用DoubleCheck包装了工厂而已,并没有什么特殊实现。所以 Singleton 作用域可以保证一个 Component 中的单例,但是如果产生多个 Component 实例,那么实例的单例就无法保证了。
所以在网上一些例子中,有看到AppComponent使用 Singleton 作用域,保证绑定的依赖实例的单例。它生效的原因是AppComponent只会在 Application 中创建一次,由AppComponent的单例来保证绑定的依赖实例的单例。
注意:Component 可以同时被多个 Scope 标记。即 Component 可以和多个 Scope 的 Moudle 或目标类绑定。
Reusable Scope
上文中的自定义的@MyScope和@Singleton都可以使得绑定的 Component 缓存依赖的实例,但是与之绑定 Component 必须有相同的 Scope 标记。假如我只想单纯缓存依赖的实例,可以复用之前的实例,不想关心与之绑定是什么 Component,应该怎么办呢?。
这时就可以使用@Reusable作用域,Reusable 作用域不关心绑定的 Component,Reusable 作用域只需要标记目标类或 provide 方法,不用标记 Component。下面先看看使用 Reusable 作用域后,生成的 DaggerManComponent 的变化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public final class DaggerManComponent implements ManComponent {
...
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.provideCarProvider = SingleCheck.provider(CarModule_ProvideCarFactory.create()); // DaggerManComponent 的改动
this.manMembersInjector = Man_MembersInjector.create(provideCarProvider);
}
...
}
public final class SingleCheck<T> implements Provider<T>, Lazy<T> {
private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance = UNINITIALIZED; // 缓存实例的引用
...
@SuppressWarnings("unchecked") // cast only happens when result comes from the delegate provider
@Override
public T get() {
// provider is volatile and might become null after the check to instance == UNINITIALIZED, so
// retrieve the provider first, which should not be null if instance is UNINITIALIZED.
// This relies upon instance also being volatile so that the reads and writes of both variables
// cannot be reordered.
Provider<T> providerReference = provider;
if (instance == UNINITIALIZED) {
instance = providerReference.get(); // 一般情况下只会生成一个实例,多线程中可能会有多个
// 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) instance;
}
...
}
|
从上面代码可以看出使用@Reusable作用域后,利用到 Reusable 实例的 Component 会间接持有实例的引用。但是这里是用SingleCheck而不是DoubleCheck,在多线程情况下可能会生成多个实例,关于这个有疑问推荐阅读 Java 设计模式之单例模式中双重检查锁定的部分。因为@Reusable作用域目的只是可以复用之前的实例,并不需要严格地保证实例的唯一,所以使用 SingleCheck 就足够了。
Binding Instances
如果想在绑定component时注入参数,如app需要一个用户名参数,就可以给component的builder方法添加一个@BindInstance方法注解,使用户名字符串实例可以被注入到component中:
1
2
3
4
5
6
7
8
9
10
|
@Component(modules = AppModule.class)
interface AppComponent {
App app();
@Component.Builder
interface Builder {
@BindsInstance Builder userName(@UserName String userName);
AppComponent build();
}
}
|
1
2
3
4
5
6
7
8
9
|
public static void main(String[] args) {
if (args.length > 1) { exit(1); }
App app = DaggerAppComponent
.builder()
.userName(args[0])
.build()
.app();
app.run();
}
|
1
2
3
4
5
6
7
8
9
10
|
@ActivityScope
@Component
public interface HomeActivityComponent {
@Component.Builder
interface Builder {
@BindsInstance
Builder activity(Activity activity);
HomeActivityComponent build();
}
}
|
注意在调用build()创建 Component 之前,所有@BindsInstance方法必须先调用。上面例子中 HomeActivityComponent 还可以注入 Activity 类型的依赖,但是不能注入 HomeActivity,因为 Dagger 2 是使用具体类型作为依据的(也就是只能使用@Inject Activity activity而不是@Inject HomeActivity activity)。
如果@BindsInstance方法的参数可能为 null,需要再用@Nullable标记,同时标注 Inject 的地方也需要用@Nullable标记。这时 Builder 也可以不调用@BindsInstance方法,这样 Component 会默认设置 instance 为 null。