破壳之Dagger2入门与使用

什么是 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 就有点粗暴了,直接自己跑到人家家里各种拿,想拿啥拿啥。

最后奉上例子代码

代码链接

在这里插入图片描述

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值