Dagger2实现原理分析

Dagger是一个依赖注入框架,这个框架往往给人一种“高端,但是却很难用“的感觉。我们在使用或者阅读别人的项目时,往往会陷入dagger搭建的”迷宫里”绕不出来。导致Dagger被认为是android史上”最受冷落的优质库”。

这里主要一方面是由于,Dagger本身功能的复杂和太灵活导致使用起来比较困难。还有一方面原因是对Dagger的定位和依赖注入的概念比较模糊。

本文先通过理清依赖注入的概念,然后详细介绍Dagger一些常见的使用场景和注意事项,最后介绍一下Dagger主要功能实现的原理。帮助大家进行理解。

一、依赖注入

我们知道在一个类中,通常会定义其他类型的变量,这个变量就是我们所说的“依赖“。

对一个类的变量进行初始化,有两种方式。第一种,这个类自己进行初始化;第二种,其他外部的类帮你进行初始化。

其中第二种方式,由外部类进行初始化的方式就是我们所说的”依赖注入“。由于他是由外部来控制,因此又叫做”控制反转“。依赖注入和非依赖注入的区别就是,变量初始化工作是由谁来做的。我们常用的工厂、建造者、带参数的构造函数,都是依赖注入。

因此,实际上我们一直都在使用“依赖依赖“,对它其实并不陌生。而Dagger的定位就是提供了用注解的方式来配置依赖的方法。因此Dagger并不是提供依赖注入的能力,而是为依赖注入提供一种更简单的方法

二、Dagger2的使用

1、耦合

先看一个没有使用Dagger的一个简单的示例。   

上面是一个非常简单框架场景,RequestManager中持有RemoteService和LocalService可以分别进行本地和远端的操作,我们假设提供了保存数据的方法。假设此时要将本地数据保存到SharedPreferences中,我们知道使用SP则需要用到上下文的Context信息。

我们往往把Context从最顶层的调用者Activity,通过RequestManager传到LocalService。上面代码可能会变成下面这样。一次性要改动三个地方。可谓牵一发而动全身。此时,我们说它产生了较高的耦合

 

当然,我们可以通过在RequestManager接收LocaService和RemoteService类型的参数,并且在MainAcitivty创建好之后传给它。这样来减少耦合。

 我们来看一下使用Dagger2的方案。简单介绍一下下面的代码,SaveDataModule负责提供组件,SavaDataComponet负责连接组件提供者和使用,调用它的inject的方法,将组件注入到RequestManager中。以此来达到组件使用者和组件之间的解耦。

 2、Dagger2主要结构

       上面的例子中,我们看到Dagger2主要用法,这里涉及到三个关键的组件Inject、Module和Component。它们分别表示,组件接收(注入)的位置、组件提供者和连接它们的桥梁。

       简单来说,就是Component将Module提供的组件注入到接收的位置。它是基于注解处理器实现的,通过注解处理器对我们注解的部分,生成中间代码。文章的最后我们看一下注解处理器帮我们生成的代码,了解一下Dagger2实现的原理。

其他还有@Provider注解,表示这是一个提供组件的方法。通过对比它的返回值类型和接受处参数的类型,来进行匹配。如果出现提供多个相同返回类型的方法,通过@Name注解进行区分,这个注解后面进行讲解。

3、组件依赖参数的方式

       上面我们的示例,我们看到向组件传递参数的第一种方式,在Module中进行提供。另外还有一种方式,用@Inject注解修饰需要进行依赖的参数

       现在对RemoteService提供一个Url信息,用于进行网络访问。增加一个提供Url的组件—UriModule,提供Url的方法用@Provider注解。将这个组件一起关联到我们的Component。用@Inject在需要用到这个参数的方法上进行注解。这个信息就会被注入到方法的参数位置。

 

4、多模块间引入

在上面的示例中我们在SavaDataModule中使用了UriModule,既是模块间的引入。除了上面给出的在Component中指定多个模块可以相互引入之外,还有两种模块间引入的方式:在自己模块中直接引入和通过其他Component引入。分别来看一下。

直接引入

将上面UriModule改为在SaveDataModule中直接引入。

通过Component引入

将目标Module引入一个新的Component,并在我们的Component中依赖该Component。改动如下,其他不变。

 5、返回相同类型不同实例

       在2小结中提到,如果多个Provider提供相同的返回值如何进行区分?这里使用@Named进行注解。

       比如,我们需要根据开发版本或者发布版本返回不同实例。只需要在Provider的位置用@Named注解提供不同名称进行区分。在使用时(注入的位置)也用同样的@Named就可以准确的进行注入。

受@Named注解启发,我们可以参考@Namede 注解,自定义自己的注解,进行更优雅的实现。

@Named注解,有三个元注解,其中@Qualifier用来区分不同对象实例。(关于注解有不太了解的,可以看一下我的博客“Java高级语言特性—注解”Java高级语言特性 ---- 注解_源码级注解对ide检查_风行水上_ZH的博客-CSDN博客)。

仿照它我们自定义一个Release注解和一个Dev注解。

 

