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 使用总结(二)