Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9300899
commit c204e79
Showing
17 changed files
with
3,007 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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! |
Oops, something went wrong.