本文主要是介绍Dagger 2 系列(五) -- 进阶篇:@Scope 和 @Singleton,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
- 该系列博客的最终目标: 搭建 MVP + Dagger2 框架
- 该系列博客包含以下几篇内容:
- Dagger 2 系列(一) – 前奏篇:依赖注入的基本介绍
- Dagger 2 系列(二) – 基础篇:@Inject、@Component
- Dagger 2 系列(三) – 基础篇:@Module 和@Provides
- Dagger 2 系列(四) – 基础篇:@Named 和 @Qualifier
- Dagger 2 系列(五) – 进阶篇:@Scope 和 @Singleton
在这篇文章中你会看到什么:
@Scope
是什么@Singleton
是什么@Scope
和@Component
如何协同作战。
Dagger2
的学习曲线确实是比较陡的,我认为陡的点一是对 依赖注入(控制反转)概念的理解,所以有了Dagger 2 系列(一) – 前奏篇:依赖注入的基本介绍,另外一个就是 对 Scope 的理解,对于此我也是翻看了大量的博客,大部分博客看完之后的感受依旧是云里雾里的,自己也是经过了学习、搁浅、再学习的往复过程,同时也看了一些国外的博客,对 Scope
的概念有了基本的认识。
1. @Scope
我们首先看一下 froer_mcs 在 一文中谈到 Scope 能给我们带来什么
In Dagger 2 scopes mechanism cares about keeping single instance of class as long as its scope exists. In practice it means that instances scoped in
@ApplicationScope
lives as long as Application object.@ActivityScope
keeps references as long as Activity exists (for example we can share single instance of any class between all fragments hosted in this Activity).In short - scopes give us “local singletons” which live as long as scope itself.
Annotated dependencies are single-instances but related to component lifecycle (not the whole application).
个人翻译
Dagger2 中 Scope 机制保证在 Scope 的作用域内类会保持单例。在实际开发中这意味着在 @ApplicationScope 对应的作用域中类的实例对象的生命会像 Application 一样长,在 @ActivityScope 的作用域内的类实例的生命周期和相应的 Activity 一样长。(不要想当然的认为 Dagger2 会根据 Scope 注解的字面意义实现相应的类实例的单例效果,实现这样的效果是需要具体实现的。)
总的来说, Scope 机制会保证在 Scope 的生命周期内实现 "本地单例"
在 Component 的生命周期内,Scope 注解依赖会保证单例。(也就是说,此处的单例是 Component 生命周期内的单例,如果 Component 实例对象重新实例化的,则单例效果失效。)
通过以上的引用和翻译不知道你是否重新认识了 Scope ,在上文中一个反复强调的概念:
在 Dagger2 中 Scope 机制可以保证在 Scope 标记的 Component 作用域内 ,类会保持单例 。 (敲黑板,这句话很重要)
2. @Singleton
重申一遍:
**在 Dagger2 中 Scope 机制可以保证在 Scope 标记的 Component 作用域内 ,类会保持单例 **
如果理解了这句话,那么回过头来看 @Singleton
这个注解,是不是有一种豁然开朗的感觉。并不是只有 @Singleton
注解标记的相关类生产的实例是单例的,是所有的 Scope(自定义 Scope)
标记的相关类生产的实例 都是单例 的,只不过这个单例是有条件的 – 在 Scope 注解标记 Component 的作用域内生产的实例是单例的 。
Scope 机制下的单例其实和 @Singleton
的字面意义 没有半毛钱关系,当初自己就是被这种错误的思想误导了很长时间。其实如果你愿意你,可以把 @Singleton
换成任意单词,什么 @Dog
、@Cat
、@XXx
都可以,你只要保证这个注解标记的 Component 在 App 进程中为单例的,并且得到正确的实现(被正确的标记到 类构造器
或 Module 中的 @Provides
标记的方法),那么它对应生成的类实例就是 单例 的。
@Singleton
之所以被默认实现,只是因为这可以让人根据它的字面意思,知道被他标记的相关生成的类实例为单例,这符合了 Java 的命名规范。
3. 示例代码
上面谈到的全都是理论,那么我们就是用相应的代码来验证他们。
- 自定义 Scope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface AnyOneScope {
}
这里为了表明最后的 单例 和 Scope
的命名没有任何关系,名字避免使用了容易给人造成疑惑的 ApplicationScope
、ActivityScope
等,而使用了 AnyOneScope
,但是其实这些名字都是无所谓的 。
- POJO – AppleBean
public class AppleBean {private String color;private int weight;public AppleBean() {Log.e("TAG", "AppleBean");}
}
- POJO – OrgranBean
public class OrgranBean {private String color;private int weight;public OrgranBean() {Log.e("TAG", "OrgranBean");}
}
- Module
@Module
public class FruitModule {@AnyOneScope@ProvidesAppleBean provideApple() {return new AppleBean();}@AnyOneScope@ProvidesOrgranBean provideOrgran() {return new OrgranBean();}
}
该 Module
提供 AppleBean
、OrgranBean
实例对象的方法,两个方法使用 @AnyOneScope 进行注解。
- Component
@AnyOneScope
@Component(modules = {FruitModule.class})
public interface FruitComponent {void inject(FuriteScopeActivity mTestScopeActivity);
}
- 目标类 (注入类)
public class FuriteScopeActivity extends AppCompatActivity {@InjectAppleBean mAppleBeanA;@InjectAppleBean mAppleBeanB;@InjectOrgranBean mOrgranBeanA;@InjectOrgranBean mOrgranBeanB;FruitComponent mFruitComponent;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test_scope);mFruitComponent = DaggerFruitComponent.builder().fruitModule(new FruitModule()).build();mFruitComponent.inject(this);// 完成注入,没有这句话是不行的Log.e("TAG", "mFruitComponent1:" + mFruitComponent.toString());Log.e("TAG", "mAppleBeanA:" + mAppleBeanA.toString());Log.e("TAG", "mAppleBeanB:" + mAppleBeanB.toString());Log.e("TAG", "mOrgranBeanA:" + mOrgranBeanA.toString());Log.e("TAG", "mOrgranBeanB:" + mOrgranBeanB.toString());}
}
如代码所示,在完成注入后分别对 AppleBean
、OrgranBean
分别调用了两次实例,按照上文中 我们对 @Scope
的理解,那么在这里两个类分别生成的类实例为同一个,下面我们运行代码,查看日志来验证一下。
打印日志如下:
E/TAG: mFruitComponent1:com.example.administrator.dagger2demo.practiceFifth.DaggerFruitComponent@7c6e469mAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eeemAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eeemOrgranBeanA:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8fmOrgranBeanB:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8f
可以看到日志分别打印了 FruitComponent 的实例 – mFruitComponent
,AppleBean 的两个实例 – mAppleBeanA
、mAppleBeanB
,OrgranBean 的两个实例 – mOrgranBeanA
、mOrgranBeanB
,发现 mAppleBeanA
和 mAppleBeanB
的哈希值相同即为 同一个对象、mOrgranBeanA
、mOrgranBeanB
的哈希值相同即为 同一个对象,验证了我们对 @Scope
的认识 – 实现单例 。
同时,为了验证 单例 是有作用域的 – Component 的作用域内,在 FuriteScopeActivity
添加以下方法:
public void jump(View view) {mFruitComponent = DaggerFruitComponent.builder().fruitModule(new FruitModule()).build();mFruitComponent.inject(this);// 完成注入,没有这句话是不行的Log.e("TAG", "mFruitComponent2:" + mFruitComponent.toString());Log.e("TAG", "mAppleBeanA:" + mAppleBeanA.toString());Log.e("TAG", "mAppleBeanB:" + mAppleBeanB.toString());Log.e("TAG", "mOrgranBeanA:" + mOrgranBeanA.toString());Log.e("TAG", "mOrgranBeanB:" + mOrgranBeanB.toString());}
重新运行程序,观察打印日志:
- 运行程序后触发的日志信息:
09-21 14:50:54.903 14184-14184/com.example.administrator.dagger2demo E/TAG: AppleBeanOrgranBeanmFruitComponent1:com.example.administrator.dagger2demo.practiceFifth.DaggerFruitComponent@7c6e469mAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eeemAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eeemOrgranBeanA:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8f
09-21 14:50:54.904 14184-14184/com.example.administrator.dagger2demo E/TAG: mOrgranBeanB:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8f
生成的对象和哈希值的对应关系为:
- DaggerFruitComponent --> 7c6e469
- AppleBean --> b7d6eee
- OrgranBean --> 648ef8f
- 触发 jump() 方法后的日志信息如下:
09-21 14:53:37.624 14184-14184/com.example.administrator.dagger2demo E/TAG: AppleBeanOrgranBeanmFruitComponent2:com.example.administrator.dagger2demo.practiceFifth.DaggerFruitComponent@8196f9emAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@50ada7fmAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@50ada7fmOrgranBeanA:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@16bea4cmOrgranBeanB:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@16bea4c
生成的对象和哈希值的对应关系为:
- FruitComponent --> 8196f9e
- AppleBean --> 50ada7f
- OrgranBean --> 16bea4c
很明显 AppleBean
、OrgranBean
重新生成了新的对象,难道不是单例了?难道上文的结论是错误的?其实不是这样的,因为 FruitComponent
生成了新的对象,所以其作用域下的类重新生成了新的实例。
证明了:在 Scope 注解标记 Component 的作用域内生产的实例是单例的。
4. 总结
至此,对 Dagger2
中的 Scope
的理解就如上文所示了,我认为内容比较容易内容理解,而且比较浅显,不会增添你的疑惑,希望自己的这篇学习记录可以帮助到看到的所有同学。
GitHub–Dagger2Demo
参考资料
Dependency injection with Dagger 2 - the API
Dependency injection with Dagger 2 - Custom scopes
这篇关于Dagger 2 系列(五) -- 进阶篇:@Scope 和 @Singleton的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!