Dagger2使用攻略-基础部分

在这篇文章中,我会介绍 什么是依赖注入,Dagger2是什么,解决什么问题以及基础注解的使用

Dagger2

依赖注入

什么是 依赖。

举个例子

有一个 A 类 它里面定了一个 B 类型的 属性 b; 这里 A 就依赖了 B;

1
2
3
4
5
6
7
8
9
10

public class A{

public A(){
b = new B();
b.print();
}

private B b;
}

这就意味着 A 离开 B 不能单独运行,也就是说 A 在哪里工作,B就会跟到哪里,A 无法离开 B 被复用。

这种情况下 A 就是 依赖者,B就是依赖。依赖者依赖于它的依赖。

两个相互使用的类称为耦合;耦合有强有弱。耦合总是有方向性的。可能 A 依赖 B,但 B 不一定依赖 A。

依赖类型

  • 类 / 接口 依赖
  • 属性 / 方法 依赖
  • 间接 / 直接 依赖

硬编码依赖的不好

在依赖者内部构建或者由依赖者寻找依赖这种就称为 硬编码依赖

  • 降低复用性
  • 不好测试
  • 强耦合
  • 增加维护成本

关于 什么是依赖,更详细的硬编码依赖的缺点这部分,更详细的可以参考这篇文章,我就是从篇文章学习来的。

https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-introduction-be6580cb3edb

什么是依赖注入

依赖注入:一个对象提供另一个对象的依赖的技术;

依赖是个能被使用的对象(一个服务);注入是将依赖传递给要使用它的对象(客户端 / 依赖者)。

服务作为客户端的一部分。将服务传递给客户端而不是客户端构建或者寻找服务,这是模式(依赖注入)的基本要求。

换句话说:

依赖作为依赖者的一部分。将依赖传递给依赖者而不是由依赖者构建或者寻找依赖,这是依赖注入的基本要求。

也就是说 依赖从来原来的由依赖者构建,改为现在由外部注入,也可以称为 控制反转。

这样的好处是很明显的,提高可测试性,解偶,降低维护成本等等。

更详细的解释 可以看一下这篇文章,解释的超级棒,如果你看过权力的游戏,就更棒了。

https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-di-part-i-f5cc4e5ad878

Dagger2 就是 Android 平台的一个依赖注入框架,它现在由 Google 维护开发。

Dagger2 是编译时框架,会在编译时根据你的注解配置生成需要的代码。


下面是我对 Dagger2 中的常用注解的理解。理解了这些注解的意思和作用,基本就学会了 Dagger2 的基本用法了。

常用注解

@Inject

这个注解有两个作用:

  • 修饰需要注入的属性,Dagger2 会自动注入
  • 修饰被注入的类的构造方法上;Dagger2 会在需要的时候通过这个注解找到构造函数自动构造对象注入
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainActivity extends AppCompatActivity {

@Inject
DBManager dbManager;

}

public class DBManager {


@Inject
public DBManager(){}
}

@Component

这个注解的作用 是连接提供依赖和注入依赖的。相当与一个注射器的角色,将依赖注入到需要的地方。

刚刚通过上面的 @Inject 注解 了 提供依赖的构造方法 和 需要注入的属性,而这样还是不够的,需要使用 @Comnponent 连接起来。

创建一个接口,并定义一个方法,定义要往哪里注入;在编译时期 Dagger2 就会自动生成这个接口的实现类 并以 Dagger 开头。

还可以定义 向外提供实例的方法;Dagger2 都会在编译时期生成相应的代码。

下面是 示例

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

@Component()
public interface MainComponent {

void inject(MainActivity mainActivity);

DBManager getDBManager();
}

// 在需要被注入的类中注入 例如:

public class MainActivity extends AppCompatActivity {

@Inject
DBManager dbManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注入
DaggerMainComponent.create().inject(this);
}

}

@Component 有两个属性 modules and dependencies

  • modules 的作用是引用 Module 的,下面 @Module 会继续说
  • dependencies 的作用是 引用其他 Component 使用的,相当于 把其他的 Component 当作组件一样引用过来;

@SubComponent

顾名思义 就是 Comnponent 的儿子,它也表示一个注射器的角色,不过它可以继承 Component的全部 属性。

Dagger2 不会生成 Dagger开头的 DaggerSubComponent 这种类,所以,SubComponent 需要在 Component 注册和维护。这样的也好统一管理维护,Dagger2 会在生成 Component的时候自动实现生成在内定义的方法。

举个例子 我的 ApplicationComponent 是个全局单例的,有 NetModule, APPModule,等等很多全局性依赖,如果我的 Activity 的注射器 使用 @SubComnponent ,那么就可以使用Application的全部依赖。

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
@ActivityScoped
@Subcomponent(modules = MainModule.class)
public interface MainComponent {

void inject(MainActivity mainActivity);
}




