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

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

  1. @Scope 是什么
  2. @Singleton 是什么
  3. @Scope@Component 如何协同做战。

Dagger2 的学习曲线确实是比较陡的,我认为陡的点一是对 依赖注入(控制反转)概念的理解,因此有了Dagger 2 系列(一) -- 前奏篇:依赖注入的基本介绍,另一个就是 对 Scope 的理解,对于此我也是翻看了大量的博客,大部分博客看完以后的感觉依旧是云里雾里的,本身也是通过了学习、搁浅、再学习的往复过程,同时也看了一些国外的博客,对 Scope 的概念有了基本的认识。git

1. @Scope

咱们首先看一下 froer_mcs 一文中谈到 Scope 能给咱们带来什么github

  • 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).api

  • In short - scopes give us “local singletons” which live as long as scope itself.bash

  • Annotated dependencies are single-instances but related to component lifecycle (not the whole application).app

我的翻译框架

Dagger2 中 Scope 机制保证在 Scope 的做用域内类会保持单例。在实际开发中这意味着在 @ApplicationScope 对应的做用域中类的实例对象的生命会像 Application 同样长,在 @ActivityScope 的做用域内的类实例的生命周期和相应的 Activity 同样长。(不要想固然的认为 Dagger2 会根据 Scope 注解的字面意义实现相应的类实例的单例效果,实现这样的效果是须要具体实现的。) 总的来讲, Scope 机制会保证在 Scope 的生命周期内实现 "本地单例" 在 Component 的生命周期内,Scope 注解依赖会保证单例。(也就是说,此处的单例是 Component 生命周期内的单例,若是 Component 实例对象从新实例化的,则单例效果失效。)ide

经过以上的引用和翻译不知道你是否从新认识了 Scope ,在上文中一个反复强调的概念:post

在 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
    @Provides
    AppleBean provideApple() {
        return new AppleBean();
    }

    @AnyOneScope
    @Provides
    OrgranBean 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 {

    @Inject
    AppleBean mAppleBeanA;
    @Inject
    AppleBean mAppleBeanB;
    @Inject
    OrgranBean mOrgranBeanA;
    @Inject
    OrgranBean mOrgranBeanB;
    FruitComponent mFruitComponent;

    @Override
    protected 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@7c6e469
    mAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eee
    mAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eee
    mOrgranBeanA:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8f
    mOrgranBeanB: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: AppleBean
    OrgranBean
    mFruitComponent1:com.example.administrator.dagger2demo.practiceFifth.DaggerFruitComponent@7c6e469
    mAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eee
    mAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eee
    mOrgranBeanA: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: AppleBean
    OrgranBean
    mFruitComponent2:com.example.administrator.dagger2demo.practiceFifth.DaggerFruitComponent@8196f9e
    mAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@50ada7f
    mAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@50ada7f
    mOrgranBeanA:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@16bea4c
    mOrgranBeanB: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

相关文章
相关标签/搜索