Skip to content

Commit

Permalink
add dagger notes
Browse files Browse the repository at this point in the history
  • Loading branch information
CharonChui committed Apr 25, 2018
1 parent 9300899 commit c204e79
Show file tree
Hide file tree
Showing 17 changed files with 3,007 additions and 114 deletions.
74 changes: 74 additions & 0 deletions Dagger2/1.Dagger2简介(一).md
@@ -0,0 +1,74 @@
Dagger2简介(一)
===

[Dagger](https://github.com/google/dagger)

> A fast dependency injector for Android and Java.
`Dagger`是一个依赖注入(`Dependency Injection`,简称`DI`)框架,`butterknife`也是一个依赖注入框架。但是`Dagger2``Butterknife`更强大的多,它的主要作用,就是对象的管理,其目的是为了降低程序耦合。

有关注解和`ButterKnife`的解析请看之前的文章:[注解使用](https://github.com/CharonChui/AndroidNote/blob/master/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md)[ButterKnife源码解析](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md)

那么神马是依赖注入,其实我们一直在用:

- 通过接口注入

```java
interface Ib {
void setB(B b)
}
public class A implements Ib {
B b;
@override
void setB(B b) {
this.b = b;
}
}
```

- 通过`set`方法注入

```java
public class ClassA {
ClassB classB;

public void setClassB(ClassB b) {
classB = b;
}
}
```

- 通过构造方法注入

```java
public class ClassA {
ClassB classB;

public void ClassA(ClassB b) {
classB = b;
}
}
```

- 通过注解的方式注入

```java
public class ClassA {
//此时并不会完成注入,
//还需要依赖注入框架的支持,如Dagger2
@inject
ClassB classB;
public ClassA() {}
}
```

说了这么久,也不知道到底这货有什么用,这里举个例子,比如有个类`A`,他的构造函数需要传入`B,C`;然后代码里有10个地方实例化了`A`,那如果功能更改,`A`的构造函数改成了只有`B`,这个时候,你是不是要去这10个地方一个一个的改?如果是100个地方,你是不是要吐血?!如果采用`dagger2`,这样的需求只需要改1-2个地方。这是真的吗?听起来好像挺牛逼的样子。


也有人怀疑`Dagger2`利用注解是不是采用了反射,会影响性能,这个问题其实在之前的文章[ButterKnife源码解析](https://github.com/CharonChui/AndroidNote/blob/master/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md)就已经介绍过了。`Dagger2`、`ButterKnife`这类依赖注入框架都已经采用了`apt`代码自动生成技术,其注解是停留在编译时,可以不用考虑性能的问题。


---

- 邮箱 :charon.chui@gmail.com
- Good Luck!
270 changes: 270 additions & 0 deletions Dagger2/2.Dagger2入门demo(二).md
@@ -0,0 +1,270 @@
Dagger2入门demo(二)
===

`Dagger`中使用了很多注解:

- `@Module`:`Modules`类里面的方法专门提供依赖,所以我们定义一个类,用`@Module`注解,这样`Dagger`在构造类的实例的时候,就知道从哪里去找到需要的 依赖。`modules`的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的`app`中可以有多个组成在一起的`modules`)
- `@Provide`:在`modules`中,我们定义的方法是用这个注解,以此来告诉`Dagger`我们想要构造对象并提供这些依赖。
- `@Singleton`:当前提供的对象将是单例模式 ,一般配合`@Provides`一起出现
- `@Component`:用于接口,这个接口被`Dagger2`用于生成用于模块注入的代码
- `@Inject`:在需要依赖的地方使用这个注解。(你用它告诉`Dagger`这个构造方法,成员变量或者函数方法需要依赖注入。这样`Dagger`就会构造一个这个类的实例并满足他们的依赖。)
- `@Scope`:`Scopes`可是非常的有用,`Dagger2`可以通过自定义注解限定注解作用域。


这个东西不太好入门,这里就直接用例子上代码了。有关如何添加以来在`github`上面写的很清楚了,`android``build.gradle`中进行配置就好了。

这里就用[dagger官方的coffee的例子说明](https://github.com/google/dagger/tree/master/examples/simple/src/main/java/coffee)

官方`demo`描述的是:一个泵压式咖啡机`(CoffeeMaker)`由两个主要零件组成,泵浦`(Pump)`和加热器`(Heater)`,咖啡机有一个功能是煮泡咖啡`(brew)`
当进行煮泡咖啡时,会按如下几个步骤进行:

- 打开加热器进行加热
- 泵浦加压
- 萃取出咖啡
- 然后关闭加热器

一杯咖啡就算制作完毕了。

好了,那我们先开始写代码了(我把官方的示例代码进行了简化,方便更好的理解):

定义加热器接口:
```java
interface Heater {
void on();
void off();
}
```

具体实现类:
```java
class ElectricHeater implements Heater {

@Override
public void on() {
System.out.println("~ ~ ~ heating ~ ~ ~");
}

@Override
public void off() {

}
}
```

定义泵浦接口:
```java
interface Pump {
void pump();
}
```

具体实现类:
```java
class Thermosiphon implements Pump {

Thermosiphon() {

}

@Override
public void pump() {
System.out.println("=> => pumping => =>");
}
}
```

咖啡机功能:
```java
class CoffeeMaker {
private final Heater heater;
private final Pump pump;

CoffeeMaker() {
heater = new ElectricHeater();
pump = new Thermosiphon();
}

public void brew() {
heater.on();
pump.pump();
System.out.println(" [_]P coffee! [_]P ");
heater.off();
}
}
```

好了完成了,如果执行的话运行结果是:
```
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
[_]P coffee! [_]P
```

完美,上面是我们普通的开发方式,那如果我们要用`dagger`来实现该怎么弄呢?

首先这几个类是没有改动的,因为这几个类中没有牵扯到其他的类:
```java
interface Pump {
void pump();
}

interface Heater {
void on();
void off();
}

class ElectricHeater implements Heater {

@Override
public void on() {
System.out.println("~ ~ ~ heating ~ ~ ~");
}

@Override
public void off() {

}
}
```

下面就开始使用`dagger`需要改动的部分,首先创建`module`类。`@Module`类的命名惯例是以`Module`作为类名称的结尾。而`@Module`类中的`@Provides`方法名称的命名惯例是以`provide`作为前缀。
而这个`Module`是干什么的呢?`Module`管理所有的以来,这里你做咖啡需要以来泵浦`(Pump)`和加热器`(Heater)`,所以可以创建`Module`把他们`Pump``Heater`管理起来,方便
之后获取`Pump``Heater`对象,所以它里面会有`provideXXX()`的函数。


- 第一步:增加`Module`类,使用`@Module`声明类(可以将Module理解成生产对象的工厂,里面提供了`provideXXX`这种生产对象的方法)

```java
@Module // Module注明该类是Module类
public class CoffeeModule {
@Provides // Provides注明该方法是用来提供依赖对象的方法
Heater provideHeater() {
return new ElectricHeater();
}
@Provides
Pump providePump() {
return new Thermosiphon();
}
}
```

- 第二步:增加接口`Component`,使用`@Component`声明(`Componet`是将`Module`中生产的对象注入到对应的需要使用该对象的类中)
```java
// 这里是声明要从CoffeeModule中去找对应的依赖,从`CoffeeModule`中去通过`provideXXX`方法来获取对应的对象
@Component(modules = CoffeeModule.class)
// 该接口会在编译时自动生成对应的实现类,这里是DaggerCoffeeComponent
public interface CoffeeComponent {
// 提供一个供目标类使用的注入方法,该方法表示要将Module中的管理类注入到哪个类中,这里当然是CoffeeMaker,因为我们要用他俩去生产咖啡
void inject(CoffeeMaker maker);
}
```

注意:`@Component(modules = {AModule.class,BModule.class})`可以设置多个`Module`,而且也可以`@Component(modules = {MainModule.class}, dependencies = AppConponent.class)`
指定依赖的`Module`和父`Component`

- 第三步:`Inject`注入
```java
import javax.inject.Inject;

class CoffeeMaker {
@Inject // 标记该对象是被注入的
Heater heater;
@Inject
Pump pump;


CoffeeMaker() {
// DaggerCoffeeComponent这个类会在编译时产生,所以可以build一下
CoffeeComponent component = DaggerCoffeeComponent.create();
// 注入
component.inject(this);

// 或者使用下面的代码也是可以的
// DaggerCoffeeComponent.builder()
// .coffeeModule(new CoffeeModule())
// .build()
// .inject(this);
}

public void brew() {
heater.on();
pump.pump();
System.out.println(" [_]P coffee! [_]P ");
heater.off();
}
}
```

到这里就完成了,执行的话,结果是一样的:
```
04-20 15:16:55.083 16375-16375/com.charon.stplayer I/System.out: ~ ~ ~ heating ~ ~ ~
04-20 15:16:55.084 16375-16375/com.charon.stplayer I/System.out: => => pumping => =>
[_]P coffee! [_]P
```

到这里你肯定就迷糊了,为什么? 本来很简单的东西,你还多搞出来几个类,弄的这么麻烦。但是麻烦吗? 你要是有十个地方呢? 一百个地方呢? 以后你修改怎么改? 一百个地方都改吗?

好了,最后来个总结,高中老师讲的,要来个综上所述:

- 通过`@Module`声明一个`XXXModule`类,用于管理所有依赖,并使用`@Provides`为每个依赖提供`provideXXX()`方法
- 通过`@Component`声明一个`XXXComponent`接口,并且声明要从哪些`Module`中寻找依赖
- 声明要去哪些`Module`中寻找依赖
`@Component(modules = CoffeeModule.class)`
- 提供一个供目标类使用的注入方法
`void inject(CoffeeMaker maker);`
- 为所有的依赖添加一个方法
这个说起来就有点多了,而且我们上面的代码中并没有进行这一步操作,这是因为方法也可以不写,但是如果要写,就按照这个格式来
但是当`Component`要被别的`Component`依赖时,这里就必须写这个方法,不写代表不向别的`Component`暴露此依赖,而且如果要写的话
这些方法返回值必须是从上面指定的依赖库`CoffeeModule.class`中取得的对象,注意:而方法名不一致也行,但是方便阅读,建议一致,因为它主要是根据返回值类型来找依赖的。
这里如果要写的话就是
```java
Heater provideHeater();
Pump providePump();
```
- 目标类使用依赖注入,首先是使用`@Inject`声明属性变量,表示注入这个依赖,然后是调用`inject()`方法注入依赖。


下面来个图,能够更方便的去理解:

![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/dagger2_liu.png?raw=true)
上图中:
- `Container`就是可以被注入的容器,具体对应上文例子中的`CoffeeMaker``Container`拥有需要被初始化的元素。需要被初始化的元素必须标上`@Inject`,只有被标上`@Inject`的元素才会被自动初始化。`@Inject``Dagger2`中一般标记构造方法与成员变量。
- `Module`可以说就是依赖的原材料的制造工厂,所有需要被注入的元素的实现都是从`Module`生产的。
- 有了可以被注入的容器`Container`,也有了提供依赖对象的`Module`。我们必须将依赖对象注入到容器中,这个过程由`Component`来执行。`Component``Module`中产生的依赖对象自动注入到`Container`中。

好像大体明白了点....


上面有`DaggerBaseComponent.create();``DaggerBaseComponent.builder().build()`两种方式有什么区别呢? 我们看一下源码:

```java
public static Builder builder() {
return new Builder();
}

public static BaseComponent create() {
return new Builder().build();
}
```

`Dagger2`会自动生成该接口的实现类(以`Dagger`开头,如果`@Component`注解类不是顶级类,自动生成的实现类的名字会闭包命名并用下划线分割如`DaggerFoo_Bar_BazComponent)`,可以通过该实现类的`builder()`方法获得`builder`对象,`builder`对象可以设置好依赖,最后通过`build()`方法构建实例:
```java
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
```

如果`Modules`都是缺省构造器,`@Provides`方法都是静态的,用户不需要构建依赖实例,那么自动生成的实现类就会有`create()`方法获取新实例,就不需要处理`builder`了。
```java
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker().brew();
}
}
```

---

- 邮箱 :charon.chui@gmail.com
- Good Luck!

0 comments on commit c204e79

Please sign in to comment.