Dagger 2从浅到深(四)

来源:互联网 发布:linux 压缩指令 编辑:程序博客网 时间:2024/05/16 18:01

Dagger系列:

  1. Dagger 2从浅到深(一)
  2. Dagger 2从浅到深(二)
  3. Dagger 2从浅到深(三)
  4. Dagger 2从浅到深(四)
  5. Dagger 2从浅到深(五)
  6. Dagger 2从浅到深(六)
  7. Dagger 2从浅到深(七)
  8. Dagger 2应用于Android的完美扩展库-dagger.android

Scope,翻译过来的意思是作用域。作用域的理解是变量的作用范围,因此,出现了全局变量和局部变量之分。而,Android开发中,首先也是必须了解的是Activity或者Fragment的生命周期,其决定了在不同的生命周期内,应该做哪些工作。Scope与Activity或者Fragment的生命周期又有什么联系呢?

于是乎,网上的很多例子,将Scope与Activity或者Fragment的生命周期相结合:自定义一个PerActivity/PerFragment注解,那创建的类实例就与Activity/PerFragment“共生死”。

或者用Singleton注解标注一个创建类实例的方法,该创建类实例的方法就可以创建一个唯一的类实例。

当时,一无所知,也就是这么认为了,跳进了这深深的大坑。对PerActivity/PerFragment,或者Singleton产生了深深的疑惑:

  1. Scope到底是什么?如何决定作用域的,是不是与变量本身的作用域一致?
  2. 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());    }    ***}

从下图,我们可以清晰的看出:

  1. OrangeBean的构造函数被调用了一次,而BananaBean的构造函数被调用了两次。
  2. 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. 步骤1:查找Module中是否存在创建该类的方法。
  2. 步骤2:若存在创建类方法,查看该方法是否存在参数

    1. 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
    2. 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
  3. 步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数

    1. 步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
    2. 步骤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());    }    ***}

从下图中,我们可以看出:

  1. 如果依赖实例的注入来源是@Provides方法时,@Provides方法必须被@Scope注解;如果依赖实例的注入来源是@Inject注解的构造函数时,实例类必须被@Scope注解。这样@Scope注解才会有效。也就是说,@Scope实际上是对注入器的控制。

  2. Scope控制的实例的注入器是当前Component之内的实例注入器,而不会影响其他的Component中的实例注入器。

这里写图片描述

所以,Scope的真正作用还是依赖于Component的组织:

  • 更好的管理Component之间的组织方式,不管是依赖方式还是包含方式,都有必要用自定义的Scope注解标注这些Component,这些注解最好不要一样了,不一样是为了能更好的体现出Component之间的组织方式。还有编译器检查有依赖关系或包含关系的Component,若发现有Component没有用自定义Scope注解标注,则会报错。
  • 更好的管理Component与Module之间的匹配关系,编译器会检查 Component管理的Modules,若发现标注Component的自定义Scope注解与Modules中的标注创建类实例方法的注解不一样,就会报错。
  • 可读性提高,如用Singleton标注全局类,这样让程序猿立马就能明白这类是全局单例类。

总结

现在,对Scope有了全新的认识,我想文章开始的那两个的疑惑,也迎刃而解了。

  1. 不管是PerActvitiy还是PerFragment,其真没有与Activity/Fragment的生命周期没有任何关联,而是当前Component在注入实例时,注入实例的方式,是注入调用构造函数新建实例还是注入缓存实例。对于,公用的Component对Scope要慎用!!!
  2. 对于@Singleton而言,其并不是严格意义上的单例模式,而是在当前Component中,调用构造函数新建实例,然后每次注入实例时,都会读取缓存中的实例。对于其他Component而言,是无效的。所以说,针对于一个App的全局类实例,应创建一个ApplicationComponent管理。

参考文档

  1. 浅析Dagger2的使用
  2. Android:dagger2让你爱不释手-基础依赖注入框架篇
  3. Android:dagger2让你爱不释手-重点概念讲解、融合篇
  4. Android:dagger2让你爱不释手-终结篇
  5. “一盘沙拉”带你入门Dagger2(一)之HelloWorld
  6. Dagger2图文完全教
  7. 为什么网上这么多dagger2教程,我还写了这篇文章。
  8. Android常用开源工具(1)-Dagger2入门
1 0
原创粉丝点击