zoukankan      html  css  js  c++  java
  • Dagger2 探索记3——两大进阶组件(一)

           今天要讲的时@Scope这个组件。为什么说它是进阶组件,就是因为它基本上没作用,但在理解了基本组件之后又必须用到。

           Scope的意思是作用域,一般用来标记@Provide方法,将生成的对象单例化。但@Scope不能直接使用,需要先实现。默认实现有@Singleton。

    一 局部单例

           话说千百句,不如码二行。

           直接上代码,就不在原来的代码上改了,另起一个Activity,命名为SecondActivity。

    public class SecondActivity extends AppCompatActivity {
    @BindView(R.id.second_text_1) TextView text1;
    @BindView(R.id.second_text_2) TextView text2;
    @BindView(R.id.second_text_3) TextView text3;
    @BindView(R.id.second_text_4) TextView text4;

    @Inject Coffee coffee1;
    @Inject Coffee coffee2;
    @Inject Tools tool1;
    @Inject Tools tool2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.second);
    ButterKnife.bind(this);

    SecondComponent component = DaggerSecondComponent.builder()
    .secondModule(new SecondModule())//添加Module
    .build();

    coffee1 = component.coffee();
    coffee2 = component.coffee();
    tool1 = component.tools();
    tool2 = component.tools();

    text1.setText(coffee1.toString());
    text2.setText(coffee2.toString());
    text3.setText(tool1.toString());
    text4.setText(tool2.toString());
    }
    }

           然后是Layout命名为second。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/second_text_1"
    />

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/second_text_2"
    />

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/second_text_3"
    />

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/second_text_4"
    />

    </LinearLayout>

            工具用过了,这次我们改喝Coffee。

    public class Coffee {
        public Coffee(){};
    }

            其实没啥变化,主要是不和之前的代码混淆,影响理解。

            时刻记住,一个阶段结束了,就要换个起点重新开始。

            下面是Module和Component:

    @Module
    public class SecondModule {

    @Singleton
    @Provides
    Coffee provideCoffee(){
    return new Coffee();
    }

    @Provides
    Tools provideTools(){
    return new Tools();
    }
    }
    @Singleton
    @Component(modules = SecondModule.class)
    public interface SecondComponent {
    Coffee coffee();

    Tools tools();
    }

              细心的小伙伴看完这些代码就会发现,这次的Component里面,没有写inject()方法!

              没错,记得我前面说过,在使用inject()方法之前,返回的是一个Component。

              还标了是考点,不记得的小伙伴重修一遍第一章。

              所以这次是手动调用Component中的方法来进行赋值。

              最后在MainActivity中加个跳转Button,layout我就不贴了,代码看多了容易脱发。

        @OnClick({R.id.turn_firstactivity,R.id.turn_secondactivity})
        public void onViewClicked(View view) {
            switch (view.getId()){
                case R.id.turn_firstactivity:
                    startActivity(new Intent(this, FirstActivity.class));
                    break;
                case R.id.turn_secondactivity:
                    startActivity(new Intent(this, SecondActivity.class));
                    break;
            }
        }

              是不是突然发现了ButterKnife的好处,简化了很大一部分代码,直接依靠id设置点击事件,不用管是不是Button。

              来我们好好看看DaggerComponen中变化的源码。

      private void initialize(final Builder builder) {
    
        this.provideCoffeeProvider =
            DoubleCheck.provider(SecondModule_ProvideCoffeeFactory.create(builder.secondModule));
    
        this.provideToolsProvider = SecondModule_ProvideToolsFactory.create(builder.secondModule);
      }
    
      @Override
      public Coffee coffee() {
        return provideCoffeeProvider.get();
      }
    
      @Override
      public Tools tools() {
        return provideToolsProvider.get();
      }

            重写了Component中的两个方法,并各自对应上了相应的工厂类的实例,直接return get()方法。

            运行一下,发现结果是

            两个用@Singleton标记了@Provide的Coffee实例的哈希地址一致,因为是同一个对象。(有兴趣的小朋友可以更改一个类的成员变量,看看另一个会不会变。)

            @Singleton就是这个用处,叫做局部单例。注意,不是全局单例,只在同一个Activity中是相同的,跨Activity就不同了。

            注意用@Singleton标记的@Provide的@Module绑定@Component时,@Component也要标记@Singleton,表示@Component中有@Singleton单例方法。

            这句话很绕,多看几遍就懂了。

            至于单例是怎么实现的,我们看看源码。

        this.provideCoffeeProvider =
            DoubleCheck.provider(SecondModule_ProvideCoffeeFactory.create(builder.secondModule));
    
        this.provideToolsProvider = SecondModule_ProvideToolsFactory.create(builder.secondModule);

           问题就出在这个DoubleCheck上面,有兴趣的朋友可以去看看DoubleCheck的实现源码,我太懒了,懒得讲。

           那么,既然@Singleton是局部单例,怎么实现全局单例呢?接下来就来讲。

    二 全局单例

           前面说过,@Scope是作用区域,我们使用的是在Activity中实例化,所以是在Activity中局部单例。那么全局单例就得在Application中实例化。

           我们先写个自己的Application来注入依赖。

    public class MyApplication extends Application {
    
        private static GlobalComponent component;
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            component = DaggerGlobalComponent.builder()
                    .globalModule(new GlobalModule())
                    .build();
        }
    
        GlobalComponent get(){
            return component;
        }
    }

             因为重写了Application,需要在Manifiest中把它添加进去

        <application
            android:name="MyApplication"
            ......
        </application>

            给全局的Application注入,前面写的很清楚了,后面直接贴代码。

            再来个类Global,全球化一点。

    public class Global {
        public Global(){};
    }

            Component:

    @Component(modules = GlobalModule.class)
    public interface GlobalComponent {
    
        Global global();
    
    }

             Module:

    @Module
    public class GlobalModule {
    
        @Provides
        Global provideGlobal(){
            return new Global();
        }
    
    }

           如果前面好好听讲了的同学,现在应该已经能看懂了。我们在MyApplication中注入了一个Component的实例,并写了个get()方法来获取它。

           后续需要就直接获取MyApplication中的Component实例即可。

           例如前面的SecondActivity怎么注入Global实例呢?仅仅需要在SecondComponent中依赖GlobalComponent即可,可以理解为继承。

           看代码!

    @Singleton
    @Component(modules = SecondModule.class,dependencies = GlobalComponent.class)
    public interface SecondComponent {
        void inject(SecondActivity activity);
    }

          在括号中用dependence即可,这里我要特别说一点. 我说完了。

          可能又有细心的盲僧发现了华点。没错,怎么又改回用inject了?难道dependence就必须用inject方法吗?

          先表扬一下这位同学,观察很细致。

          答案是:不是。只是我懒得在后面写代码了。

          改回来多方便,前面是为了给你们演示才特意写了一下。像我这么懒的人自然是怎么简单怎么来。

          SecondActivity也改了,记得要编译,不然会标红。

    public class SecondActivity extends AppCompatActivity {
        @BindView(R.id.second_text_1)    TextView text1;
        @BindView(R.id.second_text_2)    TextView text2;
        @BindView(R.id.second_text_3)    TextView text3;
        @BindView(R.id.second_text_4)    TextView text4;
    
        @Inject    Coffee coffee1;
        @Inject    Coffee coffee2;
        @Inject    Global global1;
        @Inject    Global global2;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.second);
            ButterKnife.bind(this);
    
            DaggerSecondComponent.builder()
                    .globalComponent(new MyApplication().get())
                    .secondModule(new SecondModule())//添加Module
                    .build()
                    .inject(this);
    
            text1.setText(coffee1.toString());
            text2.setText(coffee2.toString());
            text3.setText(global1.toString());
            text4.setText(global2.toString());
        }
    }

             把之前的参照组改成了实验组,实验组改成了参照组。(上过初中生物的理科生应该懂。)

             因为绑定了SecondMoudle所以调用secondMoudle,这次继承了GlobalComponent,自然也要调用,输入参数是Component。

             跑一下就能发现,global注入进去了。

             但是,很明显不是单例。因为我们还没有给它变形。

             好了,敲黑板,别低头看书了,都看我,我要开始变形了。

             先讲一下要点。@Scope不能直接用,我们之前用的是默认实现的一个@Singleton,先看看它的源码。

    @Scope
    @Documented
    @Retention(RUNTIME)
    public @interface Singleton {}

              我们自己也来设置一个:

    @Scope
    @Documented
    @Retention(RUNTIME)
    public @interface MyScope {
    }

             很简单对不对?照着抄就好了,就改了个名字。

             要是爱情也这么简单就好了。

             现在是动手时间,把SecondModule和SecondComponent中的@Singleton替换成@MyScope,然后跑一遍,看看结果是不是一样的。

             我去上个厕所......

             跑好的同学,去给GlobalModule和GlobalComponent中加上@Singleton。再跑一遍。

             一般上课都是这样,前三十分钟老师讲课,后十五分钟学生做练习。

             我写些代码,你们也要练一下。

             让我猜猜,有些调皮的同学,趁着老师上厕所没回来的时间,偷偷看了后面的练习题,想要偷个懒。

             于是很省力的,直接把@MyScope加在了GlobalModule和GlobalComponent里面。

             然后就报错了。

    错误: This @Singleton component cannot depend on scoped components:
    @com.yuanxixing.dagger2.MyScope com.yuanxixing.dagger2.GlobalComponent

             必须先表扬这一批同学,选择了少有人走的路。

             哪怕是错的,也只是发现了一条不可行的方法!

             确实,这是一个隐藏的成就。

             先讲讲Component的继承,也就是Dependence。

             一个被@Scope标记的Component,在被继承时,继承它的Component也必须被@Scope标记。这里的原因应该是,继承Component时,就继承了它提供的方法,但是我们之前说过,给Component标记@Scope就是意味着里面有单例的@Provide,所以继承了单例@Provde的Component也要打上@Scope。

             并且这两个@Scope还不能相同。我前面要求做法是用@MyScope标记的SecondComponent来dependence被@Singleton标记的GlobalComponent。

             但是有的同学反过来试了,用@Singleton标记的SecondComponent来dependence被@MyScope标记的GlobalComponent于是就报错了。

             我也尝试了几种情况,用两个自定义的@Scope标记Component继承,没问题。

             但是@Singleton就是不一样,它标记的Component就是只能被dependence,它就是高人一等,它标记的Component不需要也不能依靠别人。

             所以,记住就行。

             好了,最后老师再辛苦一下,写个ThridActivity测试一下Global是不是全局单例。

    public class ThridActivity extends AppCompatActivity {
        @BindView(R.id.thrid_text_1)    TextView text1;
        @BindView(R.id.thrid_text_2)    TextView text2;
        @BindView(R.id.thrid_text_3)    TextView text3;
        @BindView(R.id.thrid_text_4)    TextView text4;
    
        @Inject    Coffee coffee1;
        @Inject    Coffee coffee2;
        @Inject    Global global1;
        @Inject    Global global2;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.thrid);
            ButterKnife.bind(this);
    
            DaggerSecondComponent.builder()
                    .globalComponent(new MyApplication().get())
                    .secondModule(new SecondModule())//添加Module
                    .build()
                    .inject(this);
    
            text1.setText(coffee1.toString());
            text2.setText(coffee2.toString());
            text3.setText(global1.toString());
            text4.setText(global2.toString());
        }
    }

               layout的代码:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/thrid_text_1"
            />
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/thrid_text_2"
            />
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/thrid_text_3"
            />
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/thrid_text_4"
            />
    
    </LinearLayout>

               看得出来我使用的还是DaggerSecondComponent来注入,所以我需要再SecondComponent中加上代码,适配一下ThrdActivity。

    @MyScope
    @Component(modules = SecondModule.class,dependencies = GlobalComponent.class)
    public interface SecondComponent {
        void inject(SecondActivity activity);
        void inject(ThridActivity activity);
    }

              然后在SecondActivity中加入一个点击跳转,button自己去加。

        @OnClick({R.id.turn_thridactivity})
        public void onViewClicked(View view) {
            startActivity(new Intent(this, ThridActivity.class));
        }

               然后跑完就可以看。。。。。。

               妈的!虚拟机崩了。

              算了,你们自己跑吧!

              下一章再讲源代码。

  • 相关阅读:
    Javascript的IE和Firefox兼容性汇编(转)
    JSP里request变量列表
    C#中的接口(转)
    寒羽对帝国的理解(通向高手之路)(转)
    怎样获得Sqlserver 2000得实例列表和运行在一个实例上得数据库列表(转)
    jsp文件上传的处理(转)
    用户不上你的网站的50个原因
    慈勤强的JFolder1.0版代码(JSP)
    管理常用SQL语句
    .net 取得访问的url
  • 原文地址:https://www.cnblogs.com/yuanxixing/p/11538001.html
Copyright © 2011-2022 走看看