Android依赖注入Dagger的使用和源码解析(上篇)

Posted robert_chao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android依赖注入Dagger的使用和源码解析(上篇)相关的知识,希望对你有一定的参考价值。

一、基本概念

依赖注入(DI)和控制反转(IOC):

依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

使用依赖注入可以带来以下好处:

  • 依赖的注入和配置独立于组件之外。

  • 因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。

  • 依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。

二、Java和android依赖注入

   为了最大程度的提高代码的复用性、测试性和维护性,java的依赖注入为注入类中的使用定义了一整套注解(和接口)标准 Java 依赖注入标准(JSR-330,Dependency Injection for Java)1.0 规范已于2009年 10 月份发布 。

Dagger1是Android上最流行的依赖注入框架。它是由Square公司受到Guice启发创建的。Dagger2是Dagger1的分支,由谷歌公司接手开发,目前的版本是2.2。Dagger2是受到AutoValue项目的启发

Android开发从一开始的MVC框架,到MVP,到MVVM,不断变化。现在MVVM的data-binding还在实验阶段,传统的MVC框架Activity内部可能包含大量的代码,难以维护,现在主流的架构还是使用MVP(Model + View + Presenter)的方式。但是 MVP 框架也有可能在Presenter中集中大量的代码,引入DI框架Dagger2 可以实现 Presenter 与 Activity 之间的解耦,Presenter和其它业务逻辑之间的解耦,提高模块化和可维护性。

Dagger github地址为

https://github.com/square/dagger

https://github.com/google/dagger

Dagger2对Dagger的改进如下
再也没有使用反射:图的验证、配置和预先设置都在编译的时候执行。
容易调试和可跟踪:完全具体地调用提供和创建的堆栈
更好的性能:谷歌声称他们提高了13%的处理性能
代码混淆:使用派遣方法,就如同自己写的代码一样
这里我们只分析Dagger2的使用和源码,以下的所有Dagger指的都是Dagger2

三、Dagger的使用

1、引入相关库

Dagger的gradle并不是简单的在dependencies中加上相关compile就可以了,这里说一下配置方法

dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
 apply plugin: 'com.neenbedankt.android-apt'
 dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile group: 'com.google.dagger', name: 'dagger', version: '2.4'
    compile group: 'com.google.dagger', name: 'dagger-compiler', version: '2.4'
}

生成的中间文件在build/generated/source/apt/debug中

2、注解

@Inject: 通常在需要依赖的地方使用这个注解。告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。
@Module: Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的 依赖。modules的一个重要特征是它们设计为分区并组合在一起。
@Provides: 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。
@Component: Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。 Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的 @Modules组成该组件,如 果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的modules知道依赖的范围。
@Scope: ,Dagger2可以通过自定义注解限定注解作用域。

3、注入依赖

Dagger提供的注入依赖方法有:
构造方法注入:在类的构造方法前面注释@Inject
成员变量注入:在类的成员变量(非私有)前面注释@Inject
函数方法注入:在函数前面注释@Inject
以上顺序是Dagger建议使用的,因为在运行的过程中,总会有一些奇怪的问题甚至是空指针,这也意味着依赖在对象创建的时候可能还没有初始化完成。

class Thermosiphon implements Pump {
  private final Heater heater;

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

  ...
}

class CoffeeMaker {
  @Inject Heater heater;

  ...
}

class CoffeeMaker {
  @Inject
  public void setPump(Pump pump){
    this.pump=pump;
  }
  ...
}

如果 @Inject注解了成员变量,但是没有注解构造方法,有需要的话会注入,但是不会创建新的对象。如果对无参构造函数进行注解,可以创建新对象。

没有通过 @Inject注解的类是不能用Dagger创建的。

4、满足依赖关系

接口不能构造。
第三方类不能注明。
配置对象必须配置

我们需要通过被 @Provides 注解所标注的方法来实现依赖。每个方法的返回的类型就是我们需要实现的依赖。
一旦需要注入一个 Heater,provideHeater() 就会被调用:

