参考资料:
大力感谢如下文章的作者:
Dagger2 最清晰的使用教程:https://blog.csdn.net/heng615975867/article/details/80355090
前言
由于之前一直主要从事SDK研发,属于重逻辑轻UI的设计。
正好去年用Kotlin研发了个应用,接上峰要求 使用MVVP框架,配合Dagger2实现,并使用了很多比较新的技术。
硬着头皮,依葫芦画瓢,项目是完成了,但是很多技术没有深入了解。
这如鲠在喉,一直想弄明白。
本着 "程序员不能让自己出现明显的技术短板"(不知道哪位大神说的),现在正好有时间,先从Dagger2下手。
为啥写这篇文档:
- 网上有挺多资料都是用Kotlin来阐述的,所以换个Java语言,以示区分。
- 看别人的例子,属于一看就会,一写就废。很少有循序渐进的演示例子。因此本文提供完整的Demo演示。
- 同时我会说一下创建工程依赖版本的注意点。
- Demo里面增加了很多注释,应该能帮助到有需要的人。
- 里面的有些注释或者内容,对于某些人而言,可能比较啰嗦,抱歉了~
小建议:最佳学习方式就是自己写Demo,哪怕是把参考的照着敲一遍也行。
- 这样会有一个循序渐进的过程。
- 能深入理解某行代码添加所带来的改变。
- 对Dagger2 的注解而言,你就会发现我上面的说的 "一看就会,一写就废"。因为很容易就编译报错。
本文档对应的Demo地址,可以 下载 提取码:pjyk
对Dagger2 的自我理解:
不使用Dagger2 时,我们一般如何创建提供对象:
- 创建一个静态对象,类似: XXXHelper.getInstance.getData()。这是全局静态的对象。
- 还可以通过实现MyApplication类来创建对象,通过类似 (MyApplication) getApplication() 来获取MyApplication对象,这样就可以提供全局单例了。
- 类似FragmentActivity 需要提供对象多个Fragment时,我们还可以通过参数传递的方式,把FragmentActivity的某个数据对象传递给 Fragment,这样多个Fragment可以使用相同的FragmentActivity的数据。这是局部单例。
- 当然上面所说的对象,不要局限于类似POJO类或者JavaBean之类的,也会是某些复杂的逻辑类。比如NetManager,DBManager等。
Dagger2 的主要作用:
就是为了简化上面的提供获取对象的方式
说的再高大上一些,就是使用MVP模式,解耦合,因为上面创建对象的方式,调用的地方和实现的地方基本是完全耦合的,一但数据对象发生改变,调用的地方也要调整。
学习Dagger2 都是为了DaggerAndroid扩展库打基础的,如果要单独使用Dagger2来进行Android研发,请放弃这个念头,会写很多重复的代码,而且任何的修改,一旦引发编译错误,查找和定位问题,会让你有砸电脑的冲动!!!
DaggerAndroid 扩展库,看完Dagger2 的基础知识后,可以继续看这篇文章,我详细介绍了?DaggerAndroid扩展库的使用方式。
进入Dagger2的世界:
1. 创建工程,引入Dagger依赖。
由于我的Demo里面会有很多Module,因此把Dagger2 的依赖提取到了外面统一调用。
- 在项目的build.gradle同级目录,创建config.gradle。目前能查到的最新版本为 2.32.2。如果还有更新的,请自行替换。如果要查找com.google.dagger下dagger最新版本。http://central.maven.org/maven2/com/google/dagger 就能找到最新版本。
不要添加Dagger_Android扩展库,我下一篇会单独讲该扩展库的用法
//使用Dagger2,具体的版本在http://central.maven.org/maven2上查找
//目前采用最新的2.23.2版本,如果有最新版本,请自行替换
version = [
dagger : "2.23.2"
]
//这几个dagger的版本,最好保持一致。
dependencies = [
"dagger" : "com.google.dagger:dagger:${version.dagger}",
"dagger_compiler" : "com.google.dagger:dagger-compiler:${version.dagger}",
]
- 在项目的 build.gradle 中添加如下代码。
apply from: "config.gradle"
- 在Module下的build.gradle 中添加依赖
implementation rootProject.ext.dependencies.dagger
annotationProcessor rootProject.ext.dependencies.dagger_compiler
- 当然,也可以不需要config.gradle文件,直接在Module下引入完整的Dagger依赖。
- 如果AndroidStudio无法同步工程,或者依赖下载超时后者失败啥的。可以使用命令行来下载依赖,在同步一下就可以了。
./gradlew clean build
2. 学习@Inject 和@Component
Demo例子来自 DaggerTest 工程的 dagger1。这是Phone Module,可单独运行。
1. @Inject 说明:
1. 标记在对象类中,标记在某个类的构造函数上,说明如下(下面的接口在不同的类中):
/**
* @Inject 告诉Dagger 可以使用这个构造器来构造Rose对象。
*/
@Inject
public Rose(){
}
/**
* @Inject 是告诉Dagger使用该接口构造Pot类。
* 同时Dagger会自动找到Rose的构造器。
* 如果存在Rose的构造器了,参数就不需要添加@Inject了。
* @param rose
*/
@Inject
public Pot(@Inject Rose rose){
this.rose = rose;
}
2. 标记在需要使用该对象的类中,比如某个Activity,有两个用法:
/**
* @Inject 标注在属性上。就是属性注入。
* Dagger会自动生成该Pot对象。
* 不能为private,编译不通过。
*/
@Inject
public Pot pot;
/**
* 方法注入:
* Dagger2会在构造器执行之后,立即调用这个方法。
* @param pot
*/
@Inject
public void setPot(Pot pot){
this.pot = pot;
Logger.logI("setPot pot:%s",pot);
}
2. @Component类
1. 写法很简单:
@Component
public interface MainActivityComponent {
void inject(MainActivity activity);
}
2. 作用:
- 提供给需要使用注入对象的类来使用的,比如Activity,Application,Fragment等。
- 命名时 采用XXX + Component的方式,Dagger会自动生成 DaggerXXXComponent。
- 其他任意命名也可以,只是上面的方式,便于区分。
- inject接口,是最常见的注入器的入口方法。参数为MainActivity,就是告诉MainActivity,你可以用我提供的方法了。
3. MainActivity的调用
/**
* @Component 定义的类,Dagger会从inject方法,开始查找MainActivity中被@Inject标注的类。
* 1. 先找到Pot类,再找Pot类中被@Inject标注的构造方法。
* 2. 发现Pot构造方法中有Rose类,再找Rose类中被@Inject标注的方法。
* 类都找全后。
* 1. 先构造Rose类,在构造Pot类。
* 2. 然后在MainActivity中的@Inject标注的Pot就有了实例。
*/
DaggerMainActivityComponent.create().inject(this);
String show = pot.show();
Logger.logI("test dagger show:%s",show);
3. 学习 @Module 和@Provide
Demo例子来自 DaggerTest 工程的 dagger2。这是Phone Module,可单独运行。
1. 两者的关系
- @Module注解类,@Provide 注解方法。因此@Provide 必须在@Module注解的类中使用。
- @Module 里面 必须至少包含@Provide 或者@Binds(@Binds 后面说)中的一个。
- 看个例子,该例子中,提供了一个Rose对象。其实和上节例子中的 Rose的构造函数中增加@Inject 效果完全一致。
@Module
public class FlowerModule {
//此处的返回值,只能使用Rose,不能使用Flower。
@Provides
public Rose provideFlower(){
Logger.logD("provideFlower is call");
return new Rose();
}
}
//同时,@Component标注的类需要增加该Module的应用
@Component(modules = {FlowerModule.class})
public interface MainActivityComponent {...}
2. 用途:代替@Inject。为啥要代替?
- @Inject 只能标记有源码的类。如果这个类没有源码,是个三方库,就没法使用@Inject。
- @Module 会让代码更加清晰,因为@Module 可以提供多个对象的构建,减少了每个对象都用@Inject标注。
- @Component 可以依赖多个Module,有需要可以自行验证。
建议:少用@Inject来标注构造函数,统一采用Module来提供。
4. 学习@Qualifier 和 @Named用法
Demo例子来自 DaggerTest 工程的 dagger3。这是Phone Module,可单独运行。
1. @Qualifier 是用来自定义注解用的,有个统一的名字叫限定符,@Named 是@Qualifier的String类型的实现。看@Named的类就能明白,依葫芦画瓢,也能写出别的自定义@Qualifier注解。
2. 作用:
- 如果@Module中提供了两个相同对象的构建方式,那么Dagger就不知道用哪个了。因此需要区分。
- 因此,@Qualifier在提供对象的地方,和使用对象的地方都要添加。
- 例子如下:
//1. 来自@Module类的代码片段,演示如何提供对象。
@Provides
// @Named("black") //用字符串也可以。
@BlackRose //为自定义限定符。
public Rose provideRoseBlack(){
Logger.logV("provideFlower black is call");
return new Rose("Black");
}
@Provides
// @Named("red")
@RedRose
public Rose provideRoseRed(){
Logger.logV("provideFlower Red is call");
return new Rose("Red");
}
//2. 来自MainActivity 的代码片段,演示如何获取对象:
@Inject
// @Named("red")
@RedRose //使用限定符Provide后,在注入的地方,也要表明,两者必须匹配。
public Rose rose;
@Inject
// @Named("black")
@BlackRose
public Rose blackRose;
//3. RedRose的实现,@BlackRose类似:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface RedRose {
}
5. 学习@Component中的denpendence用法和@Subcomponent的用法
Demo例子来自 DaggerTest 工程的 dagger4,演示denpendence。这是Phone Module,可单独运行。
Demo例子来自 DaggerTest 工程的 dagger5,演示@Subcomponent。这是Phone Module,可单独运行。
1. 用途和作用:
- 通过之前的例子可以看出来,MainActivity获取某个注入对象的时为:MainActivity -> MainActivityComponent -> MainActivityModule -> 某个对象。就要求所有注入的对象都要在@Module类中提供。
- 如果把一部分对象放在另外Component + Module中提供,会使得逻辑更加清晰。
- 因此denpendence 和 @Subcomponent 就提供了这样的方式,因此这两个方法都是用来扩展Component对象的,便于MainActivity 通过注入一个Component就能实现所有对象的注入。
2. 先来看denpendence的用法(工程中Module dagger4):
- 我们搭建一个这样的演示环境。
- 用 XXXComponent + XXXModule + XXX对象 的流程 分别提供 Rose 和Lily两个对象的获取。
- 让LilyComponent dependence RoseComponent。
- 让 MainActivityComponent dependence LilyComponent。
- 具体代码演示:
//1. RoseModule 和 RoseComponent类和上面的例子相同。
//2. LilyComponent 修改如下。
@Component(modules = {LilyModule.class},dependencies = {RoseComponent.class})
public interface LilyComponent {
Lily getLily();
}
//3. MainActivityComponent 修改如下:
@Component(dependencies = LilyComponent.class)
public interface MainActivityComponent {
void inject(MainActivity activity);
}
//4. MainActivity 注入代码修改如下:TODO注意这里写法有了变化
//拆开写法如下:
// RoseComponent roseComponent = DaggerRoseComponent.create();
// LilyComponent lilyComponent = DaggerLilyComponent.builder().roseComponent(roseComponent).build();
//
// DaggerMainActivityComponent.builder().lilyComponent(lilyComponent).build().inject(this);
//采用级联写法:推荐:
DaggerMainActivityComponent.builder()
.lilyComponent(
DaggerLilyComponent.builder().roseComponent(
DaggerRoseComponent.create()).build()
)
.build().inject(this);
- dependence使用的应用,在注入时,需要显示的提供。看上面的MainActivity就能发现。
3. 再看@Subcomponent的用法(工程中的Module dagger5)
- 环境搭建和dependence中类似,但是写法顺序发生了变化。
- 看代码:
//1. 先创建MainComponent类,注意,这是个@Subcomponent类。孤零零的一个类,啥也不依赖,最底层的一个小弟。
@Subcomponent
public interface MainActivityComponent {
void inject(MainActivity mainActivity);
}
//2. LilyModule不做修改,LilyComponent做如下改造,依然是@Subcomponent类,可以提供MainActivityComponent。算是二弟。
@Subcomponent(modules = {LilyModule.class})
public interface LilyComponent {
MainActivityComponent plus();
}
//3. RoseModule不做修改,RoseComponent做如下改造。这是@Component,提供了LilyComponent。这是大哥
//看靠上面dependence的对比,RoseComponent从对底层,现在变成了大哥。
@Component(modules = {RoseModule.class})
public interface RoseComponent {
LilyComponent plus(LilyModule lilyModule);
}
//4. MainActivity注入代码发生变化,因为@Component标注的是RoseComponent,因此注入从RoseComponent发起。
//级联写法如下:推荐写法。
DaggerRoseComponent.create()
//如下plus返回LilyComponent
.plus(new LilyModule())
//如下plus 返回MainActivityComponent
.plus()
.inject(this);
4. dependence和@Subcomponent用法区别:
- 两者功能相同,都是扩展Component类。但是从上面的两个例子,可以很明显的发现,dependence和@Subcomponent 触发逻辑是相反的。
- 另外,从MainActivity来看,用了dependence,就得显示的提供DaggerXXXComponent的调用。而@Subcomponent,就不需要提供了。
5. 何时用dependence或者@Subcomponent?
网上有一些人会说 何时用 dependence,何时用@Subcomponent?
我的建议是:先不用管,知道两者的大概用途就行了。
因为就像文章上面说的,Dagger2 是为了DaggerAndroid 打基础的。
在DaggerAndroid扩展库中,再看两者的具体使用位置。
6. 学习@Scope和@Singleton
Demo例子来自 DaggerTest 工程的 dagger6,演示局部单例。这是Phone Module,可单独运行。
Demo例子来自 DaggerTest 工程的 dagger7,演示全局单例。这是Phone Module,可单独运行。
1. @Scope 称为作用域,也有的叫 "命名符",管理生命周期的。@Scope是用来定义注解的,Singleton是@Scope的一个实现类。有点像@Qualifier 和 @Named的关系
2. 作用和用途:
- 其实这就是说明一个对象是全局单例,还是局部单例。文章最上面已经说过了。类似通过MyApplication 提供的对象是全局对象。类似FragmentActivity 通过传递参数提供给Fragment的,可以叫做局部单例。
- @Scope和@Singleton 就是标记了这个@Module中的@Provide方法,通过@Component提供的时候,是每次都创建个新的对象,还是把创建过的对象直接提供给MainActivity。---有点拗口。
- 还是用代码来说更加直观:可以看上面的任意例子中的MainActivity类。
@Inject
Lily lily;
@Inject
Lily otherLily;
//上面的 lily 和otherLily 是两个不同的对象。因为没有命名符,每次@Provide都会是新的对象。
3. 使用方法,依然通过代码演示:
//1. LilyModule中增加@Singleton
@Module
public class LilyModule {
@Provides
@Singleton
public Lily provideLily(@Named("red") Rose rose){
return new Lily(rose);
}
}
//2. 同时,LilyComponent也要增加。说明了Component + Module的这对组合,只会提供一份Lily对象。
@Singleton
@Component(modules = {LilyModule.class},dependencies = {RoseComponent.class})
public interface LilyComponent {
Lily getLily();
}
//3. MainActivityComponent,依赖LilyComponent。
@ActivityScope //自定义的@Scope
@Component(dependencies = {LilyComponent.class})
public interface MainActivityComponent {
void inject(MainActivity activity);
}
//4. @ActivityScope 代码
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
//5. MainActivity中,lily 和 otherLily的这两个对象的地址相同。
@Inject
Lily lily;
@Inject
Lily otherLily;
4. 额外说明:
- @ActivityScope 是定义@Scope。由于LilyComponent已经使用了@Singleton,因此MainActivityComponent就不能再使用,需要定义一个。因为产生了依赖关系。
- 一个 配套的Component + Module组合,通过@Scope可以提供唯一的某个对象。
- 如果此时有个SecondActivity,这个类创建的Lily 对象 还是和MainActivity中不相同。以为SecondActivity重新使用了新的Component + Module组合。所以这是局部单例。
5. 如果MainActivity 和Second 也要使用同一份Lily对象,怎么办?
最简单的办法,引入 AppComponent ,来提供全局单例。详见 Demo中的Module dagger7代码。
7. 学习@Binds 用法。
Demo例子来自 DaggerTest 工程的 dagger8,演示@Binds。这是Phone Module,可单独运行。
1. @Binds和@Provide类似,都是在@Module中使用。@Provide提供具体的实现接口。@Binds 提供的接口,没有实现。
2. 具体的写法:
//1. 提供Peach对象的构造
public class Peach implements IFruit {
@Inject
public Peach(){
}
@Override
public String name() {
return "Peach";
}
}
//2. 提供一个Module
@Module
public abstract class FruitModule2 {
@Binds
abstract IFruit provideFruit(Peach peach);
}
//3. Component引入该Module。
@Component(modules = {FruitModule2.class})
public interface SecondActivityComponent {
void inject(SecondActivity activity);
}
//4. Activity中使用。这样,可以通过IFruit直接获取到Peach对象。
@Inject
IFruit fruit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerSecondActivityComponent.create().inject(this);
Log.i("Loner","secondActivity find fruit:"+fruit+",name:"+fruit.name());
}
3. 说明:
- @Binds提供如果提供对象。参数需要外部提供,或者Dagger可以查找到具体的构造器。
- 本例子是演示最基本的用法,具体的用法讲到DaggerAndroid时,会再次提到。
8. Component.Builder 和 @BindsInstance 的用法。
Demo例子来自 DaggerTest 工程的 dagger9。这是Phone Module,可单独运行。
按照官网的说法,Component.Builder是用来简化在某个Activity中Dagger注入代码。
上面的例子中XXXModule中提供的方法都是带参数的(有个例子中带参数了,但是参数是可以直接构造的)。
如果XXXModule中带了参数,在XXXActivity注入时,就需要显示的申明。
Component.Builder就可以简化这个过程。
1. 不使用Component.Builder时,情况大致如下:
//1. 提供MainActivityModule类
@Module
public class MainActivityModule {
Person person;
public MainActivityModule(String name){
this.person = new Person(name);
}
@Provides
Person providePerson(){
return person;
}
/**
* Dagger仅提供了对象的注入方式,String也是对象,所以可以提供。
*/
@Provides
String providePersonName(){
return person.name();
}
}
//2. MainActivityComponent
@Component(modules = MainActivityModule.class)
public interface MainActivityComponent {
void inject(MainActivity mainActivity);
}
//3. MainActivity中如需要这样写。这样就可以通过@Inject使用providePersonName。这是个字符串。
//由于MainActivityModule需要带参构造,因此Dagger不能自动生成,需要显示的生成该对象。
DaggerMainActivityComponent.builder()
.mainActivityModule(new MainActivityModule("Lily"))
.build()
.inject(this);
2. 使用Component.Builder 时,这样写:
//1. SecondActivityModule如下:
@Module
public class SecondActivityModule {
//上面提供Person的代码全部移除,生成Name的方式,替换为参数传入。
// 该参数在Component.buidler中保持一致。
@Provides
String providePersonName(Person person){
return person.name();
}
}
//2. SecondActivityComponent如下:
@Component(modules = SecondActivityModule.class)
public interface SecondActivityComponent {
void inject(SecondActivity mainActivity);
//如下是关键代码
@Component.Builder //固定不变
interface Builder{ //固定不变
SecondActivityComponent build(); //固定不变。
//该接口的参数和@Module中@Provide的参数保持一致,目的就是为其提供实例。
@BindsInstance Builder person(Person person);
}
}
//3. SecondActivity 写法。
DaggerSecondActivityComponent.builder().person(new Person("Lucy")).build().inject(this);
3. 说明:
- 上面两个对比,就可以发现Component.Builder方式简化一些代码。
- 其他文档上面写法不太一样,有些写法已经被淘汰了。
- Component.Builder 用法基本为固定套路,记住就行了。
9. 其他
- Lazy:如果某个对象的构造器会存在复杂逻辑,可以使用Lazy延迟获取。
//1. 写法。
@Inject
Lazy<Lily> lily
//2. 用法:
lily.get() //此时才会真实的创建 lily对象。
- @IntoMap,ClassKey等等,在详解DaggerAndroid扩展库时,会有说明。
11. 本文档对应的Demo地址,请 下载 提取码:pjyk
10. Dagger-Android扩展库的简单说明:
- 如果觉得学完上面的Dagger2 就用在Android研发上,那就打错特错了,Dagger2只是基础,只是学习DaggerAndroid扩展库的基础。
- Dagger2 的学习网上的资料还是挺多,但是到了DaggerAndroid扩展库时,就会发现少了很多,或者写法已经用了淘汰的方法。
- 接下来去看这篇文章吧:DaggerAndroid