前言
网上都说Dagger2是比较难上手的,我在看了大量资料和使用时也遇到了很多不懂或者模糊的知识点,而且大部分博客资料都比较古老。突然有那么一瞬间,突然明白了所以然,故总结了4篇文章。话说在java中使用还是很繁琐的,不要怕带你真正上手,并运用到我们的Android项目中去。
本次Dagger2讲解总共分4篇:
1、Dagger2基础知识及在Java中使用(1)
2、Dagger2基础知识及在Java中使用(2)
3、Dagger2进阶知识及在Android中使用
4、Dagger2华丽使用在MVP框架中
首先简单申明下,Dagger2的好处不是本文的重点。大家可以自行百度。Dagger2是依赖注解框架,像我们之间的butterknife也是这样的框架,想这样的框架依赖一般都有2行。第二行是以annotationProcessor开头的。这其实是apt的工具,而且这样的依赖注解框架不会影响性能(不是反射机制),在编译的时候,apt把要用的代码生成。所以大可放心使用
再举我理解的例子(大家不要全信,哈哈稍微不恰当):@Component相当于一个注射器(记住是接口);@Module相当于注射液,就是数据源(记住这里是类或者抽象类),此时要把注射液放入指定哪个注射器如:@Component( modules = … );@Inject 相当于标注被注射体。
之后的讲解都是走完简单的流程,实现功能,然后讲大概理解。贴在博客上的代码,可能会省略部分代码,便于理解。github上的Demo及注释,非常详细,接近完美0-0#
如果是完全没了解过,关于Dagger一些标注的具体介绍和理解,推荐这里有3篇介绍标注的意思和怎么工作的
首先添加依赖
implementation 'com.google.dagger:dagger:2.24'
annotationProcessor "com.google.dagger:dagger-compiler:2.24"
1
2
1、@Inject & @Component 的简单使用(不带@Module)
首先随便定义个类:Person,无参构造方法用@Inject标注:
public class Person {
@Inject
public Person() {
}
}
1
2
3
4
5
6
然后定义我们的 Component(这里稍微提一下,如果一个页面定义多个Component,你build的时候报错,是不是)
这里的命名规则最好是以我们页面类名+Component,这样比较清晰。用@Component标注,里面有个void方法,方法名随意定,建议用inject最好,当然也是清晰,参数是我们需要依赖注解的页面:
@Component
public interface AstudyActivityComponent {
void injectTo(AstudyActivity astudyActivity);
}
1
2
3
4
做好上面步骤后,点开studio里build标签下的Make Project。让apt帮我们生成代码,一般生成代码为Dagger+你定义Component的类名。
之后这个步骤不再重复,就是你写完准备代码的时候一定要让apt生成代码,Make Project下
然后在我们的Activity里:
public class AstudyActivity extends BaseActivity {
@Inject
Person person;
@Override
//这里是我封装的onCreate,省略部分代码,只为理解,之后都请忽略!
protected void processLogic() {
//第一种
DaggerAstudyActivityComponent.create().injectTo(this);
//第二种
//DaggerAstudyActivityComponent.builder().build().injectTo(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
在我们的Activity里build下我们的Component,然后注册在我们的Activity里,就可以使用通过我们的@Inject使用我们的person了。
这里初始化有2种:
1、DaggerAstudyActivityComponent.create().injectTo(this);
2、DaggerAstudyActivityComponent.builder().build().injectTo(this); 这个使用module传值一定要使用
那么一个简单的使用就实现了,这里忽略了new的过程,从这个过程就知道他解耦的实现了。
简单使用大致步骤( 看懂请略过 ):
第一步:用Inject标注,告诉dagger2可以实例化这个类,如:Person
第二步:使用注解Component,表示要将依赖注入到AstudyActivity里
第三步:使用android studio build下的Make Project生成代码,使他自动生成DaggerComponent生成的类,类名是:Dagge+我们定义的Component的名字
2、带@Module的使用
为什么会有module的概念,比如上面的Person的构造方法可以用@Inject标注,但是引入的第三方库可是没有办法加的,所以这里使用@Module可以解决这个问题。
这里我们定义个Human,假装他是第三方类库,里面没有使用@Inject
public class Human {
public Human() {
}
}
1
2
3
4
5
接下的步骤先定义我们的数据源Module,也就是先定义初始化的地方,之前Person的构造方法是用@Inject。首先命名规则最好加上Module,用@Module标注。然后里面定义个方法,用 @Provides标注。返回值为我们需要初始化的类,方法名最好是以Provides结尾。其实这里可以定义多个方法,后面说
@Module
public class BstudyActivityModule {
@Provides
Human humanProvides(){
return new Human();
}
}
1
2
3
4
5
6
7
然后是我们的Component。这里与之前不同的是(modules = BstudyActivityModule.class),这就相当于把注射液放进注射器。这里可以有多个Module,后面说
@Component(modules = BstudyActivityModule.class)
public interface BstudyActivityComponent {
void injectTo(BstudyActivity bstudyActivity);
}
1
2
3
4
Make Project后,在Activity里操作与之前一模一样。
带Module使用大致步骤( 看懂请略过 )
1、假设Human不可随意更改,没有@Inject标注(第三方类库,不是你项目里的代码肯定没有@Inject)用@module标注BstudyActivityModule,用@Provides标注方法的返回值就是我们需要inject的类型
2、编写Component接口使用@Component标注这个接口,并使用modules=的方法链接上第一步中编写的Module类;
3、接下来就和AstudyActivity中的使用方式一样了
3、通过Module传参
这个其实不重要,重要的引出4,5的概念。明白这步,后面才好理解。
首先我们假设2个类,女人类,和灵魂类:且灵魂类有个钱的属性。灵魂类又是女人的属性。灵魂类如下:
public class Soul {
private int money;
public Soul() {
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
女人如下:
public class Woman {
private Soul soul;
public Soul getSoul() {
return soul;
}
public Woman(Soul soul) {
this.soul = soul;
}
}
1
2
3
4
5
6
7
8
9
10
11
首先还是定义我们的Module先。既然可以传参,当然是有个money的属性。最终我们依赖注解是要使用Woman类。我们的providesWoman方法用@Provides标注,这个时候他回去找Soul的初始化,先通过@Provides去找Soul。这个时候找到了providesSoul。这样就形成了女人类。假如这个时候没有providesSoul。他会去找Soul类里有没有用@Inject标注的构造函数。如果还没有,那么不好意思。出错
@Module
public class CstudyModule {
private int money;
public CstudyModule(int money) {
this.money = money;
}
@Provides
Soul providesSoul() {
Soul soul = new Soul();
soul.setMoney(this.money);
return soul;
}
@Provides
Woman providesWoman(Soul soul) {
return new Woman(soul);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
接下来是Component,没有变化
@Component(modules = CstudyModule.class)
public interface CstudyActivityComponent {
void injectTo(CstudyActivity cstudyActivity);
}
1
2
3
4
Activity有些许变化,当然是传参了。我们给女人的灵魂传了100块,对,女人只值100块!
public class CstudyActivity extends BaseActivity {
@Inject
Woman woman;
@Override
protected void processLogic() {
DaggerCstudyActivityComponent.builder()
.cstudyModule(new CstudyModule(100))
.build().injectTo(this);
}
}
1
2
3
4
5
6
7
8
9
10
注意点( 看懂请略过 ):
在Module的构造函数带有参数且参数被使用的情况下,所生产的Component类就没有create()方法了。
在这里的module如果没有providesSoul()方法的话,还有一种情况只要在Soul的构造方法有@Inject也是可行的。
4、使用@Component.Builder(需先了解 3、通过Module传参)
我们把3、通过Module传参apt生成的代码点开DaggerCstudyActivityComponent;看下图是不是发现了一个Builder类,这是apt帮我们自动生成的,我们当然也能自己实现
还是以3、通过Module传参的例子,我们不用系统帮我们生成的Builder,自己定义。前面的步骤都一样,直接来看我们的Component。
自己定义个接口类Builder,并用@Component.Builder标注里面有2个方法:
方法一:是返回值Builder的方法,这里如果传module就会以我们传的为主,否则他会帮我们生成一个money为0的module。当然你也随意传数据类型,只不过无效。可以试试,
方法二:是返回值为当前Component的方法,方法名其实都可以自定义,当最好以规范为主,用习惯了就明白了
@Component(modules = CstudyModule.class)
public interface DstudyActivityComponent {
void injectTo(DstudyActivity dstudyActivity);
@Component.Builder
interface Builder {
Builder cstudyModule(CstudyModule cstudyModule);
DstudyActivityComponent build();
}
}
1
2
3
4
5
6
7
8
9
10
Activity里使用是一样的。只不过我们把系统自动帮我们生成的,自己去写了而已。还是贴下Activity代码吧
public class DstudyActivity extends BaseActivity {
@Inject
Woman dWoman;
@Override
protected void processLogic() {
DaggerDstudyActivityComponent.builder()
.cstudyModule(new CstudyModule(100))
.build().injectTo(this);
}
}
1
2
3
4
5
6
7
8
9
10
大致理解和总结为( 看懂请略过 ):
通过我们cstudy的内容,你可以点开cstudyModule查看源码,可以看到有个Builder cstudyModule(CstudyModule cstudyModule){}。这是dagger2自动生成的(你还可以通过,app/build/generated/source/apt/debug/你的包名/DaggerAppComponent.java 目录下找到)
所以@Component.Builder的用法,用module传参的例子。其他都不用变,要变的是Component,定义个Builder并用@Component.Builder标注。这里有2个方法:
方法一:是返回值Builder的方法,这里如果传module就会以我们传的为主,否则他会帮我们生成一个money为0的module。当然你也随意传数据类型,只不过无效。可以试试
方法二:是返回值为当前Component的方法,方法名其实都可以自定义,当最好以规范为主,用习惯了就明白了
5、使用@BindsInstance(需先了解 4、使用@Component.Builder)
这个时候你又说了,传参,我们总是要new CstudyModule(100)。本来说Dagger2在使用的时候省略new的过程,解耦。但这里还要new,很low是不是。不急不急,强大的google把一切都想好了。这个时候遇到一个新的标注@BindsInstance。
@BindsInstance 大致这里可以理解为帮我们省去写类的构造方法,而直接去赋值
省掉构造方法,那么当然是首先改我们的Module。我们去掉Module的构造方法及money成员变量属性,把money加到providesSoul里成型参。看到这里,这里又可理解为@BindsInstance 其实去找@Provides标记的方法的参数,假如类型一致就去初始化
@Module
public class EstudyModule {
@Provides
Soul providesSoul(int money) {
Soul soul = new Soul();
soul.setMoney(money);
return soul;
}
@Provides
Woman providesWoman(Soul soul) {
return new Woman(soul);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
然后是修改的Component,改完Module,当然是modules = EstudyModule.class。这些我就忽略了,看了上面的步骤你也明白,我就直接说关键地方了。用@BindsInstance标注我们返回值为Builder的方法。里面的参数改成我们的int Money。当然改成我们用@Provides标注的类型其实都可以,只不过这里你如果改成Soul soul,当然你初始化还是要传new Soul。过程就是这个过程
@Component(modules = EstudyModule.class)
public interface EstudyActivityComponent {
void injectTo(EstudyActivity estudyActivity);
@Component.Builder
interface Builder {
@BindsInstance
Builder initMoney(int money);
EstudyActivityComponent build();
}
}
1
2
3
4
5
6
7
8
9
10
11
最后是我们的Activity
public class EstudyActivity extends BaseActivity {
@Inject
Woman woman;
@Override
protected void processLogic() {
DaggerEstudyActivityComponent.builder()
.initMoney(100)
.build().injectTo(this);
}
}
1
2
3
4
5
6
7
8
9
10
看到这里,是不是觉得Dagger2还比较有意思。更有意思的在后面。当然也越来越绕了,但是你得兴奋起来,精髓啊。
6、Component依赖Component,使用dependence
这里我们以Activity和Fragment为例。假设我们再Activity依赖注入Human类,此时在Fragment里使用
先看定义Module,和之前一样,没什么区别
@Module
public class FstudyActivityModule {
@Provides
Human providesHuman() {
return new Human();
}
}
1
2
3
4
5
6
7
再建ActivityComponent当然这里,你也可以加上void inject(FstudyActivity fstudyActivity)。重要一点是我们要把依赖注入的类返回出去,定义方法provideHuman,因为是Component依赖Component。所以也能理解
@Component(modules = FstudyActivityModule.class)
public interface FstudyActivityComponent {
Human provideHuman();
}
1
2
3
4
再使用dependencies建FragmentComponent暂且可以理解为子Component,因为后面真的有子Component。dependencies = FstudyActivityComponent.class写上我们的父Component。后面是注入到Fragment里
@Component(dependencies = FstudyActivityComponent.class)
public interface TestFragmentComponent {
void inject(TestFragment testFragment);
}
1
2
3
4
在Activity里,要先生成ActivityComponent,然后提供个方法,把父Component提供给Fragment
public class FstudyActivity extends BaseActivity {
private FstudyActivityComponent fstudyActivityComponent;
@Override
protected void processLogic() {
fstudyActivityComponent = DaggerFstudyActivityComponent.create();
}
public FstudyActivityComponent getFstudyActivityComponent() {
return fstudyActivityComponent;
}
}
1
2
3
4
5
6
7
8
9
10
11
在Fragment里
public class TestFragment extends BaseFragment {
@Inject
Human human;
@Override
protected void processLogic(Bundle savedInstanceState) {
FstudyActivityComponent fstudyActivityComponent = ((FstudyActivity) getActivity()).getFstudyActivityComponent();
DaggerTestFragmentComponent.builder()
.fstudyActivityComponent(fstudyActivityComponent)
.build().inject(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
好了,在fragment可以使用human了。在java里使用,确实很绕,代码多的让你难以接受。建议先理解,后面出的一篇在Android中使用,你会很爽。
大致理解和总结为( 看懂请略过 ):
1、假设我们用Human注入,这里的FstudyActivityModule 和之前的Module一样,正常
2、FstudyActivityComponent把要注入的类返回
3、在TestFragment方面,我们新建一个TestFragmentComponent 依赖 FstudyActivityComponent;
4、在FstudyActivity自定义一个方法把FstudyActivityComponent提供出去,供TestFragment使用
5、在TestFragment,注册下就OK了。很绕,个人建议先明白这个流程就好了
7、Component依赖Component,使用@subComponent(这个和 【标题6】 实现的是同一个效果)
虽然是实现同一个效果,但是方式不同,目的是让你更多了解Dagger2。同样以上面的例子。Module和上面一样不变(我这里是为了Demo区域化,虽然类名不同,但是内容是一致的)
首先建子Component,FragmenComponent,用@Subcomponent标注,并注入我们的Fragment里。为什么先建子Component呢。因为子Component要在父Component返回,绕不绕!!
@Subcomponent
public interface DemoFragmentComponent {
void inject(DemoFragment demoFragment);
}
1
2
3
4
然后是父Component,ActivityComponent,父Component一切正常,返回值是子Component
@Component(modules = GstudyActivityModule.class)
public interface GstudyActivityComponent {
DemoFragmentComponent demoFragmentComponent();
}
1
2
3
4
在Activity里的操作一样,初始化我们的父Component,并提供方法,返回父Component,供Fragment使用。
然后是Fragment里
public class DemoFragment extends BaseFragment {
@Inject
Human human;
@Override
protected void processLogic(Bundle savedInstanceState) {
GstudyActivityComponent gstudyActivityComponent = ((GstudyActivity) getActivity()).getGstudyActivityComponent();
gstudyActivityComponent
.demoFragmentComponent()
.inject(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
这样就成功了,可以在Fragment使用human了。看明白了标题6,其实标题7原理是一样的。
大致理解和总结为( 看懂请略过 ):
1、先建一个子类Component,用@subComponent标注,DemoFragmentComponent
2、然后建父类Component: GstudyActivityComponent,定义个方法,返回子类Component。
3、在GstudyActivity自定义一个方法把GstudyActivityComponent提供出去,供DemoFragment使用
4、在DemoFragment,注册下,就好了。大致和dependencies类似
注意:但注册的时候写法不同,之前是通过子Component传入父Component;而这里是从父Component中获取子Component,然后直接inject
8、Component依赖Component,使用 @Subcomponent.Builder(和【标题6】&【标题7】实现的是一样的效果)
效果一样,方式不同,目的还是更了解Dagger2。可以看到这里的标注是@Subcomponent.Builder。所以和使用@Subcomponent类似。
好了,还是以上面的例子为例。这里需要改的是父Component和子Componet。这个时候我们不免想到@Component.Builder的用法。是不是一样呢。这个时候我只能说类似,但是又不一样。毕竟这里多了个sub。我们先看下@Component.Builder的用法,拷贝之前代码(不知道再去回顾下【标题4】)
@Component(modules = CstudyModule.class。)
public interface DstudyActivityComponent {
void injectTo(DstudyActivity dstudyActivity);
@Component.Builder
interface Builder {
Builder cstudyModule(CstudyModule cstudyModule);
DstudyActivityComponent build();
}
}
1
2
3
4
5
6
7
8
9
10
我们按照找个方式去写@Subcomponent.Builder。,@Subcomponent.Builder要使用肯定是在@Subcomponent下,毋庸置疑。首先发现没有modules = CstudyModule.class。被@Subcomponent取代了。没有Module我们就使用无参
@Subcomponent
public interface OtherFragmentComponent {
void inject(OtherFragment otherFragment);
@Subcomponent.Builder
interface Builder {
Builder noModule();
OtherFragmentComponent build();
}
}
1
2
3
4
5
6
7
8
9
我先告诉告诉你运行结果把,运行结果报错了。
@Subcomponent.Builder types must have exactly one zero-arg method,
and that method must return the @Subcomponent type. Already found: hstudyActivityModule()
报错信息如下:意思是不需要Build返回值方法,通过Already found: hstudyActivityModule()知道,已经发现了我们的Module。我们再想想这个标注的名称sub,不就是子Component继承父Componet吗。而且Dagger2内部已经默认了,所以这里没有Builder返回值方法。所以正确的子Component
@Subcomponent
public interface OtherFragmentComponent {
void inject(OtherFragment otherFragment);
@Subcomponent.Builder
interface Builder {
OtherFragmentComponent build();
}
}
1
2
3
4
5
6
7
8
接下来是父Component,返回值当然是我们的Builder。
@Component(modules = HstudyActivityModule.class)
public interface HstudyActivityComponent {
OtherFragmentComponent.Builder sonbuilder();
}
1
2
3
4
有人就疑惑了不可以返回子Component吗。我们假如此时返回子Component,我先告诉你运行报错,信息如下:
Components may not have factory methods for subcomponents that define a builder.
大概意思是:用了@Subcomponent.Builder的话,Component没有工厂模式方法去创建我们的子Component。好了,就这样,请原谅我的英语四级!!
Activity还是和之前一样,初始化我们的父Component,并通过方法返回。Fragment里使用依赖如下
public class OtherFragment extends BaseFragment {
@Inject
Human human;
@Override
protected void processLogic(Bundle savedInstanceState) {
HstudyActivityComponent hstudyActivityComponent = ((HstudyActivity) getActivity()).getHstudyActivityComponent();
hstudyActivityComponent.
sonbuilder().build().inject(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
好了,绕来绕去,功能实现了!看到这里对Dagger2大致了解了吧。