@Provides Heater provideHeater() {
  return new ElectricHeater();
}
被 @Provides 标注的方法自身也可以有依赖。
需要 Pump 作为依赖来返回一个 Thermosiphon 的实例:
@Provides Pump providePump(Thermosiphon pump) {
  return pump;
}
所有的 @Provides 标注的方法都必须属于一个 module。用 @Module 标注的类就是一个 module。
@Module
class DripCoffeeModule {
  @Provides Heater provideHeater() {
    return new ElectricHeater();
  }

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

依照惯例,@Provides 标注的方法以 provide 作为前缀来命名,而 module 类都用 Module 作为后缀来命名。

5、构建图

用 @Inject 和 @Provides 来标注的类最终会组成一个由对象构成的图,
在Dagger2中,该集合依赖关系是一个接口定义的方法,没有任何参数,只返回所需的类型。通过将@Componet注解应用到这样的接口上,并将模块类型传递给模块参数。
Dagger是一个基于有向无环图结构的依赖注入库,DAG——有向无环图(Directed Acyclic Graph),因此Dagger的使用过程中不能出现循环依赖。

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


具体实现是在接口名的基础上加上Dagger前缀,调用builder方法设置依赖构建实例。
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
    .dripCoffeeModule(new DripCoffeeModule())
    .build();
如果 @Component不是在最外层注解,除了前缀,还需要用_连接外部结构的名字
class Foo {
  static class Bar {
    @Component
    interface BazComponent {}
  }
}

生成的组件名称为DaggerFoo_Bar_BazComponent

有一个可访问的默认构造函数的module都可以作为构建者,如果没有设置,会自动构造一个实例。而对于@Provides方法都是静态的module,实现不需要实例。如果所有的依赖性会未经用户创建一个实例的依赖构造,则生成的实现类将具有create()方法可用于获取一个新的实例,而不必处理builder方式。

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

6、单例和使用范围绑定

使用@Provides 注解的方法,加上 @Singleton,就能保证生成单例。整个生命周期中只有一个实例
使用@Singleton注解的类可以多线程共享。
不同的作用域的生命周期是不同的,单例Singleton注解(Application scope)是最长的scope

组件可能有多个作用域注释。他们有相同的使用范围,不同的注解相当于别名,所以组件可以包括任何其声明作用域范围的绑定。要声明一个组件与给定作用域关联,只需将scope注释应用到组件接口。

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

@Singleton
class CoffeeMaker {
  ...
}

@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
  CoffeeMaker maker();
}
自定义scope

@Scope
@Retention(RUNTIME)
public @interface PerActivity {
}
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface HomeComponent extends AbstractActivityComponent {
  void inject(HomeActivity homeActivity);
  void inject(HomeFragment homeFragment);
}

7、可复用的使用范围

有时候需要限制@Inject注解的类实例化得对象个数,不需要保证任何组件或者子组件的生命周期内完全相同的实例使用,在android中内存比较珍贵,这种方式比较有用。使用@Reusable注解的类实例化对象之后会被缓存。父组件缓存之后,子类会重用。
谁也不能保证该组件将调用绑定只有一次,使用@Reusable绑定返回值可变的对象,或者指向相同实例很重要的对象,是很危险的。如果不关心创建多少对象,不可变对象不限定范围,使用@Reusable注解是安全的。

@Reusable // It doesn't matter how many scoopers we use, but don't waste them.
class CoffeeScooper {
  @Inject CoffeeScooper() {}
}

@Module
class CashRegisterModule {
  @Provides
  @Reusable // DON'T DO THIS! You do care which register you put your cash in.
            // Use a specific scope instead.
  static CashRegister badIdeaCashRegister() {
    return new CashRegister();
  }
}

  
@Reusable // DON'T DO THIS! You really do want a new filter each time, so this
          // should be unscoped.
class CoffeeFilter {
  @Inject CoffeeFilter() {}
}

8、懒加载注入

有些时候需要懒加载,要用到Lazy<T> 
只有一个get方法,如果不调用,dagger不会创建对象
调用之后,创建对象并保存
再次调用,返回相同对象

class GridingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;

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


9、使用Provider注入

有些情况下, 你需要多个对象实例, 而不是仅仅注入一个对象实例。这时你可以利用Provider实现, 每次调用Provider的get()函数将返回新的<T>的对象实例。


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();
      ...
    }
  }
}


10、Qualifiers的使用

有时单独类型是不足以识别区分依赖性。这个时候可以使用Qualifier注解,这里使用@Named注解,

class ExpensiveCoffeeMaker {
  @Inject @Named("water") Heater waterHeater;
  @Inject @Named("hot plate") Heater hotPlateHeater;
  ...
}
@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
  return new ElectricHeater(70);
}

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


对这个框架使用的比较少,主要参考github文档

http://google.github.io/dagger/users-guide.html

使用MVP比较少,做的项目基本都是MVC的,这个框架不是非常适用。不能充分发挥Dagger优势。

本文不是很深入。后期深入看一下源码,再做详细分析。



欢迎扫描二维码,关注公众账号。



以上是关于Android依赖注入Dagger的使用和源码解析(上篇)的主要内容,如果未能解决你的问题,请参考以下文章

Android单排上王者系列之Dagger2注入原理解析

Android Dagger-Hilt 依赖注入

Android 依赖注入: Dagger 2 实例解说

Android Dragger2快速入门浅析

Java依赖注入库框架 Dagger的源码分析

Dagger2源码浅析