Dagger2初探

在你逛Github的时候,如果遇到一些大型的开源项目,恐怕最眼熟的几个关键字就是RxJava、Retrofit、MVP、Dagger2、RetroLambda等等。在之前的文章中,我们先后学习过Rxjava、Retrofit以及Lambda表达式的使用,那么今天我们就来看看Dagger2是什么玩意

Dagger2是一款使用在Java和Android上的依赖注入的一个类库,目前Dagger有两个分支,一个由Square维护,一个为Google在前者的基础上开出的分支,即Dagger2

那么问题来了,你说了这么多基本概念,我还是一头雾水,这个到底能给我带来什么好处呢?

回顾一下我们之前是怎么初始化一个对象的

public class ModelD {
    int a;
    int b;
    public ModelD() {
    }
    public int getA() {
        return a;
    }
    public void setA(int a) {
        this.a = a;
    }
    public int getB() {
        return b;
    }
    public void setB(int b) {
        this.b = b;
    }
}

这是一个非常标准的对象。使用也是直接声明后初始化,进行相应set/get操作

ModelD modelD;

modelD=new ModelD();
modelD.setA(10);
modelD.setB(20);
Log.d("MainActivity", modelD.getA() + " " + modelD.getB());

这样就是一个典型的用到哪里new到哪里的用例。有没有觉得好心疼?每次检查代码的时候,都得翻遍代码去找哪里new的?为了解决这个问题,统一化管理各种Model,从现在开始,我们就来学习Dagger2

环境搭建

  1. 在你项目的build.gradle下添加
