Dagger2 从入门到住院(一)

Dagger2 从入门到住院(一)_第1张图片
加油.png

对于 dagger2 ,也看了不少文章了,之前也在项目中使用过,但是没有系统总结一遍总觉得似懂非懂的感觉,索信今天来总结一下,打通任督二脉~ dagger2 是在编译时期通过 apt 插件自动注入依赖的框架,主要用处是为了模块间解耦。关于依赖注入,可以先看看我上一篇博文 解耦技巧——依赖注入! ,有利于接下来 dagger2 的研究!

一、Dagger2 配置

  • 在工程 gradle 文件中添加 apt 插件
dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
        //添加apt插件
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
  • 在 app module 中添加依赖
 apply plugin: 'com.android.application'
  //添加如下代码,应用apt插件
  apply plugin: 'com.neenbedankt.android-apt'
  ...
  dependencies {
      ...
      compile 'com.google.dagger:dagger:2.4'
      apt 'com.google.dagger:dagger-compiler:2.4'
      //java注解
      compile 'org.glassfish:javax.annotation:10.0-b28'
      ...
  }

dagger 2 的配置就完成了。

二、Dagger 2 的简单使用

我们先抛开晦涩难懂的概念,从实际使用为主逐个总结!还是先以制造 car 为例子:

 public class Car {
    private String carType;
    @Inject
    public Car() {
        carType = "宝马";
    } 
    public String getCarType() {
        return carType;
    }
}

在依赖类的构造方法中添加 @Inject 注解,在 MainActivity 中定义全局 Car 变量,同时添加 @Inject 注解,表示将 Car 实例注入 MainActivity ,这个时候还没完,需要提供一个类之间依赖的桥梁(component),也类似于注入器。新建 MainComponent 接口,如下:

//用@Component表示这个接口是一个连接器,能用@Component注解的只
//能是interface或者抽象类
@Component
public interface MainComponent {

   /**
     * 需要用到这个连接器的对象,就是这个对象里面有需要注入的属性
     * (被标记为@Inject的属性)
     * 这里inject表示注入的意思,这个方法名可以随意更改,但建议就
     * 用inject开头。
     */
    void injectToMain(MainActivity mainActivity);
}

这时候点击 Rebuild 工程,Dagger2 会帮我们根据刚刚添加的注入器(component),自动生成一个 DaggerMainComponent 类,必须在 MainActivity 中调用方法注入才行。具体原理后面说。

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

现在 debug 看看。

Dagger2 从入门到住院(一)_第2张图片
空构造注入.png

一辆 car 已经轻松的制造成功了。

这个时候要是 Car 有多个重载构造呢?而且构造携带参数呢?我们尝试着再添加一个构造,也给新构造添加 @Inject 注解。

 @Inject
 public Car(String carType) {
    this.carType = carType;
    }

依然 debug 运行。卧槽,报错了~~

Dagger2 从入门到住院(一)_第3张图片
多个inject注解.png

于是我们把原来的空构造删除,只留下带参数的构造,再次 debug,咦?还是报错了

Error:(22, 10) 错误: java.lang.String cannot be provided without an @Inject constructor or from an @Provides-annotated method.
java.lang.String is injected at
com.sunnada.daggerdemo.Car.(carType)
com.sunnada.daggerdemo.Car is injected at
com.sunnada.daggerdemo.MainActivity.mCar
com.sunnada.daggerdemo.MainActivity is injected at
com.sunnada.daggerdemo.component.MainComponent.inject(mainActivity)

由此得出结论:@Inject 只能注解其中一个构造方法,不能注解多个构造,Dagger2 不能确认调用哪一个构造来实例化对象

我们现在给依赖类构造添加 @Inject ,由 MainComponent (注入器)注入生成实例。那么这时考虑以下场景:

  • 依赖类的构造存在参数,如果我们手动实例化对象的时候,一般是手动传入实参实例化对象,那么现在由 Dagger 2 自动注入该怎么传参呢?
  • 假如我们需要注入第三方框架的实例对象,那么显然是不可能给构造添加 @Inject 注解的。

这时候 @Module 就开始登场了

@Module——类似一个实例工厂,负责生产需要注入的类的实例。

添加 MainModule 类如下:

/*
@Module注解表示这个类提供生成一些实例用于注入
 */
@Module
public class MainModule {
    /**
     * 这个方法需要一个String参数,在Dagger2注入中,这些参数也是注入形式的,也就是
     * 要有其他对方提供参数poems的生成,不然会造成编译出错
     */
    @Provides
    public Car provideCar(String carType) {
        return new Car(carType);
    }
    /**
     * 这里提供了一个生成String的方法,在这个Module里生成Poetry实例时,会查找到这里
     * 可以为上面提供String类型的参数
     */
    @Provides
    public String provideCarType() {
        return "奔驰";
    }

    /**
     * @return 返回注入对象
     * @Provides 注解表示这个方法是用来创建某个实例对象的,这里我们创建返回Gson对象
     * 方法名随便,一般用provideXXX结构
     */
    @Provides
    public Gson provideGson() {
        return new Gson();
    }
}

MainModule (对象实例工厂) 建好了,可以看到生产了 Car 实例,同时为 Car 生产了构造参数 String ,生产了 第三方框架 Gson 实例。工厂建好了,需要把工厂跟MainComponent (注入器)连接起来,让MainComponent (注入器)知道要注入哪些实例。

//用@Component表示这个接口是一个连接器,能用@Component注解的只
//能是interface或者抽象类
// modules 可以添加多个,表示连接到实例生产方
@Component(modules = {MainModule.class})
public interface MainComponent {

    /**
     * 需要用到这个连接器的对象,就是这个对象里面有需要注入的属性
     * (被标记为@Inject的属性)
     * 这里inject表示注入的意思,这个方法名可以随意更改,但建议就
     * 用inject开头。
     */
    void injectToMain(MainActivity mainActivity);
}

最后的依赖需求方(MainActivity)代码:

public class MainActivity extends AppCompatActivity {

    @Inject
    Car mCar;

    @Inject
    Gson mGson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.builder()
                .build()
                .injectToMain(this);
        String carType = mCar.getCarType();
        String json = mGson.toJson(mCar);
    }
}

debug 运行效果:

Dagger2 从入门到住院(一)_第4张图片
module依赖注入.png

清楚的看出 car ,Gson 实例都已成功注入依赖需求方(MainActivity)。这样就解决了 第三方框架对象实例的注入。值得注意的是:这里注入的 car 实例 调用的是有参数的构造,这势必跟 MainModule 中的 provideCarType 有关 。

最后总结 实例构建的过程:

  1. 构建实例时,首先从 Module 中查找实例创建方法。
  2. 若 Module 中存在创建方法,则看此创建方法有无参数
  • 若有参数,这些参数也是由 Component 提供的,返回步骤1查找类实例创建方法,逐一生成参数实例,最后再生成最终类实例。
  • 若无参数,直接由该方法生成实例。
  1. 若 Module 中没有创建实例方法,则从构造函数中查找用 @Inject 注解的构造方法。
    • 若该构造有参数,则也是返回步骤1逐一生成参数实例,最终调用该构造生成实例*
    • 若该构造无参数,则直接调用生成实例。

从以上实例构建过程可以得出结论:Module 提供的实例是优先于 添加 @Inject 注解的构造方法的,Module 类似于一个生产实例的工厂,一旦没有 Module ,@Inject 只适合生成无参数的构造实例,如果有参数,则必须 添加 Module。

最后,该有的思考

有些人或许会觉得这样代码反而变啰嗦了,有啥好处?其实在上面举的例子相对简单,但是假如代码量越来越复杂的时候,一个依赖类在各个地方都实例化,这时候依赖方与依赖类耦合度很强。假如依赖类构造一改变,会造成很大的问题。关于解耦相关,可以看看我之前这篇文章解耦技巧——依赖注入!而我们用 Dagger2 来自动注入所需依赖,所有的实例全部用 Module 来管理,一旦需求改变,也只需修改对应的 Module 即可

声明:关于 Dagger2 的文章在网上也是一抓一大把了,可是光看不自己总结往往效果不好,所以自己参考一些文章先总结一下 Dagger2 的基本用法,不好之处还请指出。

参考链接:Dagger2图文完全教程,Android:dagger2让你爱不释手-重点概念讲解、融合篇,Android:dagger2让你爱不释手-基础依赖注入框架篇,Android:dagger2让你爱不释手-终结篇

更多原创文章会在公众号第一时间推送,欢迎扫码关注 张少林同学

张少林同学.jpg

你可能感兴趣的:(Dagger2 从入门到住院(一))