Dagger系列:
Demo地址:
Scope,翻译过来的意思是作用域。作用域的理解是变量的作用范围,因此,出现了全局变量和局部变量之分。而,Android开发中,首先也是必须了解的是Activity或者Fragment的生命周期,其决定了在不同的生命周期内,应该做哪些工作。Scope与Activity或者Fragment的生命周期又有什么联系呢?
于是乎,网上的很多例子,将Scope与Activity或者Fragment的生命周期相结合:自定义一个PerActivity/PerFragment注解,那创建的类实例就与Activity/PerFragment“共生死”。
或者用Singleton注解标注一个创建类实例的方法,该创建类实例的方法就可以创建一个唯一的类实例。
当时,一无所知,也就是这么认为了,跳进了这深深的大坑。对PerActivity/PerFragment,或者Singleton产生了深深的疑惑:
带着疑惑,查阅各种资料,翻看官方文档,最后才发现,竟然与当前的认识不一致,可以这么说,我被Scope的字面意思带偏了,或者说对Scope的理解不够,自己掉进了,自己挖的坑里。坑有多深,往下看…
package javax.inject
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Scope {}
@Scope是javax.inject包下的一个注解,其是用来标识范围的注解,该注解适用于注解包含可注入的构造函数的类,并控制该如何重复使用类的实例。在默认情况下,也就是说仅仅使用@Inject注解构造函数,而没有使用@Scope注解类时,每次依赖注入都会创建一个实例(通过注入类型的构造函数创建实例)。如果使用了@Scope注解了该类,注入器会缓存第一次创建的实例,然后每次重复注入缓存的实例,而不会再创建新的实例。
注意:
如果多个线程都可以访问该作用域的实例,它的实现应该是线程安全的。
这里,我们先自定义一个Scope - TodoScope:
@Scope
@Documented
@Retention(RUNTIME)
public @interface TodoScope {
}
然后,自定义两个类OrangeBean和BananaBean,它们的构造函数都被@Inject注解。不同的是,OrangeBean被@TodoScope注解,而BananaBean没有被注解。
@TodoScope
public class OrangeBean {
private String name;
private double price;
private String area;
@Inject
public OrangeBean() {
Log.d("test", "OrangeBean()");
}
}
public class BananaBean {
private String name;
private double price;
private String area;
@Inject
public BananaBean() {
Log.d("test", "BananaBean()");
}
}
在FruitActivity类中,分别依赖注入OrangeBean和BananaBean各两个对象。
@TodoScope
@Component(modules = {FruitModule.class})
public interface FruitComponent {
void inject(FruitActivity activity);
}
public class FruitActivity extends AppCompatActivity {
***
@Inject
OrangeBean orangeA;
@Inject
OrangeBean orangeB;
@Inject
BananaBean bananaA;
@Inject
BananaBean bananaB;
protected void onCreate(Bundle savedInstanceState) {
***
// orangeA:advanced.todo.com.daggerlearn.bean.OrangeBean@3d061bc6
Log.d("test", "orangeA:" + orangeA.toString());
// orangeB:advanced.todo.com.daggerlearn.bean.OrangeBean@3d061bc6
Log.d("test", "orangeB:" + orangeB.toString());
// bananaA:advanced.todo.com.daggerlearn.bean.BananaBean@19345787
Log.d("test", "bananaA:" + bananaA.toString());
// bananaB:advanced.todo.com.daggerlearn.bean.BananaBean@275e96b4
Log.d("test", "bananaB:" + bananaB.toString());
}
***
}
从下图,我们可以清晰的看出:
到这里,好像对@Scope有点,清晰的认识了,@Scope对某类注解后,其作用的核心应该是注入器的控制,说白了就是,在注入实例时,是控制注入器调用缓存的实例还是重新实例。
值得注意的是,如果有类注入实例的类被@Scope注解,那么其Component必须被相同的Scope注解。比如,OrangeBean被@TodoScope注解,那么FruitComponent也必须被@TodoScop注解。如果FruitComponent没有@TodoScop注解,编译时会报错。
@Singleton是@Scope的一个特例,或者是说是@Scope的一种实现方式。
用@Singleton注释一个@Provides方法或注入类:
@Provides @Singleton static Heater provideHeater() {
return new ElectricHeater();
}
用@Singleton注解一个类表明该类的创建采用的是单例模式,其会被多个线程共同使用:
@Singleton
class CoffeeMaker {
...
}
在Dagger2中,将图的作用域与组件的实现实例相关联,所以组件本身需要声明其作用范围。如果在同一组件中,同时使用@Singleton绑定和@RequestScoped绑定是没有意义的,因为这些范围具有不同的生命周。因此在不同生命周期的组件中,绑定是不一样的。要声明组件与给定范围相关联,只需将该范围注释应用于组件界面。
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
CoffeeMaker maker();
}
一个Component可能会应用多个范围注释,这声明它们都是同一范围的别名,因此组件可能包含与其声明的任何范围的作用域绑定。
在Dagger 2从浅到深(二)中提到,Dagger的依赖注入规则是:
步骤2:若存在创建类方法,查看该方法是否存在参数
步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
依赖对象的注入源应该是有两个,一是Module中的@Provides方法,而是使用@Inject注解的构造函数。从前面示例中可以看出,如果使用@Scope注解类同时使用@Inject注解构造函数时,第一次创建时调用构造函数,然后将创建的实例缓存。以后再依赖注入时,不再调用构造函数创建,而是取缓存中的实例。根据Dagger的依赖注入规则,Module中的@Provides方法的优先级高于@Inject,那么使用@Scopre使用注解@Provides方法会产生什么样的效果呢?
还有一个疑惑就是,既然使用@Scope注解时,会缓存创建的实例对象。那么,如果两个Component都产生同一个类的实例,此时,是取缓存的实例还是调用构造函数,重新重建呢?
带着这两个疑问,用代码测试才是王道。至于Scope,不再重复定义,用之前的定义的TodoScope即可。
现创建AppleBean、BananaBean、GreenTeaBean及OrangeBean等四个类,其中:
BananaBean和GreenTeaBean都没有使用@TodoScope注解
@TodoScope
public class AppleBean {
@Inject
public AppleBean() {
}
}
public class BananaBean {
@Inject
public BananaBean() {
Log.d("test", "BananaBean()");
}
}
public class GreenTeaBean {
public GreenTeaBean() {
}
}
@TodoScope
public class OrangeBean {
@Inject
public OrangeBean() {
Log.e("test", "OrangeBean()");
}
}
接着,创建FruitModule和DrinkModule,在FruitModule中,提供AppleBean和GreenTeaBean,但是其@Provides方法未被@TodoScode注解,而DrinkModule的@Providers方法都是使用@TodoScope注解的:
@Module()
public class DrinkModule {
@TodoScope
@Provides
public AppleBean providerApple() {
return new AppleBean();
}
@TodoScope
@Provides
public GreenTeaBean providerDrink() {
return new GreenTeaBean();
}
}
@Module()
public class FruitModule {
@Provides
public AppleBean providerApple() {
return new AppleBean();
}
@Provides
public GreenTeaBean providerDrink() {
return new GreenTeaBean();
}
}
在FruitActivity和DrinkActivity分别依赖注入OrangeBean、BananaBean、GreenTeaBean和AppleBean等各两个对象XxA和XxB,我们来看它们的内存信息就可以看出Scope的作用了:
public class FruitActivity extends AppCompatActivity {
***
@Inject
OrangeBean orangeA;
@Inject
OrangeBean orangeB;
@Inject
BananaBean bananaA;
@Inject
BananaBean bananaB;
@Inject
GreenTeaBean greenTeaA;
@Inject
GreenTeaBean greenTeaB;
@Inject
AppleBean appleBeanA;
@Inject
AppleBean appleBeanB;
@Override
protected void onCreate(Bundle savedInstanceState) {
***
Log.d("test", "Drink - orangeA:" + orangeA.toString());
Log.d("test", "Drink - orangeB:" + orangeB.toString());
Log.d("test", "Drink - bananaA:" + bananaA.toString());
Log.d("test", "Drink - bananaB:" + bananaB.toString());
Log.d("test", "Drink - greenTeaA:" + greenTeaA.toString());
Log.d("test", "Drink - greenTeaB:" + greenTeaB.toString());
Log.d("test", "Drink - appleBeanA:" + appleBeanA.toString());
Log.d("test", "Drink - appleBeanB:" + appleBeanB.toString());
}
***
}
public class DrinkActivity extends AppCompatActivity {
@Inject
OrangeBean orangeA;
@Inject
OrangeBean orangeB;
@Inject
BananaBean bananaA;
@Inject
BananaBean bananaB;
@Inject
GreenTeaBean greenTeaA;
@Inject
GreenTeaBean greenTeaB;
@Inject
AppleBean appleBeanA;
@Inject
AppleBean appleBeanB;
@Override
protected void onCreate(Bundle savedInstanceState) {
***
Log.d("test", "Drink - orangeA:" + orangeA.toString());
Log.d("test", "Drink - orangeB:" + orangeB.toString());
Log.d("test", "Drink - bananaA:" + bananaA.toString());
Log.d("test", "Drink - bananaB:" + bananaB.toString());
Log.d("test", "Drink - greenTeaA:" + greenTeaA.toString());
Log.d("test", "Drink - greenTeaB:" + greenTeaB.toString());
Log.d("test", "Drink - appleBeanA:" + appleBeanA.toString());
Log.d("test", "Drink - appleBeanB:" + appleBeanB.toString());
}
***
}
从下图中,我们可以看出:
如果依赖实例的注入来源是@Provides方法时,@Provides方法必须被@Scope注解;如果依赖实例的注入来源是@Inject注解的构造函数时,实例类必须被@Scope注解。这样@Scope注解才会有效。也就是说,@Scope实际上是对注入器的控制。
Scope控制的实例的注入器是当前Component之内的实例注入器,而不会影响其他的Component中的实例注入器。
所以,Scope的真正作用还是依赖于Component的组织:
现在,对Scope有了全新的认识,我想文章开始的那两个的疑惑,也迎刃而解了。