Dagger 2 系列(五) -- 进阶篇:@Scope 和 @Singleton

2024-02-06 20:32

本文主要是介绍Dagger 2 系列(五) -- 进阶篇:@Scope 和 @Singleton,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Dagger2

  • 该系列博客的最终目标: 搭建 MVP + Dagger2 框架
  • 该系列博客包含以下几篇内容:
  1. Dagger 2 系列(一) – 前奏篇:依赖注入的基本介绍
  2. Dagger 2 系列(二) – 基础篇:@Inject、@Component
  3. Dagger 2 系列(三) – 基础篇:@Module 和@Provides
  4. Dagger 2 系列(四) – 基础篇:@Named 和 @Qualifier
  5. Dagger 2 系列(五) – 进阶篇:@Scope 和 @Singleton

在这篇文章中你会看到什么:

  1. @Scope 是什么
  2. @Singleton 是什么
  3. @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 的命名没有任何关系,名字避免使用了容易给人造成疑惑的 ApplicationScopeActivityScope 等,而使用了 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 提供 AppleBeanOrgranBean 实例对象的方法,两个方法使用 @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());}
}

如代码所示,在完成注入后分别对 AppleBeanOrgranBean 分别调用了两次实例,按照上文中 我们对 @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 的实例 – mFruitComponentAppleBean 的两个实例 – mAppleBeanAmAppleBeanBOrgranBean 的两个实例 – mOrgranBeanAmOrgranBeanB,发现 mAppleBeanAmAppleBeanB 的哈希值相同即为 同一个对象mOrgranBeanAmOrgranBeanB 的哈希值相同即为 同一个对象,验证了我们对 @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

很明显 AppleBeanOrgranBean 重新生成了新的对象,难道不是单例了?难道上文的结论是错误的?其实不是这样的,因为 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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/685448

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

GPT系列之:GPT-1,GPT-2,GPT-3详细解读

一、GPT1 论文:Improving Language Understanding by Generative Pre-Training 链接:https://cdn.openai.com/research-covers/languageunsupervised/language_understanding_paper.pdf 启发点:生成loss和微调loss同时作用,让下游任务来适应预训

Java基础回顾系列-第七天-高级编程之IO

Java基础回顾系列-第七天-高级编程之IO 文件操作字节流与字符流OutputStream字节输出流FileOutputStream InputStream字节输入流FileInputStream Writer字符输出流FileWriter Reader字符输入流字节流与字符流的区别转换流InputStreamReaderOutputStreamWriter 文件复制 字符编码内存操作流(

Java基础回顾系列-第五天-高级编程之API类库

Java基础回顾系列-第五天-高级编程之API类库 Java基础类库StringBufferStringBuilderStringCharSequence接口AutoCloseable接口RuntimeSystemCleaner对象克隆 数字操作类Math数学计算类Random随机数生成类BigInteger/BigDecimal大数字操作类 日期操作类DateSimpleDateForma

Java基础回顾系列-第三天-Lambda表达式

Java基础回顾系列-第三天-Lambda表达式 Lambda表达式方法引用引用静态方法引用实例化对象的方法引用特定类型的方法引用构造方法 内建函数式接口Function基础接口DoubleToIntFunction 类型转换接口Consumer消费型函数式接口Supplier供给型函数式接口Predicate断言型函数式接口 Stream API 该篇博文需重点了解:内建函数式

Java基础回顾系列-第二天-面向对象编程

面向对象编程 Java类核心开发结构面向对象封装继承多态 抽象类abstract接口interface抽象类与接口的区别深入分析类与对象内存分析 继承extends重写(Override)与重载(Overload)重写(Override)重载(Overload)重写与重载之间的区别总结 this关键字static关键字static变量static方法static代码块 代码块String类特

Java基础回顾系列-第六天-Java集合

Java基础回顾系列-第六天-Java集合 集合概述数组的弊端集合框架的优点Java集合关系图集合框架体系图java.util.Collection接口 List集合java.util.List接口java.util.ArrayListjava.util.LinkedListjava.util.Vector Set集合java.util.Set接口java.util.HashSetjava