详解Dagger2系列之撸码篇:横看成岭侧成峰

Dagger2的使用

上一篇我们简单介绍了依赖注入是什么,并用简单的栗子说明了依赖注入的使用,本篇主要来分享Dagger2的简单使用,为运用到项目中做进一步的铺垫。

引入Dagger2

首先,我们需要将Dagger2的依赖写入我们的gradle中,具体配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apply plugin: 'com.neenbedankt.android-apt'

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
}

dependencies {
apt 'com.google.dagger:dagger-compiler:2.0'
compile 'com.google.dagger:dagger:2.0'
}

把 apply plugin和dependencies放在app的gradle里,把buildscript放在项目的gradle里即可,之后我们就可以开始Dagger之旅了。

注解的使用

看过Dagger2的人应该都知道,Dagger2是通过注解来实现依赖注入的,所以,在使用Dagger2之前,我们需要了解这些注解的含义,如果对注解是什么还不清楚的同学可以Google一下,在这就不细说了。Dagger2中主要有6种注解,我们把它拆为4+2,前四种通俗易懂,后两种理解起来就有一定难度了。

四个基础

这里说的四个基础,指的是四种基础的注解,他们分别是:

  • @Inject Inject主要有两个作用,一个是使用在构造函数上,通过标记构造函数让Dagger2来使用(Dagger2通过Inject标记可以在需要这个类实例的时候来找到这个构造函数并把相关实例new出来)从而提供依赖,另一个作用就是标记在需要依赖的变量让Dagger2为其提供依赖。
  • @Provide 用Provide来标注一个方法,该方法可以在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Injection的变量赋值。provide主要用于标注Module里的方法
  • @Module 用Module标注的类是专门用来提供依赖的。有的人可能有些疑惑,看了上面的@Inject,需要在构造函数上标记才能提供依赖,那么如果我们需要提供的类构造函数无法修改怎么办,比如一些jar包里的类,我们无法修改源码。这时候就需要使用Module了。Module可以给不能修改源码的类提供依赖,当然,能用Inject标注的通过Module也可以提供依赖
  • @Component Component一般用来标注接口,被标注了Component的接口在编译时会产生相应的类的实例来作为提供依赖方和需要依赖方之间的桥梁,把相关依赖注入到其中。

这些标注看起来可能比较抽像,为了方便各位理解,送图一张来说明这些标注的作用和之间的关系:

图片主要分为三部分,左边的是依赖提供者,比如我们用Module标注的类或者用Injection标注的构造函数,右边的是依赖的需求方,例如我们用inject标注的变量,而Component则是连接两者的桥梁,Component从依赖提供者提供依赖,并把这些依赖注入相关的类中,Dagge正如其名,就像把匕首让依赖能够非常犀利的注入到需要它的地方。

说了那么多前言,虽然这些注解都有各自独特的作用,单用起来其实很简单,接下来我们将进一步地讲解这些标注的作用,just show you code。在使用之前,只要大致明白这些标注的意义就行了,简单的依赖注入通过这几个标注就能完成。

简单的栗子

还是从我们的CoffeeShop说起,现在,我们有一个Activity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class OriginActivity extends Activity implements View.OnClickListener{
@BindView(R.id.btnMakeCoffee)
Button btnMakeCoffee;
@BindView(R.id.tvCoffee)
TextView tvCoffee;

CoffeeMachine coffeeMachine;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
ButterKnife.bind(this);
Cooker cooker = new Cooker("James","Espresso");
coffeeMachine = new CoffeeMachine(cooker);
initView();
}

private void initView(){
btnMakeCoffee.setOnClickListener(this);
}

private String makeCoffee(){
return coffeeMachine.makeCoffee();
}


@Override
public void onClick(View v) {
if(R.id.btnMakeCoffee == v.getId()){
tvCoffee.setText(makeCoffee());
}
}
}

以看到,我们的CoffeeMachine还是通过在在Oncreate中new出来的,这就是上一篇中说的不好的味道,我们试着用Dagger2来注入这些依赖。

需要注入依赖的是: CoffeeMachine coffeeMachine,怎么让Dagger2知道这个东西需要注入依赖呢,很简单,

1
2
@Inject
CoffeeMachine coffeeMachine;

是不是和上面ButterKnif给View注入的很相似,就是酱紫。这就是我们的依赖需求方。我们还需要依赖提供方

上面提到过,提供依赖可以给相关依赖类的构造函数添加@Inject注解,这样Dagger2就能找到它并用来提供依赖,所以我们看CoffeeMachie这个类

1
2
3
4
5
6
7
8
9
10
11
public class CoffeeMachine {
private CoffeeMaker maker;

public CoffeeMachine(Cooker cooker){
maker = new SimpleMaker(cooker);
}

public String makeCoffee(){
return maker.makeCoffee();
}
}

这个时候我们需要给他的构造函数增加一个@Inject

1
2
3
4
@Inject
public CoffeeMachine(Cooker cooker){
maker = new SimpleMaker(cooker);
}

