概念
Dependency Injection(DI,依赖注入),Dagger2(A fast dependency injector for Android and Java.)第一代由大名鼎鼎的Square公司共享出来,第二代则是由谷歌接手后推出的。
采用了apt代码自动生成技术,其注解是在编译时完成,完全不影响性能。
开始使用
在build.gradle中添加如下代码,引入dagger2
dependencies {
// dagger2
implementation 'com.google.dagger:dagger:2.15'
annotationProcessor 'com.google.dagger:dagger-compiler:2.14.1'
}
复制代码
四个注解
- @Inject Inject主要有两个作用,一个是使用在JavaBean的构造函数上,通过标记构造函数让Dagger2来使用(Dagger2通过Inject标记可以在需要这个类实例的时候来找到这个构造函数并把相关实例new出来)从而提供依赖;另一个作用就是标记在需要依赖的变量让Dagger2为其提供依赖。
- @Component Component一般用来标注接口,被标注了Component的接口在编译时会产生相应的类的实例来作为提供依赖方和需要依赖方之间的桥梁,把相关依赖注入到其中。
- @Provide 用Provide来标注一个方法,该方法可以在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Injection的变量赋值。provide主要用于标注Module里的方法。
- @Module 用Module标注的类是专门用来提供依赖的。有的人可能有些疑惑,看了上面的@Inject,需要在构造函数上标记才能提供依赖,那么如果我们需要提供的类构造函数无法修改怎么办,比如一些jar包里的类,我们无法修改源码。这时候就需要使用Module了。Module可以给不能修改源码的类提供依赖,当然,能用Inject标注的通过Module也可以提供依赖。
结合代码看几个例子:
1.1、没有Module这一层,被注入的JavaBean构造方法无参数
// 我们自己定义一个JavaBean对象,构造方法无参数
public class ABean {
@Inject
public ABean() {
}
}
复制代码
// AActivityComponent是接口或者抽象类
@Component
public interface AActivityComponent {
void inject(AActivity aActivity);
}
复制代码
/**
* 只通过@Inject @Component实现DI依赖注入
* 1、ABean是自己定义的或可编辑的类,非第三方库中的文件
* 2、ABean的构造方法无参数
*/
public class AActivity extends AppCompatActivity {
@Inject
ABean aBean;
// 这里不可以定义为private
// private ABean aBean;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a_layout);
// 上面这些编写完成,Ctrl+F9(mac使用Cmd+F9)进行一次编译,几秒后编译结束,似乎没有发生任何事情……
// 如果编译不报错的话,这个时候已经自动生成了DaggerAActivityComponent,就可以调用下面的代码完成对aBean的注入了
DaggerAActivityComponent.create().inject(this);
ToastUtils.showLong(aBean.hashCode());
}
}
复制代码
场景:水(ABean)想要注入到盒子里(AActivity),需要一根导管(AActivityComponent)做为连接。
1.2、没有Module这一层,被注入的JavaBean构造方法有参数
public class B1Bean {
private B2Bean b2Bean;
@Inject
public B1Bean(B2Bean b2Bean) {
this.b2Bean = b2Bean;
}
public String work() {
return b2Bean.work();
}
}
复制代码
public class B2Bean {
@Inject
public B2Bean() {
}
public String work() {
return "B2Bean do work";
}
}
复制代码
@Component
public interface BActivityComponent {
void inject(BActivity bActivity);
}
复制代码
/**
* 只通过@Inject @Component实现DI依赖注入
* 1、B1Bean和B2Bean都是自己定义的或可编辑的类,非第三方库中的文件
* 2、B1Bean的构造方法有参数
*/
public class BActivity extends AppCompatActivity {
@Inject
B1Bean b1Bean;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b_layout);
DaggerBActivityComponent.create().inject(this);
ToastUtils.showLong(b1Bean.work());
}
}
复制代码
说明:在BActivity中对B1Bean进行注入时,找到B1Bean的构造方法,首先判断有没有被@Inject注解,然后看到B1Bean的构造方法中有一个参数B2Bean,这个时候会继续去查看B2Bean的构造方法,发现B2Bean的构造方法有被@Inject注解,OK,创建B2Bean并注入,B1Bean有了新创建的B2Bean,B1Bean也被创建出来并被成功注入。
上面说的ABean、B1Bean、B2Bean都是我们自己创建的类,我们可以去修改并在构造方法上添加@Inject,那如果是第三方库中我们无法修改的类,该怎么办呢?
2、Module实现依赖注入
场景:水离盒子很远,我们无法直接看到水,就让工厂(Module)负责生产包装后的水,由快递(Component)运送到盒子里。
假设C1Bean和C2Bean是类库中的代码,我们无法修改,在其构造方法上添加@Inject。
// 假设C1Bean和C2Bean是类库中的代码,我们无法修改,在其构造方法上添加@Inject
public class C1Bean {
private C2Bean c2Bean;
public C1Bean(C2Bean c2Bean) {
this.c2Bean = c2Bean;
}
public String work() {
return c2Bean.work();
}
}
public class C2Bean {
public C2Bean() {
}
public String work() {
return "C2Bean do work";
}
}
复制代码
@Module
public class CActivityModule {
// 方式一:在一个方法内new一个C2Bean出来作为C1Bean的参数
// @Provides
// C1Bean provideC1Bean() {
// C2Bean c2Bean = new C2Bean();
// return new C1Bean(c2Bean);
// }
/*
方式二:
1. 把C2Bean拆分出去,通过提供另一个provideC2Bean()构建方法,构建出一个C2Bean对象
2. dagger在CActivity中注入C1Bean的时候,会到该Module中找有没有添加了@Provides注解,提供构建C2Bean对象的方法
3. 发现有添加@Provides注解提供构建C2Bean对象的方法,拿到这个创建的C2Bean做为参数传给provideC1Bean(C2Bean c2Bean)
4. 发现没有添加@Provides注解提供构建C2Bean对象的方法,会继续到C2Bean类中看C2Bean的构造方法,有没有被@Inject标注
*/
@Provides
C1Bean provideC1Bean(C2Bean c2Bean) {
return new C1Bean(c2Bean);
}
/*
接上面的方式二:
可以把这个方法注释掉,然后在C2Bean的构造方法上添加@Inject标注,验证一下上面的第4点
*/
@Provides
C2Bean provideC2Bean() {
return new C2Bean();
}
}
复制代码
// 通过modules = CActivityModule.class,告诉Component到CActivityModule这个Module中去找@Provides标注的构建方法
@Component(modules = CActivityModule.class)
public interface CActivityComponent {
void inject(CActivity cActivity);
}
复制代码
/**
* 只通过@Inject @Module @Component实现DI依赖注入
* 1、C1Bean和C2Bean是第三方库中的文件,无法编辑这个文件在构造方法上添加@Inject注解
* 2、Module类 起到一个工厂的角色,负责生产C1Bean和C2Bean
*/
public class CActivity extends AppCompatActivity {
@Inject
C1Bean c1Bean;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c_layout);
// 这里因为引入了Module,所以与1.1、1.2有点区别,需要指定需要关联的CActivityModule
DaggerCActivityComponent.builder().cActivityModule(new CActivityModule()).build().inject(this);
ToastUtils.showLong(c1Bean.work());
}
}
复制代码
上面注释中提到的两种方式,大家可以自己修改代码试一试。
总结:
- 步骤1:查找Module中是否存在创建该类的方法。
- 步骤2:若存在创建类方法,查看该方法是否存在参数
- 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
- 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
- 步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
- 步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
- 步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
概括一下就是从注解了@Inject的对象开始,从Module和注解过的构造方法中获得实例,若在获取该实例的过程中需要其他类的实例,则继续获取被需要类的实例对象的依赖,同样是从Module和标注过的构造方法中获取,并不断递归这个过程直到所有被需要的类的实例创建完成,在这个过程中Module的优先级高于注解过的构造方法。
最后
- 1、Module并不是必需的,但Component是必不可少的;
- 2、Ctrl+F9(mac使用Cmd+F9)编译后生成的Component实现类的名称是Dagger+我们所定义的Component接口的名称。
后续
@Qualifier
@Scope
@Singleton
@Subcomponent
扩展的dagger.android
感谢