Dagger2 进阶

在之前的几篇文章中,已经了解了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 类中查找符合以下两个条件的方法来构造示例
  1. 方法被@Provide注解
  2. 方法的返回类型恰好是所需要的被依赖类类型
二、@Module类写好了之后,需要将其与Component建立关联,只有这样Component(注射器)才知道去哪里构造具体的实例对象
@Component(modules = UserModule.class)
public interface UserComponent {
    void bind(MainActivity mainActivity);
}

可以看到在@Component注解中,添加了modules参数并指向我们创建的@Module类 UserModule

三、在MainActivity中对声明的OkHttpClient对象添加@Inject注解

没有错,还是@Inject注解。Dagger在初始化被@Inject注解的对象的时候会按照如下步骤去实例化:

  1. 在Component中的Module中查找是否存在创建该类的方法,并被@Provide注解
  2. 若存在创建类方法,查看该方法是否存在参数
    1. 若存在,则从步骤1开始依次初始化每个参数(有点递归的赶脚)
    2. 若不存在,则直接初始化该类实例,一次依赖注入到此结束
  3. 若在所有的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如下:
这里写图片描述

通过log可以看出,user1和user2分别是调用Module中的getUserByName和getUserByAge方法获取的。 而user3并没有被自定义注解标识,则会调用被@Inject注解的构造器进行初始化(注意:如果此时在User中并没有被@Inject标识的构造器,则编译还是会报错T_T)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值