不过看起来还是不太对,为啥,因为我们在它的构造函数里又new了一个对象,这样又是不好的味道,这个maker应该被注入进去。
我们再让Dagger2来提供CoffeeMaker的依赖,还是像刚才一样,我们去修改SimpleMaker的构造函数,不过在此之前,需要先修改一下CoffeeMachine的构造函数

1
2
3
4
5
6
7
8
9
10
11
public class CoffeeMachine {
private CoffeeMaker maker;

public CoffeeMachine(CoffeeMaker maker){
this.marker = maker
}

public String makeCoffee(){
return maker.makeCoffee();
}
}

然后我们再修改SimpleMaker

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SimpleMaker implements CoffeeMaker {
Cooker cooker;

@Inject
public SimpleMaker(Cooker cooker){
this.cooker = cooker;
}

@Override
public String makeCoffee() {
return cooker.make();
}
}

这次变聪明了,SimpleMaker的构造函数中Cooker的依赖也是注入进来的,这个时候我们还需要提供Cooker的依赖
but,先打住一下,我们前面说了,除了构造函数提供依赖,还能用Module提供,所以,机智的我们这次用Module来提供依赖。

我们需要创建一个Module:

1
2
3
4
5
6
7
8
@Module
public class SimpleModule {

@Provides
Cooker provideCooker(){
return new Cooker("James","Espresso");
}
}

Module的写法是不是很简单,只要提供一个Module类,给类打上@Module的注解,然后再添加一个提供Cooker依赖的provideCooker方法,返回一个new Cooker(“James”,”Espresso”)即可,当然provideCooker需要添加@Provide注解这样Dagger2才能在需要Cooker的时候找到它来提供毅力啊,这里有个小细节,
我们需要提供Cooker的依赖,返回类型是Cooker就行了,方法名叫什么都行,但最好是以provide开头,这样能增加代码的可读性。
提供依赖的东西都写完了,最后需要一个把依赖注入到需要依赖的地方,这个工具就是Component

1
2
3
4
@Component(modules = SimpleModule.class)
public interface SimpleComponent {
void inject(SimpleActivity simpleActivity);
}

用@Component注解这个接口能让Dagger2在需要注入时找到这个工具,同时还告诉Dagger2提供依赖的是SimpleModule这个类
当然,如果需要用这个Module给SimpleActivity注入我们的CoffeeMachine,还需要一个inject方法,里面传入我们的SimpleActivity对象的实例。

这里需要注意,我们必须传入SimpleActivity自己,不能把SimpleActivity的父类定义到方法里作为参数来接收SimpleActivity,从语法上来说是正确的,但你会发现这样做没办法完成注入,这是Dagger2的一个坑,别给踩着了。

注入器也制作完了,就剩下最后一步:“注入”。
这步也很简单,只需要在我们的Oncreate里这么写:

1
2
3
4
5
6
7
8
private SimpleComponent simpleComponent;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
simpleComponent = DaggerSimpleComponent.builder().simpleModule(getModule()).build();
simpleComponent.inject(this);
}

这里有个地方需要注意一下,DaggerSimpleComponent是什么,我们压根就没写这个东西。对,这就是Dagger2在编译时生成的类,Dagger2就是通过这些编译生成的东西完成了依赖的注入,那么Dagger2在编译时产生的类是怎么工作的呢,我们将在下一篇来叙述这个问题。

这里再说明一个问题,我们有两种方式可以提供依赖,一个是注解了@Inject的构造方法,一个是在Module里提供的依赖,那么Dagger2是怎么选择依赖提供的呢,规则是这样的:

  • 步骤1:查找Module中是否存在创建该类的方法。
  • 步骤2:若存在创建类方法,查看该方法是否存在参数
  • 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
  • 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
  • 步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数

  • 步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数

  • 步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

概括一下就是从注解了@Inject的对象开始,从Module和注解过的构造方法中获得实例,若在获取该实例的过程中需要其他类的实例,则继续获取被需要类的实例对象的依赖,同样是从Module和标注过的构造方法中获取,并不断递归这个过程直到所有被需要的类的实例创建完成,在这个过程中Module的优先级高于注解过的构造方法。明白了这个原理之后老奶奶都会用这几个标签了。

@Scope和@Qulifier

上面把四个简单的注解的用法都讲完了,但很多时候这几个注解并不能涵盖我们所有的场景,这时就需要@Scope和@Qulifier来帮忙了。

有的同学可能在用Module的时候会有疑惑,为什么方法怎么命名都行,那时怎么区分它为谁提供依赖呢。答案是根据返回类型来确定的,当某个对象需要注入依赖时,Dagger2就会根据Module中标记了@Provide的方法的返回值来确定由谁为这个变量提供实例。那问题来了,如果有两个一样的返回类型,该用谁呢。我们把这种场景叫做依赖迷失,见名知意,Dagger这时候就不知道用谁来提供依赖,自然就迷失了。所以我们引入了@Qulifier这个东西,通过自定义Qulifier,可以告诉Dagger2去需找具体的依赖提供者。

1
2
3
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface A {}

1
2
3
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface B {}

