美文网首页
Dagger2使用姿势学习

Dagger2使用姿势学习

作者: m1Ku | 来源:发表于2019-06-10 10:28 被阅读0次

    前言

    Dagger2是一个用于Android和Java的快速依赖注入框架。Dagger开始由Square公司开发并维护,后来Google接手该项目开发了Dagger2,Dagger2消除了所有的反射,并且使代码变清晰,提高了其可读性。Dagger2作为由Google开发维护的框架,在项目中使用的频率越来越高,所以有必要熟悉Dagger2的使用,并且了解其源码的实现原理。

    依赖注入

    在面向对象的开发过程中,我们一个对象通常会依赖其他的一个或者多个对象来完成任务。假设有一个类为Hand手,那它肯定要依赖Finger手指类完成Hand的构建,如果Finger类的构造改变了,这时也要去修改Hand类的实现,这显然不符合开闭原则。这样由于不同类的组合,类之间就有了耦合,而使用Dagger2依赖注入框架的目的就是降低程序的耦合,达到解耦的目的。

    常见的几种依赖方式

    • 构造方法注入

      public class Hand {
          private Finger finger;
          public Hand(Finger finger) {
              this.finger = finger;
          }
      }
      
    • setter方法注入

      public class Hand {
          private Finger finger;
          public void setFinger(Finger finger) {
              this.finger = finger;
          }
      }
      
    • 接口注入

      public interface FingerInject {
          void setFinger(Finger finger);
      }
      
      public class Hand implements FingerInject {
          private Finger finger;
          @Override
          public void setFinger(Finger finger) {
              this.finger = finger;
          }
      }
      

    Dagger2的使用

    Dagger2使用注解标注的形式,在编译时apt工具会根据这些注解自动生成特定依赖注入的代码。

    @Inject和@Component的使用

    下面使用Dagger2完成上面Finger的注入过程

    1. 使用@Inject标注被注入类的构造方法
    public class Finger {
        @Inject
        public Finger() {
            Log.e("m1ku","构建手指了");
        }
    
        public void assemble(){
            Log.e("m1ku","手是由五根手指组成的");
        }
    }
    
    public class Hand {
        @Inject
        public Hand(Finger finger) {
            finger.assemble();
            Log.e("m1ku", "构建手手类");
        }
    
        public void shakeHand() {
            Log.e("m1ku", "我在握手了");
        }
    }
    
    
    1. 使用@Component标注Component接口
    @Component
    public interface MainComponent {
        void inject(MainActivity activity);
    }
    

    make项目,让代码重新编译,编译过程Dagger2会生成依赖注入实际起作用的代码,这些代码后面再看。

    1. 在注入目标类MainActivity中,使用@Inject标注要注入的属性,最后调用MainComponent的实现类的inject方法完成注入。
    public class MainActivity extends AppCompatActivity {
    
        @Inject
        Hand hand;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
          
            DaggerMainComponent
                    .create()
                    .inject(this);
          
            hand.shakeHand();
        }
    }
    

    调用注入对象的方法后,控制台打印如下,这就证明依赖注入成功了。

    E/m1ku: 构建手指了
    E/m1ku: 手是由五根手指组成的
    E/m1ku: 构建手手类
    E/m1ku: 我在握手了
    

    @Inject

    • 标注在构造方法上

      1. 表示Dagger2可以使用这个构造方法构造对象,如Finger类。
      2. 可以用来注入当前构造方法所需要的依赖,如Hand类构造参数依赖Finger类。
      3. 如果存在多个构造方法,@Inject注解仅可以标注其中一个。
    • 标注在属性上

      表示这个属性是需要被注入的,该属性不能用private来修饰。

    • 标注在方法上

      除了属性注入,Dagger2也可以使用方法注入,方法注入会在目标类构造方法执行后执行。

      上面MainActivity的hand也可以使用方法注入,如下

      @Inject
      public void setHand(Hand hand){
          this.hand = hand;
      }
      

    @Component

    用来标注在一个接口上,接口中定义inject方法,方法的参数就是目标类对象。编译后,会生成其的实现类,主要注入逻辑在该实现类中完成,后面会看其代码。

    @Module和@Provides的使用

    在上面的注入过程中,我们都是在依赖的构造方法上使用@Inject标注,那么如果我们想注入系统提供的类或者第三方的类该怎么办呢?这时就不能标注其构造方法了,此时就要借助@Module和@Provides注解来完成依赖的注入了。

    下面使用Dagger2将系统的Date日期类注入到MainActivity中

    1. 使用@Module标注Module类
    @Module
    public class MainModule {
    
    }
    
    1. 在Module类中定义一个用@Provides标注的方法,在方法中实例化Date类,其返回值为Date类型
    @Module
    public class MainModule {
        @Provides
        public Date provideDate() {
            return new Date();
        }
    }
    
    1. 为Component指定使用MainModule类
    @Component(modules = MainModule.class)
    public interface MainComponent {
        void inject(MainActivity activity);
    }
    

    4.最后在MainActivity注入方式还是相同的

    public class MainActivity extends AppCompatActivity {
    
        @Inject
        Date date;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            DaggerMainComponent
                    .create()
                    .inject(this);
            Log.e("m1ku",date.toString());
        }
    

    代码运行后,在Logcat输出:

    E/m1ku: Mon May 27 17:43:41 GMT+08:00 2019
    

    @Module

    • 由@Module标注的类
    • 按照约定,该类名的后缀为Module
    • 该类的作用是提供依赖

    @Provides

    • 该注解用来标注定义在Module类中的方法,方法的返回值为需要的依赖

    • 用@Provides标注的方法也可以有依赖,比如

      //该方法需要的字符串参数由provideFormat提供
      @Provides
      public SimpleDateFormat provideDateFormat(String format) {
          return new SimpleDateFormat(format);
      }
      
      @Provides
      String provideFormat() {
          return "yyyy-MM-dd";
      }
      

    @Named和@Qualifier的使用

    有时候仅靠依赖的类型不足以让程序分辨出使用哪一个依赖。这时就需要使用限定符注解来标识,其中@Named注解是可以直接用String类型参数标识的注解,也可以使用@Qualifier这个元注解来自定义标识注解,如下。

    @Module
    public class LayoutManagerModule {
    
        @Named("vertical")
        @Provides
        public LinearLayoutManager provideVerticalManager(Context context) {
            return new LinearLayoutManager(context);
        }
    
        @Named("horizontal")
        @Provides
        public LinearLayoutManager provideHorizontalManager(Context context) {
            return new LinearLayoutManager(context, OrientationHelper.HORIZONTAL, false);
        }
    
        @Provides
        public Context provideContext() {
            return DaggerApp.getContext();
        }
    }
    

    LayoutManagerModule提供横向和纵向布局管理器的依赖,依赖类型相同,如果不用限定符标识就会报错。在目标类注入时,也要指定标识符,以告诉程序我们需要的是哪个依赖。

    public class MainActivity extends AppCompatActivity {
    
        @Named("vertical")
        @Inject
        LinearLayoutManager layoutManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            DaggerMainComponent
                    .create()
                    .inject(this);
            Log.e("m1ku",layoutManager.toString());
        }
    }
    

    同样在构造方法上依赖它时也要使用限定符标识。

    @Singleton和@Scope的使用

    如果想要注入的依赖对象为单例的,可以使用@Singleton注解来实现。@Singleton为@Scope的默认实现,使用@Scope注解可以达到管理依赖对象生命周期的目的,同样也可以通过@Scope来自定义scope注解。

    还是上面的例子,在MainActivity中注入两个LinearLayoutManager

    public class MainActivity extends AppCompatActivity {
    
        @Named("vertical")
        @Inject
        LinearLayoutManager layoutManager;
      
        @Named("vertical")
        @Inject
        LinearLayoutManager layoutManager1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            DaggerMainComponent
                    .create()
                    .inject(this);
            //打印注入对象的地址
            Log.e("m1ku","layoutManager = " + layoutManager.toString());
            Log.e("m1ku","layoutManager1 = " + layoutManager1.toString());
        }
    

    这两个对象地址打印如下

    E/m1ku: layoutManager = android.support.v7.widget.LinearLayoutManager@8aa6111
    E/m1ku: layoutManager1 = android.support.v7.widget.LinearLayoutManager@e44b276
    

    地址不同,证明这是两个不同的对象,初始化了两次。

    如果想实现单例,我们需要在Module类中使用@Singleton标注提供依赖的方法,同时也要用@Singleton标注使用这个Module的Component类,如下

    @Module
    public class LayoutManagerModule {
    
        @Named("vertical")
        @Provides
        @Singleton
        public LinearLayoutManager provideVerticalManager(Context context) {
            return new LinearLayoutManager(context);
        }
    
        @Named("horizontal")
        @Provides
        public LinearLayoutManager provideHorizontalManager(Context context) {
            return new LinearLayoutManager(context, OrientationHelper.HORIZONTAL, false);
        }
    
        @Provides
        public Context provideContext() {
            return DaggerApp.getContext();
        }
    }
    
    @Singleton
    @Component(modules = {MainModule.class, LayoutManagerModule.class})
    public interface MainComponent {
        void inject(MainActivity activity);
    }
    

    此时这两个对象地址相同,实现了单例。

    E/m1ku: layoutManager = android.support.v7.widget.LinearLayoutManager@ee42077
    E/m1ku: layoutManager1 = android.support.v7.widget.LinearLayoutManager@ee42077
    

    再新建一个SecondActivity,为其定义Component,并注入同样的对象

    @Singleton
    @Component(modules = LayoutManagerModule.class)
    public interface SecondComponent {
        void inject(SecondActivity secondActivity);
    }
    
    public class SecondActivity extends AppCompatActivity {
    
        @Named("vertical")
        @Inject
        LinearLayoutManager linearLayoutManager3;
    
        @Named("vertical")
        @Inject
        LinearLayoutManager linearLayoutManager4;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second);
          
            DaggerSecondComponent.create().inject(this);
            Log.e("m1ku", "linearLayoutManager3 = " + linearLayoutManager3);
            Log.e("m1ku", "linearLayoutManager4 = " + linearLayoutManager4);
        }
    }
    

    由MainActivity跳转到SecondActivity,打印结果如下

    E/m1ku: linearLayoutManager3 = android.support.v7.widget.LinearLayoutManager@88d262d
    E/m1ku: linearLayoutManager4 = android.support.v7.widget.LinearLayoutManager@88d262d
    

    我们发现这里的对象与MainActivity界面中的不同,不再是单例。但是在当前界面内仍然为单例的,这是由于@Scope注解的作用范围是局部的,它只保证依赖对象在当前Component中是单例的。如果我们想实现App内的全局单例的话,我们可以将Component保存在Application中来保证该Component在app中只有一份,后面在学习@Component的dependence会实现全局单例。

    @Component的依赖dependencies

    使用dependencies可以实现Component的依赖关系,让Component依赖另一个已经存在的Component组件。

    现在通过实现一个全局单例的UserManger来学习dependencies的用法。

    定义UserModule来提供UserManager依赖

    @Module
    public class UserModule {
    
        @Singleton
        @Provides
        public UserManager provideUserManager(){
            return new UserManager();
        }
    }
    

    然后定义应用的全局AppComponent,在其中定义能向应用提供单例对象的方法

    @Singleton
    @Component(modules = UserModule.class)
    public interface AppComponent {
        UserManager getUserManager();
    }
    

    在Application中初始化该Component,并提供获取其实例的方法

    public class DaggerApp extends Application {
    
        private static Context context;
        private static AppComponent appComponent;
    
        @Override
        public void onCreate() {
            super.onCreate();
            appComponent = DaggerAppComponent.create();
            context = this;
        }
    
        public static AppComponent getAppComponent() {
            return appComponent;
        }
    

    使用dependencies关键字为MainComponent和SecondComponent提供依赖

    @ActivityScope
    @Component(modules = {MainModule.class,LayoutManagerModule.class},dependencies = AppComponent.class)
    public interface MainComponent {
        void inject(MainActivity activity);
    }
    
    @ActivityScope
    @Component(modules = LayoutManagerModule.class, dependencies = AppComponent.class)
    public interface SecondComponent {
        void inject(SecondActivity secondActivity);
    }
    

    @ActivityScope是自定义的Scope注解。由于AppComponent已经被@Singleton标注,而MainComponent和SecondComponent依赖于AppComponent,如果他们再使用@Singleton注解就会报错,所以要自定义@ActivityScope注解,当然这两个Module中也要换成@ActivityScope注解。

    经过编译后,分别向两个activity中注入UserManager类

    public class MainActivity extends AppCompatActivity {
        @Inject
        UserManager userManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            DaggerMainComponent
                    .builder()
                    .appComponent(DaggerApp.getAppComponent())
                    .mainModule(new MainModule())
                    .build()
                    .inject(this);
            Log.e("m1ku","userManager = " + userManager.toString());
        }
      
      public class SecondActivity extends AppCompatActivity {
        @Inject
        UserManager userManager1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second);
    
            DaggerSecondComponent
                    .builder()
                    .appComponent(DaggerApp.getAppComponent())
                    .layoutManagerModule(new LayoutManagerModule())
                    .build()
                    .inject(this);
            Log.e("m1ku", "userManager1 = " + userManager1.toString());
        }
    }
    

    两个界面中UserManager地址打印相同,这就证明UserManager实现了全局的单例。

    userManager = com.m1ku.daggerdemo.entity.UserManager@c1044e4
    userManager1 = com.m1ku.daggerdemo.entity.UserManager@c1044e4
    

    最后

    我自己的项目中用的是mvparms框架,项目框架就是用Dagger2来组建的,但都是用的一键生成的,所以还是要好好学习下Dagger2框架的。即使不用但也要会呀,等下开一篇来看下它的源码实现,如果后面碰到新的使用姿势再回来补下。

    相关文章

      网友评论

          本文标题:Dagger2使用姿势学习

          本文链接:https://www.haomeiwen.com/subject/cipaxctx.html