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

依赖注入
什么是 依赖。
举个例子
有一个 A 类 它里面定了一个 B 类型的 属性 b; 这里 A 就依赖了 B;
1 |
|
这就意味着 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 | public class MainActivity extends AppCompatActivity { |
@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
()
public interface MainComponent {
void inject(MainActivity mainActivity);
DBManager getDBManager();
}
// 在需要被注入的类中注入 例如:
public class MainActivity extends AppCompatActivity {
DBManager dbManager;
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 |
|
当然还有另外一种方法不用 @SubComponent,使用 Component 并使用 denpendencies 引用上 ApplicationComponent 这样就相当于将 ApplicationComponent 组合进来。
@Module && @Provides
@Module 这个注解用来标注提供依赖的工厂。对的,工厂,我是这么理解的。
@Provides 这个注解用在提供定义提供依赖的方法上,表示向外提供依赖。方法的返回类型就是提供的依赖类型。
前面提到的 @Inject 可以在注解在构造函数以用来提供依赖;而在 @Inject 不能满足需要的时候这个就派上用场了。
例如 我注入一个 字符串,数字或一个 第三方依赖的对象 例如 Retrofit ,@Inject 已经满足不了啦。
这个时候可以创建一个类 专门用来提供这些依赖,并使用 @Module 注解,然后在 Component 的属性 modules 引用上就可以使用了。
1 | // 需要注入的 Activity |
@Named
在依赖迷失时给出方向。
解释一下 依赖迷失:
依旧是上面那个例子,现在 都是根据返回值类型来注入的,现在都是不同的类型所以还没有出现迷失的情况;
现在我如果要加上 地址 属性;如下
1 |
|
这个时候 在 module 中 有两个返回 String 类型的 方法,Dagger2 这个时候就不知道注入哪一个了,所以就会出现 依赖迷失 的情况;
1 | 错误: [Dagger/DuplicateBindings] java.lang.String is bound multiple times: |
简单的解决方法就是在 属性和提供依赖上 加上 @Named 注解
1 |
|
这样就可以解决了 依赖迷失。
@Qualifier
@Named 的元注解,解决依赖迷失的大 Boss;看一下 @Named 的源码,@Named 就是被 @Qualifier 注解的。
1 |
|
如果怕通过 @Named 写字符串的方式容易出错就可以通过 @Qualifier 自定义注解来实现。
下面举个例子,再加一个 身高属性。定义两个注解来区分 @Age and @Height.
1 |
|
@Singleton
配合 @Component 实现 范围内单例
@Singleton 必须和 @Component 配合才能实现单例,而且只能保证在 @Component 范围内单例,如果要实现全局单例,就必须要保证 @Component 的实例在全局范围内只有一个,类似 Application 。
举个例子,我要 DBManager 在全局单例,需要以下几个步骤
- 在 DBManger 上使用
@Singleton或者 在 @Provides 修饰的方法上加。 - 然后在 AppComponent 也加上()
- 在 Application 中 获取 AppComponent 实例,让其全局唯一。
1 | // 1.DBManager 标注 @Singleton |
测试结果必须是全局唯一单例,看一下 log
1 | E/MainActivity: onCreate: appdb-->192114699 |
@Singleton 的作用域 始终是跟随所在的 Component 的实例的,如果超出它的范围就无法保证单例。
就拿上个例子举例,如果每次 在 Activity 注入的时候 不从 Application 获取实例而是每次都是使用 DaggerAppComponent 创建一个新的 实例 ,那么就无法保证两个 Activity 内的 DBManager 都是一个实例了,因为每个 Activity 都是获取新的 AppComponent 的实例,它的作用范围只能在单个实例内。
下面我实现一个 只在 Activity 范围实现单例的 例子,就是把上面的代码改改,在Activity注入的时候 创建新的 Component 实例。
1 |
|
总结 : Dagger2 实现单例要 @Singleton 和 @Component || @SubComponent 配合使用,只能实现范围内(实例内)单例,所以范围要控制好。只要范围控制好,随意 Activity 或者 Application 范围。
@Scope
作用域 上面说到的 @Singleton 就是它的默认实现,也是唯一一个默认实现。
看一下 @Singleton 的源码
1 | /** |
@Singleton 能够实现范围内单例 主要是 @Scope 在起作用。默认实现叫 Singleton 也是为了更好的理解。
我们可以根据自己的情况,自定义我们自己的依赖作用域,就像我们上面说的 跟随 Application 生命周期的,跟随 Activity 生命周期的,或者 User 生命周期的等等。
举个例子 我们定义俩个 AppScoped, ActivityScoped. 分别让我们的依赖实现 全局单例和Activity内单例
1 | /** |
- 创建一个类 SingletonObj 让其在 Activity范围内 单例, 让 DBManager 全局单例
1 |
|
- 定义 Component ,注意 AppScoped , ActivityScoped 的位置
1 |
|
1 |
|
1 |
|
- 获取 Component 并开始注入
在 Application 获取 AppComponent 的实例 ,并保持唯一。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class MApplication extends Application {
private APPComponent appComponent;
public void onCreate() {
super.onCreate();
appComponent = DaggerAPPComponent.builder()
.aPPModule(new APPModule(this))
.build();
}
public APPComponent getAppComponent() {
return appComponent;
}
}
在 MainActivity 获取到 MainComponent 的实例 并注入
1 | public class MainActivity extends AppCompatActivity implements View.OnClickListener{ |
在 SecondActivity 获取到 SecondComponent 的实例 并注入 ,这里就可以看出来 是否是 范围内单例。
1 | public class SecondActivity extends AppCompatActivity { |
log 可以看出 范围内单例
1 | E/MainActivity: onCreate: appdb-->229426894 |
总结 :我们可以通过 @Scope 随意自定义我们自己的作用域,当然不是说我们定义了 ActivityScoped 他就能保证 Activity内单例了,要配合 Component 范围并用对位置。
这些Demo 的代码 我放在了 Github
基础部分就先介绍这些吧,接下来我会继续 Dagger2-Android 的分享。
参考资料
- https://google.github.io/dagger/
- https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-introduction-be6580cb3edb
- https://proandroiddev.com/how-to-dagger-2-with-android-part-1-18b5b941453f
- https://blog.csdn.net/briblue/article/details/75578459
- https://juejin.im/entry/593cee56ac502e006b3dc9c2
- https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-dagger-2-part-i-f2de5564ab25