Dagger2教程五之单例模式(原)

Posted 工程师阿杜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dagger2教程五之单例模式(原)相关的知识,希望对你有一定的参考价值。

        为了介绍Dagger2的使用,我们搭建了一个Demo来逐步分析,大家可以在这里下载源码(https://github.com/dushaofeng/DaggerDemo.git)。

        上一节我们介绍了《Dagger2教程四之多构造方法的情况》,这一节我们来介绍Dagger2中的单例模式的使用。

        Dagger2中有一个极其具有迷惑性的注释" @Singleton",字面意思感觉可以达到单例模式的作用,但实际上他的这个"单例"有点假。
        为什么说有点假呢?因为他的确可以在一些情况下达到单例的作用,但是又和我们通常意义上的单例模式相差很远,用错了,可能会适得其反。


一、Singleton注释的使用

        我们先来看一下这个注释的使用方法。
        如果我们在目标类中存在两个同样的注入类,它们实际上是两个不同的对象,请看这个定义:
        @Inject
        BeanNeedParam mBeanNeedParamCC;
        @Inject
        BeanNeedParam mBeanNeedParamDD;
        这说明Activity中的mBeanNeedParamCC和mBeanNeedParamDD两个变量都需要用BeanNeedParam来注入,接下来我们看它们是分别被注入,还是用同一个对象进行注入:
        //测试注入对象的地址
        if (mBeanNeedParamCC != null) 
            Log.d(TAG, "mBeanNeedParamCC 地址:" + mBeanNeedParamCC);
        
        if (mBeanNeedParamDD != null) 
            Log.d(TAG, "mBeanNeedParamDD 地址:" + mBeanNeedParamDD);
        
        其结果如图所示:
        
        我们看到这两个对象的地址不同,这表明, 虽然mBeanNeedParamCC和mBeanNeedParamDD都是用BeanNeedParam进行注入,但是它们是分别单独注入的,每个对象都是独立的
        如果我们想要实现单例模式该如何办呢?比如我们想要mBeanNeedParamCC和mBeanNeedParamDD是同一个对象该怎么办呢?
        这时候就可以用Singleton的注释了,进行如下改造:


1.1、改造BeanModule中的方法

        在提供BeanNeedParam的方法上面添加@Singleton注释:
        @Singleton
        @Provides
        public BeanNeedParam providerBean(String name) 
            BeanNeedParam bean = new BeanNeedParam(name);
            return bean;
        


1.2、改造BeanComponent

        需要在BeanComponent中添加注释:
        @Singleton
        @Component(modules = BeanModule.class)
        public interface BeanComponent 
            void inject(MainActivity activity);
        


1.3、效果展示

        然后我们再次运行程序,mBeanNeedParamCC和mBeanNeedParamDD的地址就变成同一个了:
        
        它们打印出来地址相同, 这说明mBeanNeedParamCC和mBeanNeedParamDD是同一个对象,Singleton完成了单例模式的注入


二、Singleton的假单例行为

        看了上面的例子,我们发现Singleton的确实现了BeanNeedParam的单例注入,但是当我们把同样的"单例类"注入到其他类中时,再来看一下效果。
        我们现在创建一个新的类,为了方便测试,我们就 在Activity中创建一个子类OtherClass
        class OtherClass 
            @Inject
            BeanNeedParam mBeanNeedParamEE;


            @Inject
            BeanNeedParam mBeanNeedParamFF;


            public OtherClass() 
                //重新注入
                DaggerBeanComponent.create().inject(this);
                this.testDagger();
            


            private void testDagger() 
                //测试注入对象的地址
                if (mBeanNeedParamEE != null) 
                    Log.d(TAG, "mBeanNeedParamEE 地址:" + mBeanNeedParamEE);
                
                if (mBeanNeedParamDD != null) 
                    Log.d(TAG, "mBeanNeedParamFF 地址:" + mBeanNeedParamFF);
                
            
        
        这个类中我们注入了两个BeanNeedParam对象,分别时mBeanNeedParamEE和mBeanNeedParamFF,按照之前的设计,它们都是使用@Singleton标注的"单例"。
        这里由于我们需要对OtherClass进行注入,因此需要在BeanComponent中添加新的inject()方法,如下:
        @Singleton
        @Component(modules = BeanModule.class)
        public interface BeanComponent 
            //将BeanModule注入MainActivity中
            void inject(MainActivity activity);
            //将BeanModule注入OtherClass
            void inject(MainActivity.OtherClass otherClass);
        
        然后我们在MainActivity中实例化OtherClass:
        OtherClass otherClass = new OtherClass();
        运行一下,我们可以看到mBeanNeedParamCC、mBeanNeedParamDD、mBeanNeedParamEE、mBeanNeedParamFF四个对象的地址分别如下:
        
        这时我们发现,MainActivity中的mBeanNeedParamCC和mBeanNeedParamDD是同一个对象,而OtherClass中的mBeanNeedParamEE和mBeanNeedParamFF是另外的一个对象。
        这说明, @Singleton的单例作用,只对同一次的inject()有效


三、Singleton的单例作用范围

        那么再进一步来分析,inject()方法实际上是通过DaggerBeanComponent.create()创建的BeanComponent对象的一个成员方法,也就是说:
            DaggerBeanComponent.create().inject(this);
        实际上就是:
            BeanComponent beanComponent = DaggerBeanComponent.create();
            beanComponent.inject(this);
        那么这个单例的作用范围究竟是在beanComponent的范围还是inject()的范围呢?
        下面我们将MainActivity和OtherClass用同一个BeanComponent进行注入来看一下效果。
        在MainActivity中,我们将beanComponent对象传递给OtherClass:
        private void testDagger() 
            BeanComponent beanComponent = DaggerBeanComponent.create();
            beanComponent.inject(this);
            if (mBeanNeedParamCC != null) 
                Log.d(TAG, "mBeanNeedParamCC 地址:" + mBeanNeedParamCC);
            
            if (mBeanNeedParamDD != null) 
                Log.d(TAG, "mBeanNeedParamDD 地址:" + mBeanNeedParamDD);
            
            OtherClass otherClass = new OtherClass(beanComponent);
        
        在OtherClass中我们使用传递进来的beanComponent直接注入:
        class OtherClass 
            @Inject
            BeanNeedParam mBeanNeedParamEE;


            @Inject
            BeanNeedParam mBeanNeedParamFF;


            public OtherClass(BeanComponent bc) 
                //使用Activity创建的BeanComponent进行注入
                bc.inject(this);
                this.testDagger();
            


            private void testDagger() 
                //测试注入对象的地址
                if (mBeanNeedParamEE != null) 
                    Log.d(TAG, "mBeanNeedParamEE 地址:" + mBeanNeedParamEE);
                
                if (mBeanNeedParamDD != null) 
                    Log.d(TAG, "mBeanNeedParamFF 地址:" + mBeanNeedParamFF);
                
            
        
        然后运行:
        
        我们发现此时四个BeanNeedParam变量实际上是指向了同一个对象。
        这说明, 在同一个BeanComponent对象内,单例是有效的


四、让@Singleton成为全局单例

        我们知道了@Singleton的作用范围之后,就可以根据不同需求来使用它, 当我们需要全局的单例时,就可以在Application中创建Component对象,然后将其提供给需要的类,从而实现App范围内的单例模式
        具体实现步骤:
        1、在应用的Application中创建Component对象,并将其暴露出来:
        public class DaggerApplication extends Application 
            private BeanComponent mBeanComponent;


            @Override
            public void onCreate() 
                super.onCreate();
                mBeanComponent = DaggerBeanComponent.create();
            


            public BeanComponent getBeanComponent() 
                return mBeanComponent;
            
        
        2、然后就可以在Activity和OtherClass中使用该Component:
            BeanComponent beanComponent = ((DaggerApplication)getApplication()).getBeanComponent();
            beanComponent.inject(this);
        如此即实现了全局的单例模式。


五、@Scope注释

        Dagger2中的@Scope注释的作用描述如下:
    /**
     * Identifies scope annotations. A scope annotation applies to a class
     * containing an injectable constructor and governs how the injector reuses
     * instances of the type. By default, if no scope annotation is present, the
     * injector creates an instance (by injecting the type's constructor), uses
     * the instance for one injection, and then forgets it. If a scope annotation
     * is present, the injector may retain the instance for possible reuse in a
     * later injection. If multiple threads can access a scoped instance, its
     * implementation should be thread safe. The implementation of the scope
     * itself is left up to the injector.
     */
        简单来说就是没有Scope时,每次注入都创建新的对象(By default, if no scope  annotation is present, the injector creates an instance, uses  the  instance for one injection, and then forgets it.),而使用了Scope的注释以后,创建的对象会被复用,从而实现单例模式(If a scope annotation is present, the injector may retain the instance for possible reuse in a later injection.)。
        看这个描述,他与@Singleton的作用非常相似,而实际上, @Singleton注释就是@Scope的一个默认实现
        因此,我们完全可以自定义一个全新的注释来实现和@Singleton相同的作用。
        比如我们可以定义一个叫做MySingleton的注释来实现单例模式:
        @Scope
        @Documented
        @Retention(RUNTIME)
        public @interface MySingleton 
        
        而他的使用方法和作用@Singleton完全相同,这里就不再介绍了。


六、Dagger流程总结

        现在我们回头来看一下Dagger2中两大组件所起到的作用:
        Module:它用来提供各个注入类的集合,Dagger2在需要注入对象时, 将会先从Module中寻找要注入的类,找不到的话再去寻找标记了Inject的构造方法
        Component:它是目标类和注入类之间的桥梁和接口, 告诉Dagger2需要向哪里注入哪些类
        我们用一个图示来说明它们之间的关系:
        
        下一节我们来介绍 《Dagger2教程六之Component的组织方法》

以上是关于Dagger2教程五之单例模式(原)的主要内容,如果未能解决你的问题,请参考以下文章

PHP进阶教程-设计模式之单例模式

Java--设计模式之单例模式+保证线程安全的5种写法(懒汉内双枚)

设计模式之单例模式

设计模式之单例模式

JAVA设计模式之单例模式

java设计模式之单例模式