@APPScoped
@Component(modules = {APPModule.class, APIModule.class} )
public interface APPComponent {

MainComponent plus(MainModule module);

SecondComponent plus(SecondModule module);


}


//注入

DaggerAPPComponent.builder()
.aPPModule(new APPModule(getApplication()))
.build()
.plus(new SecondModule())
.inject(this);

当然还有另外一种方法不用 @SubComponent,使用 Component 并使用 denpendencies 引用上 ApplicationComponent 这样就相当于将 ApplicationComponent 组合进来。

@Module && @Provides

@Module 这个注解用来标注提供依赖的工厂。对的,工厂,我是这么理解的。

@Provides 这个注解用在提供定义提供依赖的方法上,表示向外提供依赖。方法的返回类型就是提供的依赖类型。

前面提到的 @Inject 可以在注解在构造函数以用来提供依赖;而在 @Inject 不能满足需要的时候这个就派上用场了。

例如 我注入一个 字符串,数字或一个 第三方依赖的对象 例如 Retrofit ,@Inject 已经满足不了啦。

这个时候可以创建一个类 专门用来提供这些依赖,并使用 @Module 注解,然后在 Component 的属性 modules 引用上就可以使用了。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 需要注入的 Activity

public class ThirdActivity extends AppCompatActivity {


@Inject String name;

@Inject int age;

@Inject
OkHttpClient client;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);

DaggerThirdComponent.create().inject(this);

Log.e(ThirdActivity.class.getSimpleName(), "onCreate: name-"+name+";age-"+age+";client-"+client);
}
}

// 提供依赖的 工厂

@Module
public class ThirdModule {

@Provides
public String provideName(){
return "skymxc";
}

@Provides
public int provideAge(){
return 24;
}

@Provides
public OkHttpClient provideOkHttpClient(){
OkHttpClient client = new OkHttpClient.Builder().build();
return client;
}
}


// 连接 依赖和注入方 ,在这里引用 依赖提供方。

@Component(modules = ThirdModule.class)
public interface ThirdComponent {

void inject(ThirdActivity activity);

}

@Named

在依赖迷失时给出方向。

解释一下 依赖迷失

依旧是上面那个例子,现在 都是根据返回值类型来注入的,现在都是不同的类型所以还没有出现迷失的情况;
现在我如果要加上 地址 属性;如下

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

// activity内

@Inject String name;

@Inject int age;

@Inject
OkHttpClient client;

@Inject String address;

// module 中

@Provides
public String provideName(){
return "skymxc";
}

@Provides
public int provideAge(){
return 24;
}

public String provideAddress(){
return "北京";
}

这个时候 在 module 中 有两个返回 String 类型的 方法,Dagger2 这个时候就不知道注入哪一个了,所以就会出现 依赖迷失 的情况;

1
2
3
错误: [Dagger/DuplicateBindings] java.lang.String is bound multiple times:
@Provides String com.skymxc.example.dagger2.di.module.ThirdModule.provideAddress()
@Provides String com.skymxc.example.dagger2.di.module.ThirdModule.provideName()

简单的解决方法就是在 属性和提供依赖上 加上 @Named 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24



@Named("name")
@Provides
public String provideName(){
return "skymxc";
}

@Provides
@Named("address")
public String provideAddress(){
return "北京";
}


// 在 属性上也加上

@Named("name")
@Inject String name;


@Named("address")
@Inject String address;

这样就可以解决了 依赖迷失。

@Qualifier

@Named 的元注解,解决依赖迷失的大 Boss;看一下 @Named 的源码,@Named 就是被 @Qualifier 注解的。

1
2
3
4
5
6
7
8
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

/** The name. */
String value() default "";
}

如果怕通过 @Named 写字符串的方式容易出错就可以通过 @Qualifier 自定义注解来实现。

下面举个例子,再加一个 身高属性。定义两个注解来区分 @Age and @Height.

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

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Height {
}



@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Age {
}

//在 module 和 属性上使用

@Age
@Provides
public int provideAge(){
return 24;
}

@Provides
@Height
public int provideHeight(){
return 175;
}

@Age
@Inject int age;

@Height
@Inject int height;

@Singleton

配合 @Component 实现 范围内单例

@Singleton 必须和 @Component 配合才能实现单例,而且只能保证在 @Component 范围内单例,如果要实现全局单例,就必须要保证 @Component 的实例在全局范围内只有一个,类似 Application 。

举个例子,我要 DBManager 在全局单例,需要以下几个步骤

  1. 在 DBManger 上使用 @Singleton 或者 在 @Provides 修饰的方法上加。
  2. 然后在 AppComponent 也加上()
  3. 在 Application 中 获取 AppComponent 实例,让其全局唯一。
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 1.DBManager 标注 @Singleton
@Singleton
public class DBManager {



@Inject
public DBManager(){}
}

