前言
学习Dagger2有些时间了,现在来重新认识一下Dagger2是什么,相信Dagger2这个大名早已响遍android界了,之前听说的时候开始懵逼这是个啥,一开始去学习的时候也是懵逼,入门还是挺困难的,但是学习下来也不是那么难,下面总结了一下Dagger2各个方面的内容
Dagger2是啥?
A fast dependencyinjector for Android and Java. http://google.github.io/dagger
翻译:一个提供给Android和Java使用的快速依赖注射器
最早的版本Dagger1 是由Square公司开发的,Dagger2是Dagger的升级版,现在由Google接受维护了,是一个依赖注入框架。
相对Dagger1来说它使用的是预编译期间生成代码来完成依赖注入的,而Dagger1是使用反射,在android上使用反射是比较消耗性能的。还有的是Dagger2是使用生成代码来实现完整依赖注入,所以完全可以在相关代码处下断点进行运行调试。相对Dagger1更进一层楼
依赖注入(DependencyInjection,简称DI)
维基百科:控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
在我们的项目中经常遇到在一个对象里去创建另外一个对象,这样就会产生耦合的问题,违背了单一职责原则,不符合开闭原则,没错,依赖注入就是用来解耦的,举个简单栗子:
public class A {
B b;
public A() {
b = new B();
}
public void do() {
b.doSomething();
}
}
在A中有B的实例,则称A对B有一个依赖
当B业务有变化时,修改了B,你会发现A不得不改,不然完成不了所增加的业务功能,原因就是A里面的B是new出来的,这种方式会增加各个模块的耦合,单测时候还特别的困难,这时候就要使用依赖注入了,依赖注入的依赖是从外部传递过来的,而且在Java平台上很多时候都是通过反射或者动态编译来提供依赖注入,例如spring用了大量的依赖注入
依赖注入有几种方式:
(1)通过set方法注入:
public class A {
B classB;
public void setB(B b) {
classB = b;
}
}
(2)通过接口注入:
interface BInterface{
void setB(B b);
}
public class A implements BInterface {
B classB;
@override
void setB(B b) {
classB = b;
}
}
(3)通过构造方法注入:
public class A {
B classB;
public void A(B b) {
classB = b;
}
(4)通过java注解(这也是Dagger2使用注入的方式)
public class A {
//需要依赖注入框架的支持,如Dagger2
@inject B classB;
public ClassA() {}
}
Dagger2就是使用java注解的方式把依赖注入到宿主类中的,这样我们需要依赖的类和提供依赖的类的实现方法分隔开了,也不会影响到某个类,这就很愉快的改变业务需求了
引入Dagger2
Dagger2作为Android端的IOC框架,为了不影响性能,它是通过apt动态生成代码来实现的.
(1)配置apt插件(在项目根目录build.gradle文件中添加如下代码)
dependencies {
...
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
(2)添加Dagger2依赖(在app目录的build.gradle文件中添加)
//应用apt插件
apply plugin:'com.neenbedankt.android-apt'
...
dependencies {
...
//引入dagger2
compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'
//java注解
provided'org.glassfish:javax.annotation:10.0-b28'
}
Dagger2几个重要概念讲解
@Inject:带有此注解的属性(让Dagger2为其提供依赖)或构造方法(Dagger2通过Inject标记可以在需要这个类实例的时候来找到这个构造方法并把相关实例new出来)将参与到依赖注入中,Dagger2会实例化有此注解的类。
@Provide: Provide主要用于标注Module里的方法。在Module中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。用Provide来标注一个方法,该方法可以在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject的变量赋值。@Provides标注的方法可以是没有输入参数的,Module中@Provides标注的方法也是可以带输入参数的,其参数值可以由Module中的其他被@Provides标注的方法提供
@Module:用@Module注解的类是专门用来提供依赖的,这样Dagger2在构造方法实例时候就可以找到需要的依赖。看了上面的@Inject,需要在构造方法上标记才能提供依赖,那么如果我们需要提供的类构造方法无法修改怎么办,比如一些jar包里的类,我们无法修改源码。这时候就需要使用Module了。Module可以给不能修改源码的类提供依赖,当然能用Inject标注的通过Module也可以提供依赖。之所以有Module类主要是为了提供那些没有构造函数的类的依赖,这些类无法用@Inject标注,比如第三方类库,系统类,以及View接口等等
@Component:Component用来为“被注入方“提供其所需要的注入类。被标注了Component的接口在编译时会产生相应的类的实例来作为提供依赖方和需要依赖方之间的桥梁,Component通过这个Inject方法可以将依赖需求方对象送到Component类中,Component类就会根据依赖需求方对象中声明的依赖关系来注入依赖需求方对象中所需要的对象。
这些理论都比较抽象,如图说明一下依赖提供方和依赖需求方的关系:
按照上图,Dagger2的主要工作流程分为以下几步:
(1)将依赖提供方提供的实例传入给Component实现类
(2)Component根据依赖提供方的实例提供依赖,并把这些依赖注入相关的类中
(3)Component跟依赖需求方确定依赖对象后,Component会在与自己关联的Module类中查找有没有提供这些依赖对象的方法,有的话就将Module类中提供的对象设置到依赖需求方实例中
Dagger2使用
下面举个栗子说说Dagger2的使用,这里是基于简单的mvp模式
在mvp模式中最常见的一种依赖关系,就是Activity持有presenter的引用,并在Activity中实例化这个presenter,Activity要依赖presenter,presenter又要要实现view接口,达到更新UI,如下:
view:
public interface MainView {
interface View{
void updateUI();
}
}
MainActivity:
public class MainActivity extends AppCompatActivity implements MainView.View{
MainPersenter mainPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//实例化presenter,并把view穿个presenter
mainPresenter = new MainPersenter(this);
//调用presenter方法加载数据
mainPresenter.loadData();
}
@Override
public void updateUI() {
}
}
Presenter:
public class MainPersenter {
//MainView是个接口,View是他的内部接口,这里看做View接口即可
private MainView.View mView;
MainPersenter(MainView.View view) {
mView = view;
}
public void loadData() {
//调用model层方法,加载数据,如数据库,网络api
//成功时回调的方法
mView.updateUI();
}
}
这种mvp方式会让Activity跟presenter耦合在一起了,如果修改presenter的构造方法,另一个也要修改。使用Dagger2依赖注入框架就不一样了,实现方式如下:
presenter:
public class MainPersenter {
//MainContract是个接口,View是他的内部接口,这里看做View接口即可
private MainView.View mView;
@Inject
MainPersenter(MainView.View view) {
mView = view;
}
public void loadData() {
//调用model层方法,加载数据
//回调方法成功时
mView.updateUI();
}
}
Module:
@Module
public class MainModule {
private final MainView.View mView;
public MainModule(MainView.View view) {
mView = view;
}
@Provides
MainView.View provideMainView() {
return mView;
}
}
Component:
@Component(modules = MainModule.class)
public interface MainComponent {
void inject(MainActivity activity);
}
MainActivity:
public class MainActivity extends AppCompatActivity implements MainView.View{
@Inject
MainPersenter mainPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder()
.mainModule(new MainModule(this))
.build()
.inject(this);
//调用Presenter方法加载数据
mainPresenter.loadData();
}
@Override
public void updateUI() {
}
}
这看起来好像复杂了,就是第一种方式虽然简单,但具有耦合性,为了解决这种耦合,可能会产生很多辅助类,让这种直接的关系变得间接,依赖关系,从而降低耦合,大多数设计模式也一样,为了高内聚低耦合,会多很多类和接口,跟mvc和mvp相比的是同一个道理,Dagger2也一样,虽然看起来类多了,复杂了,但是仔细看下来还是很明朗的,Dagger2中生成代码(下面再分析原理)里面也是用到了很多设计模式,如装饰模式、建造者模式等,非常值得我们学习借鉴
(1)先看看MainActivity类,第一种方式是直接MainPresenter实例化,现在在声明的基础上加了一个注解@Inject,说明MainPresenter是需要注入到MainActivity中,即MainActivity依赖于MainPresenter,这里要注意的是,使用@Inject时,不能用private修饰符修饰类的成员属性。
(2)然后在MainPresenter的构造函数上同样加了@Inject注解。这时候MainActivity里的mainPresenter与他的构造函数建立了联系,简单来说就是当看到某个类被@Inject标记时,就会关联到他的构造方法中,如果这个构造方法也被@Inject标记的话,就会自动初始化这个类,从而完成依赖注入。
(3)它们关联起来是靠Component这个桥梁来联系的
Component是一个接口或者抽象类,用@Component注解标注(这里先不管括号里的modules),我们在这个接口中定义了一个inject()方法,参数是Mainactivity。注意编写完Component接口后Dagger2并不会自动创建对应的类,要Rebuild Project一下项目,就会生成一个以Dagger为前缀的Component类,这里是DaggerMainComponent,然后在MainActivity里写下面代码
DaggerMainComponent.builder()
.mainModule(new MainModule(this))
.build()
.inject(this);
这里通过new MainModule(this)将view传递到MainModule里,然后MainModule里的provideMainView()方法返回这个View,当去实例化MainPresenter时,发现构造函数有个参数,此时会在Module里查找提供这个依赖的方法,将该View传递进去,这样就完成了presenter里View的注入。
(4)Module类,注解是Dagger2中的关键,编写Module类时要在该类上声明@Module以表明该类是Module类,这样Dagger2才能识别。那@Provides的作用是声明Module类中哪些方法是用来提供依赖对象的,当Component类需要依赖对象时,他就会根据返回值的类型来在有@Provides注解的方法中选择调用哪个方法.在一个方法上声明@Provides注解,就相当于创建了一个实例
来回顾一下上面的几个基本概念:
@Inject:这个注解是用来把属性或者构造方法参与到依赖注入中,Dagger2会实例化带有此注解的类
@Module:是用来提供依赖的,类中带有@Provide注解的方法就是所提供的依赖,Dagger2会在类中寻找实例化某个类所需要的依赖
@Component:用来将@Inject和@Module搭建联系的桥梁,从@Module中获取依赖并将依赖注入给@Inject
回顾一下上面的注入过程:
首先MainActivity需要用@Inject标注进行依赖MainPresenter,表明这是要注入的类。然后,对MainPresenter的构造函数也添加注解@Inject,此时构造函数里有一个参数MainView.View,因为MainPresenter需要依赖MainView.View,所以我们定义了一个类,叫做MainModule,提供一个方法provideMainView,用来提供这个依赖,这个MainView是通过MainModule的构造函数注入进来的,接着我们需要定义Component接口类,并将Module包含进来
@Component(modules = MainModule.class)
public interface MainComponent {
void inject(MainActivity activity);
}
同时里面声明了一个inject方法,方法参数为ManActivity,用来获取MainActivity实例,以初始化在里面声明的MainPresenter
DaggerMainComponent.builder()
.mainModule(new MainModule(this))
.build()
.inject(this);
到这里,依赖注入的过程就完成了,下面会对注入过程生成的源码进行分析Dagger2内部做了什么
Dagger2是怎么选择依赖提供的呢,规则是这样的:
步骤1:查找Module中是否存在创建该类的方法。
步骤2:若存在创建类方法,查看该方法是否存在参数
步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
概括一下就是从注解了@Inject的对象开始,从Module和注解过的构造方法中获得实例,若在获取该实例的过程中需要其他类的实例,则继续获取被需要类的实例对象的依赖,同样是从Module和标注过的构造方法中获取,并不断递归这个过程直到所有被需要的类的实例创建完成,在这个过程中Module的优先级高于注解过的构造方法。
Dagger2进阶的一些方法
方法参数:
上面的例子@Provides标注的provideMainView方法是没有输入参数的,Module中@Provides标注的方法是可以带输入参数的,其参数值可以由Module中的其他被@Provides标注的方法提供。例如这样:
@Module
public class MainModule {
private final MainView.View mView;
private Context mContext;
private MultiParameter mMultiParameter;
public MainModule(MainView.View view) {
mView = view;
}
@Provides
MainView.View provideMainView() {
return mView;
}
@Provides
MultiParameter provideMainView(MultiParameter multiParameter) {
return new MultiParameter(mContext);
}
}
public class MultiParameter {
private Context mContext;
@Inject
public MultiParameter(Context context){
mContext = context;
}
}
如果找不到被@Provides注释的方法提供对应参数对象的话,将会自动调用被@Inject注释的构造方法生成相应对象。
多module:
一个Component可以添加多个Module,这样Component获取依赖时候会自动从多个Module中查找获取。添加多个Module有两种方法:(1)在Component的注解@Component(modules={××××,×××})中添加多个modules,例如:
@Component(modules={ModuleA.class,ModuleB.class,ModuleC.class})
public interfaceMyComponent{
...
}
(2)添加多个Module的方法可以使用@Module的 includes的方法(includes={××××,×××}),例如:
@Module(includes={ModuleA.class,ModuleB.class,ModuleC.class})
public classMyModule{
...
}
@Component(modules={MyModule.class})
public interfaceMyComponent{
...
}
创建特定Module实例
需要传入特定的Module实例,可以使用
DaggerMainComponent.builder()
.mainModule(new MainModule(this))
.moduleA(new ModuleA())
.moduleB(new ModuleB())
.build()
.inject(this);
区分@Provides方法(@Named注解来区分)
这里以Android Context来举例。当有Context需要注入时,Dagger2就会在Module中查找返回类型为Context的方法。但是,当Container需要依赖两种不同的Context时,你就需要写两个@Provides方法,而且这两个@Provides方法都是返回Context类型,靠判别返回值的做法就行不通了。这时候就可以使用@Named注解来区分
//定义Module
@Module
publicclass ActivityModule{
privateContext mContext ;
privateContext mAppContext = App.getAppContext();
public ActivityModule(Context context) {
mContext = context;
}
@Named("Activity")
@Provides
public Context provideContext(){
return mContext;
}
@Named("Application")
@Provides
public Context provideApplicationContext (){
return mAppContext;
}
}
//定义Component
@Component(modules={ActivityModule.class})
interfaceActivityComponent{
void inject(Container container);
}
//定义Container
classContainer extends Fragment{
@Named("Activity")
@Inject
Context mContext;
@Named("Application")
@Inject
Context mAppContext;
...
public void init(){
DaggerActivityComponent.
.activityModule(new ActivityModule(getActivity()))
.inject(this);
}
}
这样,只有相同的@Named的@Inject成员变量与@Provides方法才可以被对应起来。
更常用的方法是使用注解@Qualifier来自定义注解。
@Qualifier: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 qualifier注解“@perApp”和“@perActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。
@Qualifier
@Documented //起到文档提示作用
@Retention(RetentionPolicy.RUNTIME) //注意注解范围是Runtime级别
public@interface ContextLife {
String value() default "Application"; // 默认值是"Application"
}
接下来使用我们定义的@ContextLife来修改上面的例子
//定义Module
@Module
publicclass ActivityModule{
privateContext mContext ;
privateContext mAppContext = App.getAppContext();
public ActivityModule(Context context) {
mContext = context;
}
@ContextLife("Activity")
@Provides
public Context provideContext(){
return mContext;
}
@ ContextLife ("Application")
@Provides
public Context provideApplicationContext (){
return mAppContext;
}
}
//定义Component
@Component(modules={ActivityModule.class})
interfaceActivityComponent{
void inject(Container container);
}
//定义Container
classContainer extends Fragment{
@ContextLife ("Activity")
@Inject
Context mContext;
@ContextLife ("Application")
@Inject
Context mAppContext;
...
public void init(){
DaggerActivityComponent.
.activityModule(new ActivityModule(getActivity()))
.inject(this);
}
}
组件间依赖
假设ActivityComponent依赖ApplicationComponent。当使用ActivityComponent注入Container时,如果找不到对应的依赖,就会到ApplicationComponent中查找。但是ApplicationComponent必须显式把ActivityComponent找不到的依赖提供给ActivityComponent。
//定义ApplicationModule
@Module
publicclass ApplicationModule {
private App mApplication;
public ApplicationModule(App application) {
mApplication = application;
}
@Provides
@ContextLife("Application")
public Context provideApplicationContext() {
return mApplication.getApplicationContext();
}
}
//定义ApplicationComponent
@Component(modules={ApplicationModule.class})
interfaceApplicationComponent{
@ContextLife("Application")
Context getApplication(); // 对外提供ContextLife类型为"Application"的Context
}
//定义ActivityComponent
@Component(dependencies=ApplicationComponent.class,modules=ActivityModule.class)
interfaceActivityComponent{
...
}
单例的使用
创建某些对象有时候是耗时浪费资源或者没有完全必要的或者需要确保其唯一性,这时候Component没有必要重复地使用Module来创建,这时就需要使用@Singleton注解标注为单例了。
@Module
classMyModule{
@Singleton // 标明该方法只产生一个实例
@Provides
B provideB(){
return new B();
}
}
@Singleton // 标明该Component中有Module使用了@Singleton
@Component(modules=MyModule.class)
classMyComponent{
void inject(Container container)
}
上面的例子可以看到,实现单例需要两步
(1)在Module对应的Provides方法标明@Singleton
(2)同时在Component类标明@Singleton
自定义Scope
@Scope: Dagger2可以通过自定义注解限定注解作用域,来管理每个对象实例的生命周期。@Singleton就是一种Scope注解,也是Dagger2唯一自带的Scope注解,下面是@Singleton的源码
@Scope
@Documented
@Retention(RUNTIME)
public @interfaceSingleton{}
以看到定义一个Scope注解,必须添加以下三部分:
@Scope :注明是Scope
@Documented :标记在文档
@Retention(RUNTIME):运行时级别
对于Android,我们通常会定义一个针对整个APP全生命周期的@PerApp的Scope注解如下:
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public@interface PerApp {}
和还会针对一个Activity生命周期的@PerActivity注解,如下:
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public@interface PerActivity {}
@PerApp的使用用例:
@Module
publicclass ApplicationModule {
private App mApplication;
public ApplicationModule(App application) {
mApplication = application;
}
@Provides
@PerApp
@ContextLife("Application")
public Context provideApplicationContext() {
return mApplication.getApplicationContext();
}
}
@PerApp
@Component(modules= ApplicationModule.class)
publicinterface ApplicationComponent {
@ContextLife("Application")
Context getApplication();
}
//单例的有效范围是整个Application
publicclass App extends Application {
privatestatic ApplicationComponent mApplicationComponent; // 注意是静态
public void onCreate() {
mApplicationComponent =DaggerApplicationComponent.builder()
.applicationModule(newApplicationModule(this))
.build();
}
// 对外提供ApplicationComponent
public static ApplicationComponent getApplicationComponent() {
return mApplicationComponent;
}
}
@PerActivity的使用用例:
//单例的有效范围是Activity的生命周期
publicabstract class BaseActivity extends AppCompatActivity {
protected ActivityComponent mActivityComponent; //非静态,除了针对整个App的Component可以静态,其他一般都不能是静态的。
// 对外提供ActivityComponent
public ActivityComponent getActivityComponent() {
return mActivityComponent;
}
public void onCreate() {
mActivityComponent = DaggerActivityComponent.builder()
.applicationComponent(App.getApplicationComponent())
.activityModule(newActivityModule(this))
.build();
}
}
子组件@Subcomponent
可以使用@Subcomponent注解拓展原有component。Subcomponent其功能效果优点类似component的dependencies。但是使用@Subcomponent不需要在父component中显式添加子component需要用到的对象,只需要添加返回子Component的方法即可,子Component能自动在父Component中查找缺失的依赖。
//父Component:
@Component(modules=××××)
publicAppComponent{
SubComponent subComponent (); //1.只需要在父Component添加返回子Component的方法即可
}
//子Component: //2.注意子Component的Scope范围小于父Component
@Subcomponent(modules=××××) //3.使用@Subcomponent
publicSubComponent{
void inject(SomeActivity activity);
}
//使用子Component
publicclass SomeActivity extends Activity{
public void onCreate(Bundle savedInstanceState){
App.getComponent().subCpmponent().inject(this); // 4.这里调用子Component
}
}
懒加载模式Lazy与Provider
Lazy和Provider都是用于包装Container中需要被注入的类型,Lazy用于延迟加载,Provide用于强制重新加载,具体如下:
publicclass Container{
@Inject Lazy<B> lazyB; // 注入Lazy元素
@InjectProvider<B> providerB; //注入Provider元素
public void init(){
DaggerComponent.create().inject(this);
B f1=lazyB.get(); //在这时才创建f1,以后每次调用get会得到同一个f1对象
B f2=providerB.get(); //在这时创建f2,以后每次调用get会再强制调用Module的Provides方法一次,根据Provides方法具体实现的不同,可能返回跟f2是同一个对象,也可能不是。
}
}
值得注意的是,Provider保证每次重新加载,但是并不意味着每次返回的对象都是不同的。只有Module的Provide方法每次都创建新实例时,Provider每次get()的对象才不相同。
学习资料:
http://blog.piasy.com/2016/04/11/Dagger2-Scope-Instance/
http://www.jianshu.com/p/1d84ba23f4d2
https://github.com/luxiaoming/dagger2Demo
http://www.jianshu.com/p/cd2c1c9f68d4
https://dreamerhome.github.io/2016/07/07/dagger/
http://google.github.io/dagger/