美文网首页
破壳之Dagger2入门与使用

破壳之Dagger2入门与使用

作者: 小武的知识铺 | 来源:发表于2019-03-03 23:09 被阅读0次

    什么是 Dagger2

    Dagger2 的核心是 依赖注入,对于 依赖注入 的理解,每个人都有一个自己的版本,我的理解是:通过注入的方式获得想要的依赖。

    在开始学习使用 Dagger2 之前,我们最好先理解清楚这个框架帮我们做了什么,我在一开始使用 Dagger2 的时候也是这样,一上来不管三七二十一先看怎么用,结果导致的结果是用的过程中一脸问号。原因就是没有理解这个框架它究竟是帮我们干了什么。

    Dagger2 是一个能帮助我们通过注解的方式,快速完成依赖注入的优秀框架。

    不过也别过于神化 Dagger2,要想使用好它,我们还是要遵循不少约定规则的,该写的代码也逃避不了。

    至于 Dagger2 好处,我觉得最大的好处就是解藕了,最直观的一个解释是:哪天代码升级,需要修改一个在整个项目中大量被 new 的对象的构造方法时,如果用了 Dagger2 依赖注入,就可以不用大范围的修改代码,只要修改依赖提供者 Module 里面的 @Provides 注解的方法即可。当然上面说的都是有前提条件的,所以不必较真于此,毕竟我们的目的是理解并使用 Dagger2

    下面先逐个击破 Dagger2 的注解。Let‘s go...

    Dagger2 的注解

    @Inject

    @Inject 注解会在两个地方被看到:

    1. 依赖的构造方法
    2. 依赖需求者的属性或者方法

    先看代码:

    // 车轮
    public class Wheel {
    
      @Inject
      public Wheel(){
        Log.e("Wheel", "我是自行车轮胎");
      }
    
    }
    
    // 自行车
    public class Bike {
    
      // 方式一
      @Inject
      Wheel mWheel;
      
      // 方式二
      //@Inject
      //public void setWheel(Wheel wheel){
      //  this.mWheel = wheel;
      //}
    
      public Bike() {
        Log.e("Bike", "我是自行车");
        DaggerBikeComponent.builder().build().inject(this);
      }
    }
    
    // 自行车依赖注入器
    @Component
    public interface BikeComponent {
      // 这个方法名可以任意起,只是大家更习惯用 inject
      // 因为inject 比较形象
      void inject(Bike bike);
    }
    

    自行车对象依赖车轮,我们使用 @Inject 注解了 Wheel 的构造方法,以及 Bike 中的 mWheel 属性(或者被注释的方法),结合被 @Component 注解的 BikeComponent,我们实现了最最简单的依赖注入代码。

    DaggerBikeComponent 的起到桥梁的作用,连接 依赖需求者依赖提供者,后面会有更多讲解。

    DaggerBikeComponentDagger2 自动帮我们生成的,名称的规则是 Dagger{ Component 名称 }

    @Inject 的一些约定:

    1. @Inject 不能使用 private,打开 build 后生成的代码我们会找到原因,这里留给大家自己验证;
    2. @Inject 修饰依赖构造方法的时候,如果存在多个构造方法,只能选择修饰一个。至于要使用多个构造方法,后面会有解决方案「???????」。

    @Module & @Provides

    可以看到,上面我们为了获取 Wheel 依赖,是直接修改了 Wheel 的源码,在其构造方法上动了些手脚,加上了 @Inject 注解。但是如果我们使用的是一个第三方库提供的类的话,@Inject 这招就行不通了。

    @Module 登场!!!

    先看代码:

    
    // 【新加】车灯,没有被 @Inject 注解构造方法
    // 三方提供
    public class Light {
    
      public Light(){
        Log.e("Light", "我是车灯");
      }
    
    }
    
    // 自行车
    public class Bike {
    
      @Inject
      Wheel mWheel;
    
      @Inject
      Light mLight;
    
      //@Inject
      //public void setWheel(Wheel wheel){
      //  this.mWheel = wheel;
      //}
    
      public Bike() {
        Log.e("Bike", "我是自行车");
        // 【修改1】多了 .bikeModule(new BikeModule())
        DaggerBikeComponent.builder().bikeModule(new BikeModule()).build().inject(this);
      }
    }
    
    // 自行车依赖注入器 【修改2】指定了 modules 为 BikeModule
    @Component(modules = {BikeModule.class})
    public interface BikeComponent {
      void inject(Bike bike);
    }
    
    // 【新加】自行车依赖管理员
    @Module
    public class BikeModule {
    
      // 名字没有规定,
      // 一般为 provide+待被提供的依赖名称
      @Provides 
      Light provideLight(){
        return new Light();
      }
    
    }
    

    可以将 @Module 注解的类理解为依赖管理员,这个管理员通过 @Provides 注解的方法提供依赖给依赖需求者,比如上面的例子,通过 @Provides 注解了 Light provideLight() 方法,该方法实际直接返回了一个 Light 对象( 这里就是我一开始讲的,该写的代码还是逃避不了,比如创建这个依赖的代码还是需要我们手动的血出来 )。

    Dagger2 在寻找依赖的时候有一个顺序,会先从 @Module 中寻找,如果找不到再从 @Inject 注解了构造方法的类寻找,这个了解一下就好,在定位一些问题的时候会派上用场

    @Component

    @Component 注解的类可以理解为一个桥梁,这个桥梁的作用是连接 Module(依赖管理员)和依赖需求者(比如依赖了 WheelBike ),没有这个中介,@Inject@Module 都会失去作用。

    Component 会声明一个 Module 集合,也就是说,一个 Componet 可以有多个依赖管理员。

    @Qualifier & @Named

    @Qualifier 注解的作用按字面理解便知一二,Qualifier 是 “限定” 的意思,意思可以用它来限定一些东西,具体限定什么,先看下面的例子,Light 类多个一个灯色的属性:color, 默认颜色是白色。

    public class Light {
    
      private String color = "white";
    
      public Light() {
        Log.e("Light", "我是车灯, 并且颜色是:" + this.color);
      }
    
      public Light(String color) {
        this.color = color;
        Log.e("Light", "我是车灯, 并且颜色是:" + this.color);
      }
    }
    

    现在我们的 Light 类有两个构造方法,改写一下依赖管理员:

    @Module
    public class BikeModule {
    
      @Provides 
      Light provideLight() {
        return new Light();
      }
      
      @Provides 
      Light provideRedLight() {
        return new Light("red");
      }
    }
    

    这个时候我们 run build 一下,会得到下面的编译错误:

    xxx.xx.xxx.Light is bound multiple times...
    

    说明直接这样写肯定不行,那么可以请 @Qualifier 登场了。首先我们自定一个 Qualifier

    @Qualifier
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LightColorQualifier {
      String value() default "white";
    }
    

    下面调整一下 Module@Inject 的地方就可以让代码又重新跑起来了

    @Module
    public class BikeModule {
    
      @LightColorQualifier()
      @Provides
      Light provideLight() {
        return new Light();
      }
    
      @LightColorQualifier("red")
      @Provides
      Light provideRedLight() {
        return new Light("red");
      }
    }
    
    
    public class Bike {
    
      @Inject
      Wheel mWheel;
    
      @LightColorQualifier("red")
      @Inject
      Light mLight;
    
      //@Inject
      //public void setWheel(Wheel wheel){
      //  this.mWheel = wheel;
      //}
    
      public Bike() {
        Log.e("Bike", "我是自行车");
        DaggerBikeComponent.builder().bikeModule(new BikeModule()).build().inject(this);
      }
    }
    

    你可以试着改变 @LightColorQualifier("red")@LightColorQualifier("blue"), 会发现 build 又会失败,这说明这个 @LightColorQualifier("red") 起到了一个限定的作用,依赖管理员会根据这个限定去拿需要的依赖给需求者。

    讲完 @Qualifier 的原理和作用,@Named 水到渠成,看源码可知:

    @Qualifier
    @Documented
    @Retention(RUNTIME)
    public @interface Named {
        /** The name. */
        String value() default "";
    }
    

    是的,@Named 只是一个官方帮我们实现好的 @Qualifier,没啥特殊要求,直接用这个即可,至于在定义自己的 @Qualifier 的时候,下面几个方面可以大家自己尝试探究一下:

    1. 我上面是参考的官法 @Named 用的 value() ,可否换成其他的名字?
    2. 可否定义多个变量?
    3. 变量除了 String 之外,还可以用其他类型吗,比如 intboolean 之类?

    @Scope & @Singleton

    这边就不绕弯子了,@Scope@Singleton 的关系,其实和上面的 @Qualifier@Named 一样, @Singleton 是一个官方 @Scope 实现:

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

    回到 Scope ,中文意思是 “范围”,使用 @Scope 可以约定一些声明周期,比如我们开发中经常会用到 @ActivityScope@FragmentScope,但是是不是我们只要使用了 @Singleton@ActivityScope@FragmentScope。。依赖就会自动有了生命周期了呢?

    当然不会,还是开头讲的,不能神化 Dagger2。。

    @Singleton 并不是真正意义上用来创建单列的。 其实 Dagger2 真正创建单列的流程是:

    1. 首先创建一个 Module 实现全局类的实现
    2. 然后在 AppComponent 中使用这个 Module
    3. 保证 AppComponent 只会初始化一次,一般会在 ApplicationonCreate() 方法中创建这个 AppComponent

    那么 @Singleton 的作用是什么:

    1. 更好的管理 ModuleComponent 之间的关系,保证 AppComponentModule 是匹配的。若AppComponentModuleScope 是不一样的,则会在编译时报错。
    2. 代码可读性,让别的人看代码的时候更好的了解 Module 中创建的类实例是单例。

    最后整理一些约定:

    1. 如果在构造方法或者 @Provides 方法注解了 Scope@Component 一定要有注解
    2. @Component 注解了 Scope,不一定非得有构造方法或者 @Provides 方法被 Scope 注解
    3. @Scope@Component 可以依赖有 @Scope@Component,有 @Scope@Component 只能依赖有 @Scope@Component,并且两者的 @Scope 不能相同
    4. @Singleton@Component 只能被依赖而不能依赖任何 @Component

    @Component 的复用

    好的程序员怎能不会偷懒???

    Component 的复用主要有下面两种方式:Component 中的 dependencies 属性和 @Subcomponent 注解

    通过 dependencies 属性指定依赖的 Component

    @ActivityScope
    @Component(modules = {MainModule.class } , dependencies = {AppComponent.class})
    public interface MainComponent {
      void inject(MainActivity mainActivity);
    }
    
    
    @Singleton
    @Component(modules = { AppModule.class })
    public interface AppComponent {
      Gson gson();
    }
    

    这种方式,如果在 MainActivity 中想要获取到 AppModule 里面提供的依赖,必须在 AppComponent 里面显式的提供出来。不然的话是没法在 MainActivity 中获取到 AppModule 中想要依赖的。

    @Subcomponent

    @ActivityScope
    @Subcomponent(modules = {MainModule.class })
    public interface MainComponent {
      void inject(MainActivity mainActivity);
    }
    
    
    @Singleton
    @Component(modules = { AppModule.class })
    public interface AppComponent {
      MainComponent plus(MainModule mainModule);
    }
    
    public class MainActivity extends AppCompatActivity {
    
      @Inject Gson mGson;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        DemoApp.getInstance().getAppComponent().plus(new MainModule(this)).inject(this);
    
        //DaggerMainComponent.builder()
        //    .appComponent(DemoApp.getInstance().getAppComponent())
        //    .mainModule(new MainModule(this))
        //    .build()
        //    .inject(this);
    
        Log.e("MainActivity", mGson.toString());
        //new Bike();
      }
    }
    

    @Subcomponent 注解的 Component 不会自动生成类似 DaggerxxxComponent 的类文件,其实是把自己扔到了父 Component 中,从而获得到父 Component 的所有注入属性, 比如上面我们就没有再显式的在 AppComponent 中提供 Gson 对象了。

    通过这两个例子,大家可以对比体会一下二者的区别。我个人的理解是 dependencies 更加委婉,我打好招呼要啥再给啥,@Subcomponent 就有点粗暴了,直接自己跑到人家家里各种拿,想拿啥拿啥。

    最后奉上例子代码

    代码链接

    相关文章

      网友评论

          本文标题:破壳之Dagger2入门与使用

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