// 2.
@Singleton
@Component(modules = {APPModule.class, APIModule.class})
public interface APPComponent {

MainComponent plus(MainModule module);

SecondComponent plus(SecondModule module);

//可有可无 为了测试
DBManager getDBManager();
}

//3. 在 Application 中获取 实例,并保证唯一实例
public class MApplication extends Application {

private APPComponent appComponent;

@Override
public void onCreate() {
super.onCreate();

appComponent = DaggerAPPComponent.builder()
.aPPModule(new APPModule(this))
.build();
}

public APPComponent getAppComponent() {
return appComponent;
}
}

// 测试,在 MainActivity 注入两个。


public class MainActivity extends AppCompatActivity {
@Inject
DBManager dbManager;


@Inject DBManager dbManager1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//使用 Application 获取 AppComponent
((MApplication)getApplication()).getAppComponent()
.plus(new MainModule())
.inject(this);

Log.e(MainActivity.class.getSimpleName(), "onCreate: appdb-->"+((MApplication)getApplication()).getAppComponent().getDBManager().hashCode());
//是否是全局范围内单例

if (dbManager==dbManager1) {
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode());
}else{
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}



}


}

// 在 SecondActivity 注入两个看看是否和 Main 中的是一个实例

public class SecondActivity extends AppCompatActivity {

@Inject
DBManager dbManager;

@Inject
DBManager dbManager1;



@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);

((MApplication)getApplication()).getAppComponent()
.plus(new SecondModule())
.inject(this);

if (dbManager==dbManager1) {
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode());
}else{
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}

}
}

测试结果必须是全局唯一单例,看一下 log

1
2
3
4
E/MainActivity: onCreate: appdb-->192114699
onCreate: dbmanager-sintleton->192114699

E/SecondActivity: onCreate: dbmanager-singleton->192114699

@Singleton 的作用域 始终是跟随所在的 Component 的实例的,如果超出它的范围就无法保证单例。

就拿上个例子举例,如果每次 在 Activity 注入的时候 不从 Application 获取实例而是每次都是使用 DaggerAppComponent 创建一个新的 实例 ,那么就无法保证两个 Activity 内的 DBManager 都是一个实例了,因为每个 Activity 都是获取新的 AppComponent 的实例,它的作用范围只能在单个实例内。

下面我实现一个 只在 Activity 范围实现单例的 例子,就是把上面的代码改改,在Activity注入的时候 创建新的 Component 实例。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

public class SecondActivity extends AppCompatActivity {

@Inject
DBManager dbManager;

@Inject
DBManager dbManager1;



@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);

// ((MApplication)getApplication()).getAppComponent()
// .plus(new SecondModule())
// .inject(this);

// 获取新实例
DaggerAPPComponent.builder().aPPModule(new APPModule(getApplication())).build().plus(new MainModule()).inject(this);

if (dbManager==dbManager1) {
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode());
}else{
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}

}
}


public class MainActivity extends AppCompatActivity {
@Inject
DBManager dbManager;


@Inject DBManager dbManager1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取新实例
DaggerAPPComponent.builder().aPPModule(new APPModule(getApplication())).build().plus(new MainModule()).inject(this);

if (dbManager==dbManager1) {
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode());
}else{
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}



}


}



// log

09-23 00:02:52.937 E/DBHelper: DBHelper:
09-23 00:02:52.937 E/MainActivity: onCreate: dbmanager-sintleton->115289709
09-23 00:02:57.097 E/DBHelper: DBHelper:
09-23 00:02:57.097 E/SecondActivity: onCreate: dbmanager-singleton->64826129

总结 : Dagger2 实现单例要 @Singleton@Component || @SubComponent 配合使用,只能实现范围内(实例内)单例,所以范围要控制好。只要范围控制好,随意 Activity 或者 Application 范围。

@Scope

作用域 上面说到的 @Singleton 就是它的默认实现,也是唯一一个默认实现。

看一下 @Singleton 的源码

1
2
3
4
5
6
7
8
9
/**
* Identifies a type that the injector only instantiates once. Not inherited.
*
* @see javax.inject.Scope @Scope
*/
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

@Singleton 能够实现范围内单例 主要是 @Scope 在起作用。默认实现叫 Singleton 也是为了更好的理解。

我们可以根据自己的情况,自定义我们自己的依赖作用域,就像我们上面说的 跟随 Application 生命周期的,跟随 Activity 生命周期的,或者 User 生命周期的等等。

