Dagger 2从浅到深(四)
来源:互联网 发布:linux 压缩指令 编辑:程序博客网 时间:2024/05/16 18:01
Dagger系列:
- Dagger 2从浅到深(一)
- Dagger 2从浅到深(二)
- Dagger 2从浅到深(三)
- Dagger 2从浅到深(四)
- Dagger 2从浅到深(五)
- Dagger 2从浅到深(六)
- Dagger 2从浅到深(七)
- Dagger 2应用于Android的完美扩展库-dagger.android
Scope,翻译过来的意思是作用域。作用域的理解是变量的作用范围,因此,出现了全局变量和局部变量之分。而,Android开发中,首先也是必须了解的是Activity或者Fragment的生命周期,其决定了在不同的生命周期内,应该做哪些工作。Scope与Activity或者Fragment的生命周期又有什么联系呢?
于是乎,网上的很多例子,将Scope与Activity或者Fragment的生命周期相结合:自定义一个PerActivity/PerFragment注解,那创建的类实例就与Activity/PerFragment“共生死”。
或者用Singleton注解标注一个创建类实例的方法,该创建类实例的方法就可以创建一个唯一的类实例。
当时,一无所知,也就是这么认为了,跳进了这深深的大坑。对PerActivity/PerFragment,或者Singleton产生了深深的疑惑:
- Scope到底是什么?如何决定作用域的,是不是与变量本身的作用域一致?
- PerActivity/PerFragment怎么与Activity/PerFragment的生命周期相关联的?
带着疑惑,查阅各种资料,翻看官方文档,最后才发现,竟然与当前的认识不一致,可以这么说,我被Scope的字面意思带偏了,或者说对Scope的理解不够,自己掉进了,自己挖的坑里。坑有多深,往下看…
@Scope
package javax.inject@Target(ANNOTATION_TYPE)@Retention(RUNTIME)@Documentedpublic @interface Scope {}
@Scope是javax.inject包下的一个注解,其是用来标识范围的注解,该注解适用于注解包含可注入的构造函数的类,并控制该如何重复使用类的实例。在默认情况下,也就是说仅仅使用@Inject注解构造函数,而没有使用@Scope注解类时,每次依赖注入都会创建一个实例(通过注入类型的构造函数创建实例)。如果使用了@Scope注解了该类,注入器会缓存第一次创建的实例,然后每次重复注入缓存的实例,而不会再创建新的实例。
注意:
如果多个线程都可以访问该作用域的实例,它的实现应该是线程安全的。
这里,我们先自定义一个Scope - TodoScope:
@Scope@Documented@Retention(RUNTIME)public @interface TodoScope {}
然后,自定义两个类OrangeBean和BananaBean,它们的构造函数都被@Inject注解。不同的是,OrangeBean被@TodoScope注解,而BananaBean没有被注解。
@TodoScopepublic 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()); } ***}
从下图,我们可以清晰的看出:
- OrangeBean的构造函数被调用了一次,而BananaBean的构造函数被调用了两次。
- OrangeBean的两个实例的内存地址是一致的,而BananaBean的两个实例地址是不一致的。
到这里,好像对@Scope有点,清晰的认识了,@Scope对某类注解后,其作用的核心应该是注入器的控制,说白了就是,在注入实例时,是控制注入器调用缓存的实例还是重新实例。
值得注意的是,如果有类注入实例的类被@Scope注解,那么其Component必须被相同的Scope注解。比如,OrangeBean被@TodoScope注解,那么FruitComponent也必须被@TodoScop注解。如果FruitComponent没有@TodoScop注解,编译时会报错。
@Singleton
@Singleton是@Scope的一个特例,或者是说是@Scope的一种实现方式。
用@Singleton注释一个@Provides方法或注入类:
@Provides @Singleton static Heater provideHeater() { return new ElectricHeater();}
用@Singleton注解一个类表明该类的创建采用的是单例模式,其会被多个线程共同使用:
@Singletonclass CoffeeMaker { ...}
在Dagger2中,将图的作用域与组件的实现实例相关联,所以组件本身需要声明其作用范围。如果在同一组件中,同时使用@Singleton绑定和@RequestScoped绑定是没有意义的,因为这些范围具有不同的生命周。因此在不同生命周期的组件中,绑定是不一样的。要声明组件与给定范围相关联,只需将该范围注释应用于组件界面。
@Component(modules = DripCoffeeModule.class)@Singletoninterface CoffeeShop { CoffeeMaker maker();}
一个Component可能会应用多个范围注释,这声明它们都是同一范围的别名,因此组件可能包含与其声明的任何范围的作用域绑定。
Component才是关键
在Dagger 2从浅到深(二)中提到,Dagger的依赖注入规则是:
- 步骤1:查找Module中是否存在创建该类的方法。
步骤2:若存在创建类方法,查看该方法是否存在参数
- 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
- 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
- 步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
- 步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
依赖对象的注入源应该是有两个,一是Module中的@Provides方法,而是使用@Inject注解的构造函数。从前面示例中可以看出,如果使用@Scope注解类同时使用@Inject注解构造函数时,第一次创建时调用构造函数,然后将创建的实例缓存。以后再依赖注入时,不再调用构造函数创建,而是取缓存中的实例。根据Dagger的依赖注入规则,Module中的@Provides方法的优先级高于@Inject,那么使用@Scopre使用注解@Provides方法会产生什么样的效果呢?
还有一个疑惑就是,既然使用@Scope注解时,会缓存创建的实例对象。那么,如果两个Component都产生同一个类的实例,此时,是取缓存的实例还是调用构造函数,重新重建呢?
带着这两个疑问,用代码测试才是王道。至于Scope,不再重复定义,用之前的定义的TodoScope即可。
现创建AppleBean、BananaBean、GreenTeaBean及OrangeBean等四个类,其中:
- AppleBean和OrangeBean被@TodoScope注解,同时其构造函数使用@Inject注解
BananaBean和GreenTeaBean都没有使用@TodoScope注解
@TodoScopepublic class AppleBean { @Inject public AppleBean() { }}public class BananaBean { @Inject public BananaBean() { Log.d("test", "BananaBean()"); }}public class GreenTeaBean { public GreenTeaBean() { }}@TodoScopepublic class OrangeBean { @Inject public OrangeBean() { Log.e("test", "OrangeBean()"); }}
接着,创建FruitModule和DrinkModule,在FruitModule中,提供AppleBean和GreenTeaBean,但是其@Provides方法未被@TodoScode注解,而DrinkModule的@Providers方法都是使用@TodoScope注解的:
@Module()
public class DrinkModule {@TodoScope@Providespublic AppleBean providerApple() { return new AppleBean();}@TodoScope@Providespublic GreenTeaBean providerDrink() { return new GreenTeaBean();}
}
@Module()
public class FruitModule {@Providespublic AppleBean providerApple() { return new AppleBean();}@Providespublic 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的组织:
- 更好的管理Component之间的组织方式,不管是依赖方式还是包含方式,都有必要用自定义的Scope注解标注这些Component,这些注解最好不要一样了,不一样是为了能更好的体现出Component之间的组织方式。还有编译器检查有依赖关系或包含关系的Component,若发现有Component没有用自定义Scope注解标注,则会报错。
- 更好的管理Component与Module之间的匹配关系,编译器会检查 Component管理的Modules,若发现标注Component的自定义Scope注解与Modules中的标注创建类实例方法的注解不一样,就会报错。
- 可读性提高,如用Singleton标注全局类,这样让程序猿立马就能明白这类是全局单例类。
总结
现在,对Scope有了全新的认识,我想文章开始的那两个的疑惑,也迎刃而解了。
- 不管是PerActvitiy还是PerFragment,其真没有与Activity/Fragment的生命周期没有任何关联,而是当前Component在注入实例时,注入实例的方式,是注入调用构造函数新建实例还是注入缓存实例。对于,公用的Component对Scope要慎用!!!
- 对于@Singleton而言,其并不是严格意义上的单例模式,而是在当前Component中,调用构造函数新建实例,然后每次注入实例时,都会读取缓存中的实例。对于其他Component而言,是无效的。所以说,针对于一个App的全局类实例,应创建一个ApplicationComponent管理。
参考文档
- 浅析Dagger2的使用
- Android:dagger2让你爱不释手-基础依赖注入框架篇
- Android:dagger2让你爱不释手-重点概念讲解、融合篇
- Android:dagger2让你爱不释手-终结篇
- “一盘沙拉”带你入门Dagger2(一)之HelloWorld
- Dagger2图文完全教
- 为什么网上这么多dagger2教程,我还写了这篇文章。
- Android常用开源工具(1)-Dagger2入门
- Dagger 2从浅到深(四)
- Dagger 2从浅到深(一)
- Dagger 2从浅到深(二)
- Dagger 2从浅到深(三)
- Dagger 2从浅到深(五)
- Dagger 2从浅到深(六)
- Dagger 2从浅到深(七)
- Dagger 2
- Dagger 2学习笔记
- Dagger 2 入门
- Dagger 2 before
- Dagger 2 初体验
- dagger 2 详解
- Dagger
- Dagger
- dagger
- dagger
- Dagger 2应用于Android的完美扩展库-dagger.android
- 史上最全最权威的Android Studio插件整理
- HTML页面加载完毕后运行某个js
- 每天坚持爬楼梯、拒绝电梯!
- Java面向对象设计模式(五)——原型模式
- HttpClient如何提交list
- Dagger 2从浅到深(四)
- 03-MySQL-视图的操作
- 信息安全——安全威胁
- windows部署React-Native的开发环境实践(技术细节)
- docker笔记之构建nginx的Dockerfile
- onerror远程移动调试 收集报错信息
- 未来的WEB工程设想
- 整数转换为罗马字符
- Java基础知识讲解(持续更新)