我们定义了两个注解,@A和@B,他们都是用@Qulifiier标注的

再看看我们的Module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Module
public class SimpleModule {

@Provides
@A
Cooker provideCookerA(){
return new Cooker("James","Espresso");
}


@Provides
@B
Cooker provideCookerB(){
return new Cooker("Karry","Machiato");
}

}

再看看具体的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ComplexMaker implements CoffeeMaker {
Cooker cookerA;
Cooker cookerB;

@Inject
public ComplexMaker(@A Cooker cookerA,@B Cooker cookerB){
this.cookerA = cookerA;
this.cookerB = cookerB;
}

@Override
public String makeCoffee() {
return cooker.make();
}
}

1
2
cookerA.make();//James make Espresso
cookerB.make();//Karry make Machiato

这样说是不是很简单,相信大家很快就能理解@Qulifier的作用和用法

接着说@Scope,@Scope就要难理解点了,继续来看栗子,我们定义一个Scope注解

1
2
3
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {}

我们把定义的@PerActivity用到Module里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Module
public class ActivityModule {

@Provides
CoffeeShop provideCoffeeShop(){
return CoffeeShop.getInstance();
}

@Provides
@PerActivity
CookerFactory provideCookerFactory(){
return new CookerFactory();
}

@Provides
CookerFactoryMulty provideCookerFactoryMulty(){
return new CookerFactoryMulty();
}
}

这个Module提供了CoffeeShop,CookerFactory和CookerFacotryMulty的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CoffeeShop {
private static CoffeeShop INSTANCE;

private CoffeeShop(){
Log.d("TAG","CoffeeShop New Instance");
}

public static CoffeeShop getInstance(){
if(INSTANCE == null){
INSTANCE = new CoffeeShop();
}
return INSTANCE;
}
}

1
2
3
4
5
6
public class CookerFactory {

public CookerFactory(){
Log.d("TAG","CookerFactory New Instance");
}
}
1
2
3
4
5
6
public class CookerFactoryMulty {

public CookerFactoryMulty(){
Log.d("TAG","CookerFactoryMulty New Instance");
}
}

我们在这三个对象的构造方法里都加了Log,当他们的实例产生时能看到相关的Log,再看我们用到的地方,在MainActivity里给每个类都写两个变量,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class MainActivity extends Activity {

ActivityComponent activityComponent;

@Inject
CoffeeShop coffeeShop1;

@Inject
CoffeeShop coffeeShop2;

@Inject
CookerFactory cookerFactory1;

@Inject
CookerFactory cookerFactory2;

@Inject
CookerFactoryMulty cookerFactoryMulty1;

@Inject
CookerFactoryMulty cookerFactoryMulty2;



@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
activityComponent = DaggerActivityComponent.builder()
.activityModule(provideModule())
.applicationComponent(MyApplication.getComponent()).build();
activityComponent.inject(this);
coffeeFactory.run();
}

private ActivityModule provideModule(){
return new ActivityModule();
}
}

ok,来看下运行的结果把

1
2
3
4
07-11 16:53:27.978    1927-1927/? D/TAG﹕ CoffeeShop New Instance
07-11 16:53:27.978 1927-1927/? D/TAG﹕ CookerFactory New Instance
07-11 16:53:27.978 1927-1927/? D/TAG﹕ CookerFactoryMulty New Instance
07-11 16:53:27.978 1927-1927/? D/TAG﹕ CookerFactoryMulty New Instance

从Log中可以看到,CoffeeShop和CookerFactory的类都只new过一次,而CookerFactoryMulty被new了两次
再回头看我们的Module,其中CoffeeShop的依赖是通过单例模式提供的,只打一条Log很容易理解,而CookerFactory相对于CookerFactoryMulty来说内容几乎是一模一样,只多加一个@PerActivity的注解,但却比它少打了一次Log,这是为什么呢。哈哈,客官们,这就是@Scope神秘的地方,他通过自定义@Scope注解提供了单例,正如上面的CookerFactory,虽然并未用单例来提供依赖,但却和用单例提供依赖的CoffeeShop一样,两个对象的实例都是同一个,这就是Scope的作用,提供局部单例的功能,局部范围是啥,那就是它生命周期范围内。

OK,上面所有注解的作用和用法都说完了,再回头看看是不是超级简单,这些东西并没有想象中的那么难懂,只要结合上面的栗子,我相信大家都能明白了。其实dagger2正真的难点并不是理解这些注解的作用和用法,而是如何在我们项目中来灵活地运用它。要做到这点,光靠这两篇内容是不够的,我们除了了解这些注解,还要明白它们是如何工作的,比如Scope为什么能实现局部单例等,这是要通过源码分析才能明白,同时,要运用到项目中,我们还得明白如何确定我们的最小粒度,Component,Module是怎么划分的等,这就涉及到Component的依赖,继承等内容,在接下来的篇幅中,我将通过源码分析和项目实战,来继续详解Dagger2的用法,敬请期待。

Maydaaa wechat
欢迎添加微信好友共同交流学习!
坚持原创技术分享,您的支持将鼓励我继续创作!