依赖注入
在介绍 Dagger2 这个之前,必须先解释一下什么是依赖注入,因为这个库就是用来做依赖注入的。所以这里先简单用一句话来介绍一下依赖注入:
依赖注入是一种设计模式,它允许对象在运行时注入其依赖项。而不是在编译时确定(也就是硬编码)。通过这种方式,可以更好地解耦代码,提高测试性和可维护性。
详细了解依赖注入,这里建立先看完这个博文,详细介绍了这个概念和实现方式:Java 基础知识之 依赖注入(Dependency Injection)
Dagger2
上面的博客已经介绍了,依赖注入有很多不同的框架都可以做这个事,那为什么选择 Dagger2 呢?对于后端开发可能会用 Spring,而对于 Android 开发,只能用 Dagger2 了。这主要是因为这个库本身就是由 Google 推出的,而且它通过注解处理器生成高效的依赖注入代码,避免了运行时反射产生的性能开销。在 Android 源代码项目中,广泛使用了这个库。
这里重点注意 Dagger2 这个库与其他依赖注入库的区别在于 Dagger2 使用的是注解处理器,而不是运行时反射。如果不了解这两个方式的区别可以看一些这个:
制作自己的ButterKnife(使用AutoService和APT注解处理器在编译期生成Java代码)
制作自己的 @OnClick、@OnLongClick(告别setOnClickListener,使用注解、反射和动态代理)
使用注解处理器可以在编译时生成代码来完成功能,这比使用运行时反射要快很多。而性能在 Android 这种嵌入式设备中相当重要,因此对于 Android 开发者来说,如果使用依赖注入,这个库就是必选的。
基本概念
在使用 Dagger2 这个库时,主要会有三个角色:
- 依赖需求方:就是需要依赖对象的那些类。例如一个人想要玩电脑,那么他就必须得有一台电脑,因此这个人就是依赖需求方;
- 依赖供应方:负责提供依赖对象,类似与实际编码中的工厂类,这个人依赖一台电脑玩游戏,那么就必须有个地方能够提供一台电脑,这个地方就是依赖供应方,顾名思义,就是用于创建以来对象的;
- 依赖注入器:负责将以来对象注入到以来需求方,在实际代码中是一个接口,编译时 Dagger2 自动生成的就是这个接口的实现类。接着上面的说,这个人是依赖需求方,他需要一台电脑,依赖供应方能够提供一台电脑,可是这两者没有打通啊,电脑没有给到这个人,他还是玩不了游戏啊,因此这个时候就由依赖注入器将这台电脑注入给这个人,他就能够使用这台电脑玩游戏了。
上面已经说得很形象了,大家应该都能理解,不能理解的,可以想象下面的一个场景。
你需要一台电脑打游戏,那么你依赖于电脑,你就是依赖需求方,依赖对象是一台电脑。这台电脑哪里能提供呢?当然是淘宝、京东、实体店了,这些都能提供一台电脑,那么它们都是依赖供应方。但是这中间必须得有个东西把电脑从供应商的仓库送到你手里,你才能用,这就可以理解为将电脑这个依赖对象注入到你手中。什么是依赖注入器呢?在这里例子中,那就是三通一达这些快递公司了。
就是一个简单的购物的流程,只是把依赖注入的概念套进去了而已。下面我们就以这个场景为例,写个 Demo,告诉大家如何使用 Dagger2 这个库。
引入 Dagger2
截止到目前,Dagger2 这个库的最新版本是 2.51.1。引入这个库的方式也很简单,在 build.gradle 中添加如下依赖:
dependencies {
implementation 'com.google.dagger:dagger:2.51.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.51.1'
}
大家再引入的时候最好查看一下 Dagger 的官网,引入最新的版本:https://dagger.dev/
在引入依赖并 Sync Project 之后,你会发现项目的依赖会多出来两个库:
编写依赖需求方
先编写一个 Person 类,里面有一个 playGame 的方法,这个方法中要使用 Computer,也就是说,Computer 是 Person 的依赖,我们使其成为一个成员变量:
public class Person {
private String name;
private Computer computer;
public void Person(String name) {
this.name = name;
}
public void playGame(String gameName) {
computer.play(gameName);
}
}
以下是 Computer 类,作为依赖对象:
public class Computer {
private String name;
public Computer(String name) {
this.name = name;
}
public void play(String game) {
System.out.println("使用 " + name + " 玩 " + game);
}
}
编写依赖供应方
现在,有了依赖需求方,那就要找到依赖提供商提供一台电脑。哪里能提供电脑呢,那就先编写一个淘宝类吧:
@Module
public class TaoBao {
private Computer assembleComputer() { //组装一台电脑
Computer computer = new Computer("淘宝的电脑");
return computer;
}
@Provides
public Computer getComputer() {
return assembleComputer();
}
}
这里注意两个注解 @Module 和 @Provides,这两个注解是 Dagger 提供的。其中 @Module 用于告知 Dagger 这个类是一个依赖提供商,@Provides 用于告知 Dagger 这个依赖提供商里面哪些方法是用于提供依赖对象的。
在这个例子中, TaoBao 是一个依赖供应方,其中 getComputer 用于提供依赖对象,assembleComputer 则是一个普通方法。
编写依赖注入器
有了需求方和供应方,那么就需要将两者链接起来,依赖对象只有从供应方交给需求方,才有意义,这就是依赖注入器的工作。在这个例子中,依赖注入器就是快递了,快递把电脑从淘宝店家送到买家手中。这里我们就先编写一个中通吧:
@Component(modules = TaoBao.class)
public interface ZTOExpress {
void deliverTo(Person person);
}
注意这个注入器是一个 interface 而非 class,在编译时,Dagger 会生成对应的实现类。
这个接口添加了一个注解:@Component,这个注解是就是告诉注入器,从哪个依赖供应方拿依赖对象。这段代码里,@Component 注解告知了中通,去淘宝拿电脑快递给买家。
但还有一个问题,中通知道将电脑配送给买家,那配送到那个成员变量呢?Person 里有 name 和 computer,从名字上就能看到电脑肯定要配送到 computer 的成员变量上,这个时候需要将 computer 这个成员变量添加 @Inject 注解:
public class Person {
private String name;
@Inject
private Computer computer;
//......
}
依赖注入结果
现在三个角色都有了,那我们现在就把它们拼接在一起,看看效果吧。
Person person = new Person("张三");
ZTOExpress ztoExpress = DaggerZTOExpress.builder().taoBao(new TaoBao()).build();
ztoExpress.deliverTo(person);
person.playGame("赛博朋克2077");
输入:
System.out I 使用 淘宝的电脑 玩 赛博朋克2077
总结
先讲到这里。后续再补全。