Dagger2是一套依赖注入框架,在编译期间自动生成代码,创建依赖的对象。项目中使用Dagger2可以降低代码的耦合度。
使用Dagger2库,重点是了解其中的各种注解并熟练使用,下面看一下具体用例。
注:为了能够更直观地了解Dagger2的使用,本文用例会尽量简化不相关的业务逻辑
环境配置
配置下build.gradle
即可
dependencies {
implementation 'com.google.dagger:dagger:2.14.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.14.1'
}
@Inject和@Component注解
使用@Inject
和@Component
注解是Dagger2最基本的使用方式,来看一个简单的Demo,向 MainActivity
里注入 Apple
:
- 编写
Apple
类代码
public class Apple {
@Inject //构造器注入注解,在被注入对象的构造器上标注
public Apple() {
}
public void print() {
Log.d(TAG,"This is an apple");
}
}
- 编写
MainActivity
类代码
@Inject //属性注入注解,在需要注入的属性上标注,不能为private,如果需要用private可以使用方法注入,下面会讲
public Apple apple;
@Component //用于连接注入类和目标类的接口,建议命名:目标类名+Component
interface MainActivityComponent {
void inject(MainActivity activity);
}
- 编译项目
这时候编译项目会在build目录下生成以下文件:
Apple_Factory.java
DaggerMainActivity_MainActivityComponent.java
MainActivity_MembersInjector.java
这些文件是Dagger2框架帮我们自动生成的,用于帮我们注入依赖。
- 在
MainActivity
注入依赖
使用依赖注入
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//给之前定义的apple注入依赖
DaggerMainActivity_MainActivityComponent.builder().build().inject(this);
apple.print();
}
如果不使用依赖注入,那写法将会是下面这样:
//直接new出对象,耦合性高,在项目较复杂的情况下扩展性较差
apple = new Apple();
这样看起来比不使用Dagger2注入对象要简单很多,但是如果对象引用的地方很多,构造复杂,那么一旦改变构造,工作量会很大,这也是Dagge2最重要的优势——解耦。
- 小结
以上是Dagger2最简单的使用方法,注入依赖后,会用在Apple
类中标注@Inject
注解的构造器自动创建apple
对象。而@Component
标注的接口用于连接目标类和注入类的工厂Apple_Factory
。
- 方法注入
如果注入的对象需要设置为private
,那我们可以使用方法注入,示例:
private Apple apple;
@Inject
public void setApple(Apple apple) {
this.apple = apple;
}
上述代码作用等同于:
@Inject
public Apple apple;
- 拓展
如果现在需要修改Apple
类的构造器,需要加一个Color
参数,如下:
public class Apple {
public Color color;
@Inject
public Apple(Color color) {
this.color = color;
}
public void print() {
Log.d(TAG, "This is an apple");
}
}
这时候构造器中的color
也需要注入依赖,创建一个简单的Color类并在构造器上标注注解@Inject即可
public class Color {
@Inject
public Color() {
}
}
这样在构建apple
对象时会寻找标注了@Inject
注解的Color
的构造器新建color
对象用于apple
对象的构造。
除了@Inject
和@Component
之外,Dagger2中还有其他注解,下文会说明。
@Modele和@Provides注解
@Inject
注解存在局限性,以下两种情况时不能使用:
- 注入的对象来自第三方库
- 注入的对象声明为抽象类或接口(依赖倒置原则)
比如上述的例子修改下,添加一个抽象类Fruit
:
public abstract class Fruit {
public abstract void print();
}
修改后Apple
类继承Fruit
:
public class Apple extends Fruit {
@Inject
public Apple() {
}
@Override
public void print() {
Log.d(TAG,"This is an Apple");
}
}
这时候,如果只按照下面的形式声明,是无法注入apple
对象的,运行会报错。
@Inject
public Fruit apple;
这时候就需要用到@Modele
和@Provides
注解,完整示例如下:
- 创建
Fruit
类和Apple
类
Fruit
抽象类:
public abstract class Fruit {
public abstract void print();
}
Apple
类:
public class Apple extends Fruit {
//这里注意可以不再标注@Inject
public Apple() {
}
@Override
public void print() {
Log.d(TAG,"This is an Apple");
}
}
- 创建Module类
@Module //注解提供注入对象的类
public class FruitModule {
@Provides //注解方法,方法返回注入的对象
Fruit provideFruit() {
return new Apple();
}
}
- 在
MainActivity
中注入对象
@Inject //和之前一样的注解
public Fruit apple;
@Component(modules = FruitModule.class) //这里注意,需要加上Module类
interface MainActivityComponent {
void inject(MainActivity activity);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//和之前一样的方式注入对象
DaggerMainActivity_MainActivityComponent.builder().build().inject(this);
apple.print();
}
- 小结
使用@Inject
和@Component
注解注入对象时,我们需要在声明的注入类的构造器上加上@Inject
注解来使对象被创建。
如果遇到注入类构造器不方便编辑时(比如来自三方库,抽象类,接口),则可以使用@Module
和@Provides
注解,将对像获得由构造器获得转变为由方法返回,使对象的创建形式更加可控。
再形象看一下两者的区别(不想看可以跳过):
仅使用@Inject
和@Component
注入对象build目录下生成的文件如下
Apple_Factory.java
DaggerMainActivity_MainActivityComponent.java
MainActivity_MembersInjector.java
使用@Module
和@Provides
注入对象build目录下生成的文件如下
FruitModule_ProvideAppleFactory
DaggerMainActivity_MainActivityComponent.java
MainActivity_MembersInjector.java
区别在于前者是Apple_Factory.java
而后者是FruitModule_ProvideAppleFactory
,这两个类都是用于提供对象的,而DaggerMainActivity_MainActivityComponent.java
类实现了MainActivityComponent
接口,用于将目标类和提供对象的类连接起来。所以两者的区别仅在于提供对象的方式不同,再具体可以看这两个差异类的实现。
- 拓展
Component接口可以指定多个Module类,便于将它们一起注入,比如在这个例子中,可以按下述方式编写:
@Component(modules = {FruitModule.class, OtherModule.class})
interface MainActivityComponent {
void inject(MainActivity activity);
}
这样我们便可以注入OtherModule
中可以获得的对象。
@Named注解
上述的@Module
和@Provides
注解仅能返回一个继承了Fruit的对象,如果再加入一个Banana
类,继承Fruit
并在MainActivity
中注入,这时候就需要使用@Named
注解。基于上述代码,示例如下:
- 新建
Banana
类
public class Banana extends Fruit {
@Override
public void print() {
Log.d(TAG, "This is a Banana");
}
}
- 修改
FruitModule
类
@Module
public class FruitModule {
@Provides
@Named("Apple") //用@Named注解标注返回Apple的方法
Fruit provideApple() {
return new Apple();
}
@Provides
@Named("Banana") //用@Named注解标注返回Banana的方法
Fruit provideBanana() {
return new Banana();
}
}
- 修改
MainActivity
类
@Inject
@Named("Apple")
public Fruit apple; //用@Named注解标注这个声明注入的为Apple
@Inject
@Named("Banana")
public Fruit banana; //用@Named注解标注这个声明注入的为Banana
@Component(modules = FruitModule.class)
interface MainActivityComponent {
void inject(MainActivity activity);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainActivity_MainActivityComponent.builder().build().inject(this);
apple.print();
banana.print();
}
- 小结
@Named
注解使用比较简单,只要将Module类中的标注和目标类中声明注入类的标注一一对应即可。
@Qualifier注解
@Qualifier
注解的作用和@Named
相同,只是实现有所区别,直接看示例:
- 修改FruitModule类
@Module
public class FruitModule {
@Qualifier //定义Apple的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvideApple{}
@Qualifier //定义Banana的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvideBanana{}
@Provides
@ProvideApple //表示这个方法返回Apple
Fruit provideApple() {
return new Apple();
}
@Provides
@ProvideBanana //表示这个方法返回Banana
Fruit provideBanana() {
return new Banana();
}
}
- 修改MainActivity类
@Inject
@FruitModule.ProvideApple //表示这个对象注入Apple
public Fruit apple;
@Inject
@FruitModule.ProvideBanana //表示这个对象注入Banana
public Fruit banana;
@Component(modules = FruitModule.class)
interface MainActivityComponent {
void inject(MainActivity activity);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainActivity_MainActivityComponent.builder().build().inject(this);
apple.print();
banana.print();
}
- 小结
@Qualifier
和@Named
注解作用是一样的,区别是@Named
使用字符串来区分不同的返回对象,而@Qualifier
用自定义的接口来区分,这样可以提高代码的可读性,且不容易出错。
内容有点多,不得不分成两篇总结了。
贴个链接:Dagger2 使用总结(二)