举个例子 我们定义俩个 AppScoped, ActivityScoped. 分别让我们的依赖实现 全局单例和Activity内单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* APP全局单例
* 此注解使用的 Component 要全局范围内唯一 ,不然无法实现全局单例
*/
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface APPScoped {
}


/**
* activity 内单例
* 使用 此注解的Component 生命周期要跟随 Activity 的生命周期。
*/
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScoped {
}
  1. 创建一个类 SingletonObj 让其在 Activity范围内 单例, 让 DBManager 全局单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ActivityScoped
public class SingletonObj {


@Inject
public SingletonObj(){}
}

/**
*
*/
@APPScoped
public class DBManager {

@Inject DBHelper helper;

@Inject
public DBManager(){}
}
  1. 定义 Component ,注意 AppScoped , ActivityScoped 的位置
1
2
3
4
5
6
7
8
9
10
@APPScoped
@Component(modules = { APIModule.class,APPModule.class})
public interface APPComponent {

MainComponent plus(MainModule module);

SecondComponent plus(SecondModule module);

DBManager getDBManager();
}
1
2
3
4
5
6
@ActivityScoped
@Subcomponent(modules = MainModule.class)
public interface MainComponent {

void inject(MainActivity mainActivity);
}
1
2
3
4
5
@ActivityScoped
@Subcomponent(modules = SecondModule.class)
public interface SecondComponent {
void inject(SecondActivity activity);
}
  1. 获取 Component 并开始注入

在 Application 获取 AppComponent 的实例 ,并保持唯一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MApplication extends Application {

private APPComponent appComponent;

@Override
public void onCreate() {
super.onCreate();

appComponent = DaggerAPPComponent.builder()
.aPPModule(new APPModule(this))
.build();
}

public APPComponent getAppComponent() {
return appComponent;
}
}

在 MainActivity 获取到 MainComponent 的实例 并注入

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
40
41
42
43
44
45
46
47
48
49
50
51
public class MainActivity extends AppCompatActivity implements View.OnClickListener{


@Inject
DBManager dbManager;


@Inject DBManager dbManager1;

@Inject
SingletonObj mainSingleton;

@Inject
SingletonObj mainSingleton1;



@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bt_to_second).setOnClickListener(this);
findViewById(R.id.bt_to_third).setOnClickListener(this);


((MApplication)getApplication()).getAppComponent()
.plus(new MainModule())
.inject(this);

Log.e(MainActivity.class.getSimpleName(), "onCreate: appdb-->"+((MApplication)getApplication()).getAppComponent().getDBManager().hashCode());

//查看 是否和 second的一致,是否是全局范围内单例
if (dbManager==dbManager1) {
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode());
}else{
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}
//主要看 这个 和 second的是否一致,是否是activity范围内单例。
if (mainSingleton==mainSingleton1){
Log.e(MainActivity.class.getSimpleName(), "onCreate: main-singleton->"+mainSingleton.hashCode());
}else{
Log.e(MainActivity.class.getSimpleName(), "onCreate: main:"+mainSingleton.hashCode());
Log.e(MainActivity.class.getSimpleName(), "onCreate: main1:"+mainSingleton1.hashCode());
}


}


}

在 SecondActivity 获取到 SecondComponent 的实例 并注入 ,这里就可以看出来 是否是 范围内单例。

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
40
public class SecondActivity extends AppCompatActivity {

@Inject
DBManager dbManager;

@Inject
DBManager dbManager1;

@Inject
SingletonObj mainSingleton;
@Inject
SingletonObj mainSingleton1;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);

((MApplication)getApplication()).getAppComponent()
.plus(new SecondModule())
.inject(this);

if (dbManager==dbManager1) {
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode());
}else{
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}

if (mainSingleton==mainSingleton1){
Log.e(SecondActivity.class.getSimpleName(), "onCreate: main-singleton>"+mainSingleton.hashCode());
}else{
Log.e(SecondActivity.class.getSimpleName(), "onCreate: main:"+mainSingleton.hashCode());
Log.e(SecondActivity.class.getSimpleName(), "onCreate: main1:"+mainSingleton1.hashCode());
}


}
}

log 可以看出 范围内单例

1
2
3
4
5
6
E/MainActivity: onCreate: appdb-->229426894
onCreate: dbmanager-sintleton->229426894
onCreate: main-singleton->142055919

E/SecondActivity: onCreate: dbmanager-singleton->229426894
onCreate: main-singleton>241744847

总结 :我们可以通过 @Scope 随意自定义我们自己的作用域,当然不是说我们定义了 ActivityScoped 他就能保证 Activity内单例了,要配合 Component 范围并用对位置。


这些Demo 的代码 我放在了 Github

基础部分就先介绍这些吧,接下来我会继续 Dagger2-Android 的分享。

参考资料

坚持原创技术分享,您的支持将鼓励我继续创作!