在提供组件的位置和注入组件的位置,分别用我们定义的注解进行修饰,即可完成准确的注入。

6、单例

       Dagger2中使用@Singleton来实现提供者每次返回的都是同一个对象,就是我们说的单例。写法下面这样,需要注意的是,在@Providers和@Component注解位置都需要写上@Singleton注解,才能生效。

我们知道java中的单例,是一种“全局的“,在整个jvm虚拟机中只有一个实例。需要注意的是,Dagger2中的单例不同,它是一种“有范围的单例”。上面用Singleton修饰的单例。我们增加一个SaveDataComponent2同样关联SaveDataMoule,将它注入到RequestManager2对象中。此时在RequestManager2中注入的LocalService和RequestManager中注入的LocalService将是不同的对象。

                               

Dagger2中实现全局单例的做法通常是,提供一个全局的单例Component,在Application进行初始化,再提供给其他的Component使用。

定义一个用Singleton修饰的组件,关联一个提供单例对象的模块。

 在Application中进行初始化,并提供返回的方法。

在其他组件中进行依赖,并在对组件进行初始化时,通过Appliction获取该单例组件对依赖组件进行初始化。此时就是全局单例的。

7、Scope

       上面的小结中,我们看到在SaveDataComponent的注解上我们使用了@ActivityScope。这个就是Scope,它表示组件生效的范围。比如,这个组件负责一个Activity的注入,当这个Activity销毁时,它所关联的Module以及Molude中提供的实例,都会等待被销毁或者被销毁。

       这里的@ActivityScope是我们自己定义的Scope。Dagger2只定义了一种Scope,就是@Singleton。Dagger2的约束中,要求“Component的dependices与Component自身的Scope不能相同”,并且“没有Scope的Component不能依赖有Scope的Component”。

       上面的示例,因为AppComponent使用了Singleton,因此在SaveDataComponent我们需要使用一个不同的Scope,因此自定义了一个ActivityScope。

       这里定义成其他的Scope的名称也是可以的。这个地方命名为ActivityScope只是一种推荐的实践。

       Scope推荐实践主要用三种Singleton、ActivityScope 、UserScope。实现三种范围Scope的定义。Singleton用于Application,ActivityScope 用于Activity,UserScope用于其他,如class等。

8、Lazy与Provider

这两个工具是Dagger2两种特定的提供示例的方式。Lazy和Provider都是“懒加载”的实现方式,即在第一次获取实例时,才进行实例的创建。不同的是,Lazy后续获取对象都是同一个,而Provider每次调用都会调用Provider方法,是否返回同一个实例,根据Provider的具体实现来。

这个是Dagger2中比较好理解的内容。我们直接看一下下面的示例。

9、Dagger2注意事项总结

       通过上面的介绍,我们看到Dagger2的使用相当的灵活,这也是导致它比较难用的原因之一。针对上面的案例,给出一些Dagger2使用中的注意事项,帮助大家更好的使用。有的在上面示例中没有体现的,请读者自行理解。

       1)Inject注入没有“子类是父类”的继承关系。如果Component的Inject方法接收父类型参数,调用时传递的是子类类型,则无法注入。(编译可以通过)。

2)Component所关联的所有Module中不能有提供相同示例的Provide,需要用Named属性进行区分。

3)Module的Provide使用了Scope,则使用它的Component必须使用相同的注解。

4)Module的Provide没有使用Scope,与它相关的Component、Module可以加也可以不加Scope。

5)Component的dependencies与Component自身的Scope不能相同。

6)Singleton Scope修饰的组件不能依赖其他Scope组件,只能其他Scope组件依赖Singleton的组件。

7)没有Scope修饰的Component不能依赖有Scope修饰的Component。

8)一个Component不能有多个Scope。

三、Dagger主要实现原理

前面说到Dagger2是用注解的方式,简化了依赖注入的实现。它是基注解处理器生成的代码。

这一章节我们看一些简单场景下,注解处理器为我们生成的代码。理解一下Dagger2的实现原理。

看一下注解处理器生成的代码。这段代码主要在工程下的build/generated/ap_generated_sources目录下。

针对这个示例,它主要会生成DaggerUserComponent、MainActivity_MembersInjector、UserModule_ProviderUserFactory这三个类。看下他们的代码,看上去比较多,实际也不复杂。

DaggerUserComponent是核心的类,通过他的build方法,创建Module的实例。一手拿着MainActivity_MembersInjector,一手拿着UserModule_ProviderUserFactory。UserModule_ProviderUserFactory是提供实例的地方,MainActivity_MembersInjector是注入实例的位置。将从提供实例处获取的实例,注入到目标位置。

提供实例的地方,可以看到,就是调用了我们在Module中写的Provider方法返回一个实例。

注入的方式也很简单,直接调用”instance.user = user”,进行赋值。

单例注入,增加Singleton Scope修饰之后,DaggerUserComponent增加了initiallize方法,在这个方法中,会将Module实例放到Provider中。注入时候也从Provider获取进行注入,由此每次得到的实例都是同一个,即是单例。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值