Dagger2这枚大名鼎鼎的匕首,相对于Rxjava、Retrofit、Okhttp等可能是最难上手的的框架了;许多人都学习它不下数遍,网上的教程也都是从入门到放弃、再从入门到放弃,如此反复才最终恍然大悟;这其中也有许多人看了一两遍之后对他望而却步。
Dagger是安卓界最棒的依赖注入框架,第一代由大名鼎鼎的Square公司共享出来,第二代则是由谷歌接手后推出的。
很多小白对于Dagger2是啥浑然不知,更不知其能带来的好处了。这里举个例子,比如有个类A,他的构造函数需要传入B,C;然后代码里有10个地方实例化了A,那如果功能更改,A的构造函数改成了只有B,这个时候,你是不是要去这10个地方一个一个的改?如果是100个地方,你是不是要吐血?!如果采用dagger2,这样的需求只需要改1-2个地方,你感觉怎么样?对你有诱惑吗?
也有人怀疑Dagger2利用注解是不是采用了反射,性能怎么样?其实Dagger2、ButterKnife这类依赖注入框架都已经采用了apt代码自动生成技术,其注解是停留在编译时,完全不影响性能。
笔者在学习过程中当然看过不少的教程,但是很多教程都是讲思想讲的很全面,却忽略一个更重要的事实:如果一个初学者在一时半会的时间内压根理解不了这种思想岂不是就学不会Dagger2的用法了;如果小白们的项目中立即就需要使用Dagger2,去哪里要来数月的学习时间呢。于是本文就从小白的视角,先教小白们如何上手它;相信在上手后使用它的过程中小白们更深刻也更快的理解Dagger2的依赖注入思想。
比如很多教程就贴出如下代码,然后说@Inject 注解就是这么用的,结果小白们照抄运行后就崩了且不知其所以然,懵逼了。
错误师范
话说可能是本人较笨,但是我抵制这样的教程,你这教的根本不到位啊,本篇就会给小白们贴出到位的代码。
目录导读:(1 和 2 是基础上手的内容,对其练习熟悉且有一定的理解后再去看 3 和 4,不然想一口吃成胖子容易噎住)
0、项目添加dagger2支持;
1、最简单不带 Module 的 Inject 方式;
2、带 Module 的 Inject方式;
3、Component 依赖 Component(a、dependence方式;b1、subComponent方式);
3.1、遗漏的:b2——subComponent方式
4、Scope 作用域——单例;
5、总结;
6、扩展的dagger.android最新api。
使用dagger2的前提,需要先在app的build.gradle中的dependencies代码块里添加如下依赖:
(最新的版本号请浏览dagger2的github主页中的releases详情 https://github.com/google/dagger)
添加dagger2支持
由我们自己定义的类,我们可以自由修改的情况下我们使用这种方式,也分为两种:带参数和不带参数的。
a、构造参数不带参数的:
不带参数的 inject
编写步骤:
第一,将我们需要注入的对象的类的构造参数使用@Inject标注,告诉dagger2它可以实例化这个类;
第二,编写Component接口使用@Component进行标注,里面的void inject()的参数表示要将依赖注入到的目标位置;
第三,使用android studio的Build菜单编译一下项目,使它自动生成我们编写的Component所对应的类,生成的类的名字的格式为 "Dagger+我们所定义的Component的名字";
第四,在需要注入的类中使用@Inject标注要注入的变量;然后调用自动生成的Component类的方法create()或builder().build(),然后inject到当前类;在这之后就可以使用这个@Inject标注的变量了。
Notice:对比上面闲言碎语里的错误师范,我们发现此处只是多了一个Component而已;Component就是注入者与被注入者之间联系的桥梁,有了它dagger2才知道要把谁注入到什么地方,所以它是非常重要且不可缺少的。
b、构造参数带参数的:编写步骤与上面的相似就不说了。
带参数的 inject
我们给Factory的构造参数也添加了@Inject标注,但是它的构造参数是带参数的;那这里就会有一个问题:我们上面说了必须使用Component的inject()之后才完成注入,我们并没有给Factory进行inject()的操作啊,它为什么能去实例化Product呢?
其实这个inject过程并不像我们的代码看上去是这种inject套inject的过程,真实过程是这样:在FactoryActivity中进行inject()的时候,发现Factory的构造函数被@Inject标注了且带有一个参数,然后dagger2就去寻找Product发现它的构造函数也被@Inject标注并且无参数,于是dagger2把Product的实例注入给FactoryActivity,然后再去实例化Factory的时候用的是已经注入给FactoryActivity的那个Product实例。也就是说我们可以这样理解:并不是Factory直接实例化Product,而是FactoryActivity实例化Product后交给Factory使用的。
直接给构造函数添加@Inject标注的方式对于我们自己编写的代码肯定是没问题,但若是我们引入的第三方库不能随意改动代码的话就不方便了,我们这里使用如下两个类OkHttpClient和RetrofitManager模拟不可改动代码的情况:
模拟不可改动代码的类
这种情况下就需要用到Module了,代码如下:
a、简单Module使用
不可更改代码需要inject要用module
编写步骤:
第一步,编写Module类并使用@Module标注这个类,编写方法返回值为我们需要inject的类型并使用@Provides标注这个方法;
第二步,编写Component接口,使用@Component标注这个接口,并使用modules=的方法链接上第一步中编写的Module类;
之后的步骤就和 1 中的inject一样了。
b、复杂Module使用:(只需要在简单Module的基础上作些许改动,Component代码未作改动就不贴了)
1、如果我们希望在使用的时候才传入一些配置,直接使用Module的构造参数传入即可,这种用法注意HttpActivity中Component实例化的时候使用builder模式传入了我们需要传入的值;
2.1、Module中其中一个依赖又要依赖另外一个依赖,如果被@Provides标注的方法带有参数,dagger2会自动寻找本Module中其他返回值类型为参数的类型的且被@Provides标注的方法,如果本Module中找不到就会去看这个类的构造参数是否被@Inject标注了(所以一般情况下Module中方法的返回值都不能相同,当然也有办法使多个方法的返回值类型相同,有需要的朋友请自行研究吧,本篇只讲解基础上手);
复杂module
2.2、如果本Module中找不到就会去看这个类的构造参数是否被@Inject标注了:
复杂module2
a、在Module的构造函数带有参数且参数被使用的情况下,所生产的Component类就没有create()方法了。
b、熟练使用1和2,我们就能配合mvp进行使用dagger2了,比如将presenter注入到view层;值得一提的是谷歌不推荐直接将presenter的构造参数添加注解,更加推荐的是将presenter放到Module里进行管理,因为这样代码更加容易管理。
当我们其中一个Component跟另外一个Component所提供的依赖有重复的时候,我们没有必要完全再写一遍,一个Component是可以依赖另外一个依赖的,理解起来就像extends关键字;有两种实现方式:
a、dependence方式
dependence方式依赖其他Component
dependence实现方式总结:
1、父Component中要显式的写出需要暴露可提供给子Component的依赖;
2、子Component在注解中使用dependencies=来连接父Component;
3、注意子Component实例化方式。
b1、subComponent方式:(跟dependence实现方式一样的代码就不贴了)
subcomponent方式依赖其他Component
b1、subComponent实现方式总结:
1、先定义子Component,使用@Subcomponent标注(不可同时再使用@Component);
2、父Component中定义获得子Component的方法;
3、注意子Component实例化方式。
3.1、遗漏的:b2——subComponent方式:
b1直接在父Component中提供了返回子Component的方法,忽略了子Component构建时需要传入参数的情况,当然不需要传入参数也可以用这种方法;[重点:子Component构建时传入参数的话就需要在子Component中使用@Subcomponent.Builder注解(接口或抽象类)];
补充子moudle需要传入参数的情况(1)
补充子moudle需要传入参数的情况(2)
b2、subComponent实现方式总结:
1、在子Component,定义一个接口或抽象类(通常定义为xxBuilder),使用@Subcomponent.Builder标注:
(一)、编写返回值为xxBuilder,方法的参数为需要传入参数的Module,(二)、编写返回值为当前子Component的无参方法;
2、父Component中定义获得子Component.Builder的方法;
a、无Module的使用方式,只需提供依赖的类及Component都添加@Singleton标注即可;
无Module的Scope
如果使用@Singleton标注了构造参数,或者只标注了提供依赖的类而没有标注Component,在编译的时候分别会报如下两种错误:
@Singleton错误用法提示
b、如果是带Module的,Component必须添加@Singleton标注,然后再根据需要给Module中@provides标注的方法再标注上@Singleton(跟无Module的一样的代码就不贴了):
有Module的Scope
关于Scope要注意的:
1、把Scope简单的解释为单例还是不科学的,只是我们刚开始接触的时候使用@Singleton从而觉得它就是单例,其实正确的理解应该是:在某个范围里它是单例(何为作用域呢,可以看作是我们在程序中实例化的Component的生命周期的长短:如果在Application里build的那它的作用域就是整个App的生命周期,如果是在Acitivity中build的那它的作用域就跟此Acitivity的生命周期相同,依次类推);
2、Scope只是一个标注,跟它的名字无关,跟它使用的地方及Component实例化的地方有关,Scope在很大程度上是为了方便阅读代码;
3、在Component依赖Component的时候,Scope的名字必须是不同的,这就需要自定义Scope了,这个实在是太简单本篇就不写了。
1、Module并不是必需的,但Component是必不可少的;
2、编译后生成的Component实现类的名称是Dagger+我们所定义的Component接口的名称。
3、Dagger2的依赖注入思想重在理解,希望小白们上手以后不单单是在使用它,更重要的是要理解它;Dagger2还有很多其他花式用法,比如在文里提到的一个Module两个方法返回值相同,还有懒加载等等,希望大家自己研究一下,以防不时之需。
在使用dagger2的过程中,在定义一些类或方法的名字的时候,要遵守一些谷歌提出的固定标准,以方便代码阅读与维护:
1、定义的Component和Module的名字是无所谓的,但是一般遵照以Component或Module结尾的名称;
2、Module中用@Provides标注的方法的方法名是无所谓的,返回值是最重要的,但是一般遵照以provide开头的方法名;
3、Component中返回值为void且有参的方法,方法名是无所谓的,参数是最重要的代表的是要注入的目标位置,但是方法名一般为inject;
4、Component中返回值不为void且无参的方法,方法名是无所谓的,返回值是最重要的代表的是暴露给子Component使用的依赖或者是获取的子Component的类型。
dagger2已经很完美了吗?谷歌大神们并不满足。因为android有许多系统组件,所以以上各种注入方式在activity注入的时候需要再去获得appComponent,十分繁琐;于是谷歌大神们又研究出一套专门用于android的注入方式,直接上代码,注意最后的一行代码注入是不是很牛叉:
dagger.android最新api写法(1)
dagger.android最新api写法(2)
我们必须在父Module中添加@Module(subcomponents={子Component})的方式来关联它们;
注意这样使用的时候子Component中必须存在被@Subcomponent.Builder标注的类或接口,否则会报如下的错:
必须使用@Subcomponent.Builder
其实观察下来这种SubComponent的实现方式与b2很像有木有!
dagger.android使用总结:
1、在AppComponent中将dagger2库里的AndroidInjectionModule注入到Application中,并将Application实现相应的接口(例如:HasActivityInjector、HasFragmentInjector、HasServiceInjector、HasBroadcastReceiverInjector等等很多),并返回相应的方法,返回值参照以上App中的方式;
2、子Component继承AndroidInjector,内部的Builder使用抽象类并继承AndroidInjector.Builder;
3、父Module使用@Module(subcomponents={})的方式关联子Component,并在父Module中编写返回值为AndroidInjector.Factory、参数为子Component.Builder的抽象方法(如果有其他被@Provides标注的方法,应将方法改为static,否则报错);
4、最后在Acitivity的onCreate()中第一行代码的位置使用AndroidInjection注入,如果是Fragment则是在onAttach()方法中,其他的请自行查阅。
5、dagger.android库也提供了其他实现方式,诸如DaggerApplication、DaggerActivity、DaggerFragment、DaggerService、DaggerBroadcastReceiver等实现类,有兴趣的小伙伴自己研究一下吧。