在之前的几篇文章中,已经了解了Dagger为何物, 如何用,使用中可能出现的问题。其中在使用环节只是简单介绍了使用@Inject注解添加依赖注入。但是@Inject有一个先天性缺陷–对于第三方jar包中的类,我们无法在其构造器中添加@Inject注解
例如我们在使用OkHttp发送网络请求的时候,经常会使用OkHttpClient类。
OkHttpClient okHttpClient;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 主动初始化OkHttpClient对象
okHttpClient = new OkHttpClient();
}
但是如果我们要使用Dagger的@Inject依赖注入OkHttpClient对象就没有办法了, 因为okhttp是以依赖包的方式被导入到项目,我们无法修改其中的代码。如果是这种情况,就要使用接下来将要介绍的@Module注解
@Module
用Module标注的类是专门用来提供依赖的,不同于@Inject需要在构造函数上标记才能提供依赖,@Module可以标记任何类提供依赖。 可以将其看成是一个简易的工厂类, 具体使用步骤如下(以使用OkHttp为例):
一、 创建类并使用@Module注解,并添加被依赖类的工厂方法,如下所示
@Module
public class UserModule {
@Provides
OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
return okHttpClient;
}
}
细心的童鞋已经发现了在provideOkHttpClient()方法上添加了一个@Provide的注解,这是什么鬼?
用@Provide来标注一个方法,该方法可以在需要提供依赖时被调用。当Dagger实例化某一个被依赖类时,会到Module 类中查找符合以下两个条件的方法来构造示例
- 方法被@Provide注解
- 方法的返回类型恰好是所需要的被依赖类类型
二、@Module类写好了之后,需要将其与Component建立关联,只有这样Component(注射器)才知道去哪里构造具体的实例对象
@Component(modules = UserModule.class)
public interface UserComponent {
void bind(MainActivity mainActivity);
}
可以看到在@Component注解中,添加了modules参数并指向我们创建的@Module类 UserModule
三、在MainActivity中对声明的OkHttpClient对象添加@Inject注解
没有错,还是@Inject注解。Dagger在初始化被@Inject注解的对象的时候会按照如下步骤去实例化:
- 在Component中的Module中查找是否存在创建该类的方法,并被@Provide注解
- 若存在创建类方法,查看该方法是否存在参数
- 若存在,则从步骤1开始依次初始化每个参数(有点递归的赶脚)
- 若不存在,则直接初始化该类实例,一次依赖注入到此结束
- 若在所有的Module中都不存在创建类方法,则查找Inject注解的构造函数,具体详情见Dagger2 初体验
同使用@Inject时一样,如果在一个Module类中存在多个返回类型一样的方法,那Dagger会如何选择工厂方法并返回实例对象呢?
我们通过一个使用User的简单demo来实践一下,首先在UserModule中添加如下两个方法
@Provides
User getUserByAge() {
return new User(10);
}
@Provides
User getUserByName() {
return new User("DANNY");
}
然后编译工程,同样会发现编译报错,log如下图所示
从log中可以看出,报错原因是以为User被多次绑定,导致编译不过。那么这种情况就要使用一种限定符告诉Dagger具体该使用Module中的哪一种工厂方法来实例化对象。 而这种限定符就@Qualifier
@Qualifier
@Qualifier 英文含义限定词,它的作用和函数的重载很像。它告诉Dagger依赖需求方 创建数据的时候使用哪个依赖提供方。 更深入一点就是使用@Qualifier我们可以自定义注解,通过自定义注解可以在Module类中区分开不同情况该使用哪一个工厂方法创建被依赖对象
具体使用步骤如下
一、使用@Qualifier创建自定义注解类
创建两个接口,分别叫UserAge和UserName,如下所示
@Qualifier
public @interface UserAge {
}
@Qualifier
public @interface UserName {
}
通过这种方式,我们就自定义了两种注解,分别为@UserAge和@UserName
注意:在interface之前也有一个@符号
二、在Module类中,分别使用自定义注解@UserAge和@UserName注解相应的工厂方法
@Provides
@UserAge
User getUserByAge() {
return new User(10);
}
@Provides
@UserName
User getUserByName() {
return new User("DANNY");
}
可以看到getUserByAge和getUserByName方法除了之前的@Provide注解之外,又重新被添加了@UserAge和@UserName的注解,通过这种方式就讲这两个方法进行了区分
三、在被依赖对象的声明处,使用自定义注解标识相应的变量
在MainActivity中,分别声明user1和user2,并使用不同的自定义注解标识,如下所示:
@Inject
@UserName
User user1;
@Inject
@UserAge
User user2;
重新编译,此时编译可以成功。 通过这种方式,Dagger已经知道当初始化user1和user2时分别该使用的工厂方法。最后通过将两个User的age和name显示到两个TextView上验证一下是否正确
textView = ((TextView) findViewById(R.id.tv1));
textView2 = ((TextView) findViewById(R.id.tv2));
textView.setText("user1 name is " + user1.getName());
textView2.setText("user2 age is " + user2.getAge());
运行后,显示如下:
最后还有一个问题,如果在MainActivity中,除了user1和user2之外,还有一个user3,但是user3并没有被自定义注解标识,那会怎么样呢?
@Inject
@UserName
User user1;
@Inject
@UserAge
User user2;
@Inject
User user3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerUserComponent.builder().build().bind(this);
Log.e("DANNY", "user3 is " + user3.getName());
textView = ((TextView) findViewById(R.id.tv1));
textView2 = ((TextView) findViewById(R.id.tv2));
textView.setText("user1 name is " + user1.getName());
textView2.setText("user2 age is " + user2.getAge());
}
可以看到user3并没有被自定义注解标识,那在onCreate方法中打印出的会是什么呢? 为了demo更加完善,将之前的User的代码也贴出来
public class User {
private String name;
private int age;
public User() {
Log.e("DANNY", "default construction called");
}
@Inject
public User(Vip vip) {
Log.e("DANNY", "construction with vip param called");
}
@Inject
public void init2() {
Log.e("DANNY", "init2 is called");
}
@Inject
public void init() {
Log.e("DANNY", "init is called");
}
public User(String name) {
this.name = name;
}
public User(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
}
在User中只有一个带Vip参数的构造器添加了@Inject注解。 最后运行一下程序,看一下打印的log如下: