Dagger 2 入门

Dagger 2

Dagger是针对Android和Java平台提供的依赖注入框架。Dagger 1.*最早版本由 JakeWharton 开发,从 2.0 版本开始正式由 Google 接手。

Dagger 2 的不同之处

依赖注入 是一种设计模式,而不是一个库。框架发展至今也有很长的时间了,面对丰富的工具提供配置与注入,我们为什么要重新造轮子?Dagger 2 是首先实现了一站式的DI实现方案,它在 Dagger 1.* 版本的基础上进行优化,更加注重开发者的体验,使工具运用变得更为简单、可以追踪、性能更高。对比常用的DI框架 Spring、Guice、Dagger 1.* ,它的优势总结如下(详细比较请看 演示文档):

  • 优点
    1. 编译时校验所有依赖配置。提早发现配置的语法及逻辑错误
    2. 调试简单,可追踪。运行时可以对 DI 的全过程添加断点调试,能够追踪到所有的调用堆栈
    3. 性能更高。相比于 Java 反射机制实现的DI框架,性能更高
  • 缺点
    1. 不够灵活。注入依赖于 Component 作为中介,需要手动调用
    2. 不是动态的。子类调用 inject 方法不能注入属于父类的属性
    3. 不能从 Guice 自动地迁移到 Dagger

如何使用

项目配置

Maven
<dependencies>
    <dependency>
        <groupId>com.google.dagger</groupId>
        <artifactId>dagger</artifactId>
        <version>2.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.dagger</groupId>
        <artifactId>dagger-compiler</artifactId>
        <version>2.0</version>
        <optional>true</optional>
    </dependency>
</dependencies>
Gradle
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.0.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' // 3
    }
}
... //略
def Dagger2_Version = '2.0'
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'
    //1. Dagger 2.0
    compile com.google.dagger:dagger:$Dagger2_Version
    apt com.google.dagger:dagger-compiler:$Dagger2_Version
    //2. Dagger 2 中会用到 @Generated 注解,而 javax.anotation.generated 在 Java 6 及以上版本中都有,Android API 中没有,所以在Android项目中一定要加此句
    provided 'org.glassfish:javax.annotation:10.0-b28'
}

语法介绍

这里演示使用的是 Google 提供的一个煮咖啡程序,你也可以下载示例代码编译和运行,参见 coffee example

声明依赖

Dagger 能够创建类的实例,并为它所需的属性提供注入。通过使用 javax.inject.Inject 指定需要实现注入的构造方法和属性。

对需要使用 Dagger 来创建对象的构造函数添加 @Inject 注解,当需要创建一个实例时,Dagger 会去获取需要的参数,并调用构造函数。

class Thermosiphon implements Pump {
  private final Heater heater;

  @Inject
  Thermosiphon(Heater heater) {
    this.heater = heater;
  }

  ...
}

Dagger 能够直接注入属性。在下面代码里, HeaderPump 可以通过 Dagger 直接注入。

class CoffeeMaker {
  @Inject Heater heater;
  @Inject Pump pump;

  ...
}

如果一个类中包含 @Inject 注解的属性,但是没有 @Inject 注解的构造函数,如果有需要 Dagger 将注入这些属性,单不会创建一个新的实例。添加一个注解了 @Inject 的无参构造函数后,Dagger可以同时创建实例。

Dagger 也支持方法注入,但是更推荐使用构造方法和属性注入。

缺少 @Inject 的类无法通过 Dagger 创建。

注入依赖

默认情况下,正如上面介绍的 Dagger 通过构造实例的方式实现注入。当需要获得一个 CoffeeMaker 的时候,Dagger会通过 new CoffeeMaker() 创建实例,并注入属性。

注意: @Inject 在以下情况下不可用:
- 接口不能构造
- 第三方类无法添加注解
- 可配置的类必须配置

由于这些缺陷,使用 @Provides 注解方法来实现注入,被注解的方法将返回说依赖的对象。

单需要注入Heater的时候, provideHeater() 将被调用:

@Provides Heater provideHeater() {
  return new ElectricHeater();
}

某些情况下 @Provides 方法可能依赖自身的对象:

@Provides Pump providePump(Thermosiphon pump) {
  return pump;
}

所有的 @Provides 方法必须属于一个 module

@Module
class DripCoffeeModule {
  @Provides Heater provideHeater() {
    return new ElectricHeater();
  }

  @Provides Pump providePump(Thermosiphon pump) {
    return pump;
  }
}

通常情况下,建议所有的 @Provides 方法都以 provide 作为前缀,module 模块都是用 Module 作为后缀。

创建映射

@Inject 和 @Provides 注解将类组织成一张或多张有向无环图谱(Graph),通过在Android 的Application中使用 Dagger 根据图谱完成注入。在 Dagger 2 中,这些依赖的映射关系是通过一系列提供了无参方法的接口来实现。为接口添加 @Component 注解并传入 module 列表,Dagger 2 会为其在编译时为其提供具体的注入实现。

@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
  CoffeeMaker maker();
}

实现类使用 Dagger 作为前缀,和接口同名。调用 builder() 方法得到 builder 设置依赖并得到一个实例。

CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
    .dripCoffeeModule(new DripCoffeeModule())
    .build();

每一个包含默认构造函数的 module 可以再 builder() 的构建中省略,Dagger 将自动为其创建并设置实例。如果 Component 所依赖的所有 module 都包含无参构造函数,它的实现类中将提供一个 create() 可以直接创建一个 Component 的实现。

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

得到了 Dagger 生成的 Component 实现类之后,便可以使用注入后的依赖对象了。

public class CoffeeApp {
  public static void main(String[] args) {
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();
    coffeeShop.maker().brew();
  }
}

万事俱备,运行结果如下:

$ java -cp ... coffee.CoffeeApp
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
 [_]P coffee! [_]P
其他注解
@Singleton

注解了 @Singleton 的 @Provides 方法或可以注入的类,在图谱(Graph)中将为所有的依赖使用唯一的实例。

@Provides @Singleton Heater provideHeater() {
  return new ElectricHeater();
}

如果 Component 类提供的注入使用的 scope 使用的是 @Singleton,Component 在定义时也需要添加 @Singleton 注解。

@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
  CoffeeMaker maker();
}
延时注入

有时候我们需要让依赖的属性延时注入,可以使用 Lazy<T> 完成。只有当第一次调用 Lazy<T>.get() 方法时才会注入。如果 T 是一个单例,那么 Lazy<T> 也将返回同一个对象。不然,每一个注入点得到的都是不同的实例。除此之外,对于同一个 Lazy<T> 多次调用 .get() 返回的是同一个实例。

class GridingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;

  public void brew() {
    while (needsGrinding()) {
      // Grinder created once on first call to .get() and cached.
      lazyGrinder.get().grind();
    }
  }
}
多实例注入

有时候我们在一个注入点获取多个实例。可以使用 Provider<T>。如果 T 中包含 @Inject 的构造方法,每次调用 .get() 将返回一个新的实例。

class BigCoffeeMaker {
  @Inject Provider<Filter> filterProvider;

  public void brew(int numberOfPots) {
  ...
    for (int p = 0; p < numberOfPots; p++) {
      maker.addFilter(filterProvider.get()); //new filter every time.
      maker.addCoffee(...);
      maker.percolate();
      ...
    }
  }
}
条件注入

有时候单一的类型无法满足对同一个依赖类型的分类。例如,一个完善的 Coffee Maker 应该能提供水温加热和热盘子加热两种方式。

在这种情况下,需要添加一个条件注解(qualifier annotaion)。这个注解本身有一个 @Qualifier 注解,例如这里创建一个 @Named 注解:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

通过类型和注解同时确定一个依赖类型,可以用在属性或者参数中。

class ExpensiveCoffeeMaker {
  @Inject @Named("water") Heater waterHeater;
  @Inject @Named("hot plate") Heater hotPlateHeater;
  ...
}

也可以用在 @Provides 方法上。

@Provides @Named("hot plate") Heater provideHotPlateHeater() {
  return new ElectricHeater(70);
}

@Provides @Named("water") Heater provideWaterHeater() {
  return new ElectricHeater(93);
}

注意:每个依赖最多只能包含一个条件注解。

编译时校验

Dagger 的校验十分严格,任何依赖绑定配置错误或者不完整都将在项目编译时抛出一个编译错误。例如,下面的 module 配置在 component 中,但缺少了对 Executor 的依赖配置:

@Module
class DripCoffeeModule {
  @Provides Heater provideHeater(Executor executor) {
    return new CpuHeater(executor);
  }
}

执行编译,Java编译器将抛出如下错误:

[ERROR] COMPILATION ERROR :
[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.

解决这个错误的方法是在该 Component 的任意 module 中添加一个 @Provides 方法提供 Executor 的注入。当 @Inject、@Module 和 @Component分别校验通过后,Dagger 在确保整个依赖图谱完整之后,校验才算通过。

生成代码

Dagger在执行编译时,将生成辅助类,类中包含 CoffeeMaker$$Factory.javaCoffeeMaker$$MembersInjector.java 等属性。这些属性在 Dagger 工作中会用到,虽然在注入的过程中通过单步调试可以查看到内容,但开发者不应该在代码中直接使用。

附录

参考资料

Dagger 2 GitHub: https://github.com/google/dagger

Dagger 1 GitHub: https://github.com/square/dagger

Dagger 2 文档: http://google.github.io/dagger

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android入门到精通是一个较为庞大的话题,无法在短时间内详细覆盖所有内容。不过,我可以给你提供一个大致的学习路径和一些重要的主题,帮助你入门并逐步提升技能。 1. Java基础:Android开发主要使用Java语言,所以你需要对Java有一定的了解。学习Java的基本语法、面向对象编程、异常处理等。 2. Android基础知识:学习Android的核心组件,包括Activity、Fragment、Intent等,了解Android应用的生命周期和各个组件之间的交互。 3. 布局和视图:学习如何使用XML文件创建Android应用的界面布局,以及如何使用不同类型的视图(View)来构建用户界面。 4. 用户交互:学习如何处理用户的输入事件,包括点击按钮、滑动手势等。了解如何使用Toast、Dialog、Snackbar等来向用户显示信息。 5. 数据存储:学习如何使用SQLite数据库进行数据的持久化存储,以及如何使用SharedPreferences进行简单的数据存储。 6. 网络通信:学习如何使用HTTP协议发送和接收网络请求,以及如何解析JSON数据。了解常见的网络通信库,如OkHttp、Retrofit等。 7. 多媒体处理:学习如何使用Android提供的API来处理图片、音频和视频。了解如何使用Camera API进行拍照和录像。 8. 性能优化:学习如何优化Android应用的性能,包括减少内存占用、优化布局、优化网络请求等方面。 9. 安全性和权限管理:学习如何保护Android应用的数据安全,并了解如何处理用户权限请求和权限管理。 10. 第三方库和框架:学习如何使用常用的第三方库和框架来加快开发速度,如Glide、ButterKnife、Dagger等。 11. Material Design:学习如何使用Material Design风格来设计美观的用户界面,并了解Material Design的设计原则和组件。 12. 进阶主题:学习更高级的Android开发主题,如响应式编程(RxJava)、MVVM架构、测试和调试技巧等。 以上是一个大致的学习路径,你可以根据自己的兴趣和需求来选择深入学习的内容。同时,不断实践和项目开发也是提升技能的重要方式。希望这些信息对你有所帮助!如果你还有其他问题,可以继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值