buildscript {
    ......
    dependencies {
        ......
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
  1. 在你主工程的build.gradle下添加
apply plugin: 'com.neenbedankt.android-apt'
......
dependencies {
    ......
    compile 'com.google.dagger:dagger:2.5'
    apt "com.google.dagger:dagger-compiler:2.5"
    provided 'javax.annotation:javax.annotation-api:1.2'
}

这样就完成了环境搭建了

@Inject

我们先简单接触一下Dagger2,看看他到底有什么功能

Inject即javax.inject.Inject。当你的类需要自动实例化的功能,那么就得在你的构造方法中通过@Inject注解来告诉Dagger2如何实例化。在完成构造方法的注解之后,后续就需要对成员变量进行注解以告诉Dagger2哪个对象需要实例化

那么我们如何使用Dagger2去对之前初始化过程进行改造呢?

public class ModelD {
    ......
    @Inject
    public ModelD() {
    }
    ......
}

@Inject
ModelD modelD;

注意这边modelD对象不可以是private

初步改造完成,但是这样还是无法运行的,我们还需要一个桥梁去连接这两部分,谁去做这个连接器呢?它就是Component

@Component

刚说了Component是一个连接器,他通过查找目标类中用Inject注解标注的成员变量,查找到相应的成员变量后,接着查找该成员变量所在的类对应的用Inject标注的构造函数(这时候就发生联系了),剩下的工作就是初始化该变量的实例并把实例进行赋值。
Component注解的类只能是接口或抽象类,每个@Component必须至少有一个抽象方法,他们的名字可以是任意的,但是格式必须符合provision或者members-injection,后面还有一种@Subcomponent时需要的一种接口方法,总共就这三种。provision在下期Subcomponent的介绍中会提及,members-injection就是我们之前写的inject方法

@Component
public interface MyComponent {
    public void inject(MainActivity activity);
}

这里强调说明下,inject中的内容代表真正消耗依赖的类型,这里是MainActivity。如果你在这边写其他对象,或者重复某个对象,在编译过程中都会出错的

编译之后Dagger2会按照上面接口规定的协议生成一个实现类,通过编译我们可以生成以Dagger为前缀的类,提供builder()来生成实例

public final class DaggerMyComponent implements MyComponent {
  private MembersInjector mainActivityMembersInjector;
  private DaggerMyComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }
  public static Builder builder() {
    return new Builder();
  }
  public static MyComponent create() {
    return builder().build();
  }
  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {
    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(ModelD_Factory.create());
  }
  @Override
  public void inject(MainActivity activity) {
    mainActivityMembersInjector.injectMembers(activity);
  }
  public static final class Builder {
    private Builder() {}
    public MyComponent build() {
      return new DaggerMyComponent(this);
    }
  }
}

这里的injectMembers就是将成员变量注入。

这样就可以直接通过无参构造方法去实例化一个对象了

DaggerMyComponent.builder().build().inject(this);

但是请注意,这种写法不适用于有参构造方法,那么有参构造方法需要怎么实现呢?我们来看看@Module与@Provides

@Modules

Modules相当于一个管理集合,他负责集中创建欲初始化的类的对象,简单的说它就是一个依赖提供者。

直接在构造方法中写Inject注解也是很麻烦的一件事:

  1. 有时候我们不能使用Inject去注解一个构造方法,比如jar包里面的
  2. 只可以在一个构造方法中添加Inject注解,不然Dagger2也不知道你初始化的到底是哪个构造方法
  3. 接口不能使用,谁知道你接口是怎么实现的

所以我们这里就需要使用Provides去注解一个方法来满足依赖。方法的返回类型就是依赖要满足的类型。

@Provides

这是添加该注解的方法,表明用来初始化某个实例对象。

我们将刚才ModelD的初始化用Modules跟Provides来实现以下

public class ModelD {
    int a;
    int b;
    public ModelD(int a, int b) {
        this.a=a;
        this.b=b;
    }
}

@Module
public class TotalModule {
    int a;
    int b;
    public TotalModule(int a, int b) {
        this.a=a;
        this.b=b;
    }
    @Provides
    public ModelD providesModelD() {
        return new ModelD(a, b);
    }
}

通过对TotalModule的初始化,将变量用于ModelD的初始化。注意这边Module的类名以及Provides注解的方名的命名规则,尽量做的规范起来

请注意,如果Provides所注解的方法有入参,这个参数一定要被Dagger得到(通过其他的provider方法或者@Inject注解的构造方法获得)。并且方法的返回值类型必须唯一,不能重复

剩下就是用Component去关联起来,不然谁知道这个Module?

@Component(modules = TotalModule.class)
public interface TotalComponent {
    void inject(MainActivity activity);
}

有个地方要注意,我们这边TotalModule显示声明了一个带2个参数的构造方法,所以在编译时注解生成的DaggerTotalComponent对象的内部类Builder跟之前就有所区别了,它强制要求我们实现TotalModule

public static final class Builder {
  private TotalModule totalModule;
  private Builder() {}
  public TotalComponent build() {
    if (totalModule == null) {
      throw new IllegalStateException(TotalModule.class.getCanonicalName() + " must be set");
    }
    return new DaggerTotalComponent(this);
  }
  public Builder totalModule(TotalModule totalModule) {
    this.totalModule = Preconditions.checkNotNull(totalModule);
    return this;
  }
}

使用时候跟之前类似

DaggerTotalComponent.builder().totalModule(new TotalModule(2, 3)).build().inject(this);

其实还有一种写法。可能你觉得刚才的写法实在是麻烦,每次TotalModule都初始化相同的数据,不需要这么麻烦

@Module
public class TotalModule2 {
    @Provides
    public ModelD providesModelD(int a, int b) {
        return new ModelD(a, b);
    }
    @Provides
    public int providesInteger() {
        return 1;
    }
}

@Module的优先级高于@Inject。优先从provides里面获取构造方法,如果找不到,再从Inject里面获取,如果什么都找不到,就报错了

到底为止,就是Dagger2的典型使用方式了

参考文章

Dagger2使用,详细解读和从Dagger1迁移的方法介绍
Dagger2使用详解
Android常用开源工具(2)-Dagger2进阶

本文演示示例已经上传到Github

首次接触Dagger2框架,个人觉得相对于其他框架来说,这玩意还是有些难度的,所以错误之处请多多指点,我也会在空闲时间外根据自己的理解对文章多多改进,谢谢支持

你可能感兴趣的:(Dagger2初探)