Dagger2是啥?
继续来学习IOC思想的一个新框架---Dagger,说实话这个框架还木有在工作项目中用过到,但是它在大型项目中用它的还是很常见的,项目越大越能体现它的好处,核心好处就是能够解耦,所以接下来好好把这个框架的使用给了解了,待未来在项目中看到它的用法也能很读懂,先来上官网了解一下它:https://github.com/google/dagger
貌似跟ButterKnife的思想类似,都是在编译期来生成Java代码,很显然只是编译受点影响,完全不影响程序的运行性能了。
另外它还有一个官网:https://dagger.dev,其中有一条:
所以可以看出此技术的重要性。
为啥要用Dagger2?
在正式学习使用它之前,得要知道这个框架解决的是啥问题,这样才能在实际场景中去学以致用,下面举个场景来描述一下:
比如我们要操作数据库时肯定是会用到数据库的一些封装工具对象,伪代码如下:
package com.android.dagger2study; import android.database.sqlite.SQLiteDatabase; public class DatabaseObject { private static DatabaseObject instance; private SQLiteDatabase mdb; public static synchronized DatabaseObject getInstance() { if (instance == null) { instance = new DatabaseObject(); } return instance; } public synchronized SQLiteDatabase openDatabase() { //todo:init SQLiteDatabase... return mdb; } public synchronized void closeDatabase() { if (mdb != null) mdb.close(); } }
然后在Activity中来初始化它:
貌似这代码很常见木有生病嘛,但是如果此项目是一个非常之庞大的,比如支付宝,京东这种,成千上万个地方都需要操作数据库,那是不是DatabaseObject.getInstance()得要写成千上万次?你的回答肯定说:“是呀,那这有啥问题么?”,那如果在某个未来的结点不得已需要对这个getInsance()加个参数:
此时是不是成千上万处代码都像这样了:
然后咱们就硬着头皮开始一个个手动改:
而大项目通常都会使用组件化的开发,可以一个工程有大量的module,所以此时你就得各个module将报错的都手工的改了,而如果使用Dagger2这种依赖注入的框架之后,只需改一处就能达到类似的效果,也就是将强编码生成对象的这句代码给通过依赖注入的方式进行对象的注入了,使用方式就可能是成这样了:
貌似使用的思想还是好像ButterKnife呀~~以上就是为啥Dagger2解决的问题,很明显是解耦了,另外也体现出来项目越大它的价值越大。
简单使用:
那具体Dagger2是怎么使用的呢?下面看个入门的例子感受一下,咱们就以注入DatabaseObject为例,先来新建一个包,里面存放的都是依赖注入的代码:
然后依赖一下Dagger2:
接下来则会新建若干个你觉得一脸懵逼对象,不要紧,对于入门的例子先感受一下既可,细节暂且先不用管,里面的原理啥的会慢慢再来剖析,前方高能开始:
接下来则在di中建立一下跟这个对象对应的module对象,此module非Android Studio的组件概念中的module哈,为啥要建它莫要管,用就行了:
@Module:
然后增加一个Dagger2的这个注解:
看一下此注解的解释:
其中提到了一个“object graph”,google 一下:
简单理解:
@Provides
既然这个类是用来提供对象的,那里面定义一个提供对象的方法:
但是此方法是不能被Dagger识别是用来提供对像的,需要加一个注解:
也来读一下它的官方说明:
其中提到了一个component,下面就需要来创建它了。
@Component:
此时还是得要用注解来标注一下:
那此时也来看一下它官方的说明:
其中有一个modules()的数组属性,咱们也来读一下:
也就是咱们这里面得将我们所有的module,所以咱们定义一下,目前就只有一个module:
接下来它里面还需要定义一下注入方法:
表示我们是要往MainActivity这个类来注入代码,注意,这个参数不能用多态,比如说定义一个父类之类的,是注入哪个类就得写这个类。
生成注入代码实现类:
此时一切就绪,接下来则需要先编译一下,很明显这里用到了注解处理器的技术了,先生成注入的具体实现类,编译一下:
再来编译:
使用注入:
接下来咱们就可以来使用了,如下:
运行:
妥妥的,这样就通过Dagger来实现了对象的注入了,那有啥好处呢?假如这个对象的构造方法要进行变动,则只要改一处既可,如下:
应用层一点都不用动,足以体现出此框架的好处了。 24:31
再来复习下用法:
这里再来建一个对像,比如说要使用网络进行请求,则:
接着来定义Module,它是用来提供对象的:
接着在Component中来对我们的module注册一下:
Dagger2的关键概念梳理:
https://www.jianshu.com/p/2cd491f0da01 描述了整个查找的过程
对于上面的Dagger2基本使用中涉及到了@Inject、@Component, @Module, @Provides这几个东东,那如何来理解它们之间的关系呢?这里借助于https://www.jianshu.com/p/2cd491f0da01这位博主的一个图解来理解一下,我觉得很清晰的表述出了之间的关系,如下:
另外还有一篇http://xzane.cc/2016/01/22/DI%E7%90%86%E8%A7%A3%E4%BB%A5%E5%8F%8ADagger%E7%94%9F%E6%88%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/博主用了一个非常形像的例子来理解它们之间的关系,如下:
用上面的这些描述来理解我觉得应该是比较清晰了。
注入原理剖析:
对于整个对像的注入过程又是咋样的呢?接下来咱们来分析一下源码:
那中builder()方法则是新建一个Builder对像:
此时再看一下它的build()方法:
好,此时再回到主流程继续:
其实它的实现类也是通过注解生成器帮生成的,如下:
所以跟进去看一下细节:
咱们挑一个对像的生成来看既可:
它又是一个接口,其实它的实现也是通过注解生成器生成的,如下:
咱们分析的是数据库的对像,则进数据库对象工厂看一下:
最终:
其实注入过程还是很好理解的。
Dagger2的进一步深入:
对于Dagger2的基础用法已经掌握了,其实它还有其它的一些用法,也有一些坑,所以接下来带着问题进一步的学习它。
@Singleton:
假如说咱们多次注入同一个对像会是单例的么,如下:
很显然是的,因为创建对像其实就是调用的单例对象:
验证一下:
但是!!如果咱们使用new来创建对像呢?
再运行:
其实这里可以使用Singleton注解来达到单例的效果,有几处都得加,如下:
但是!!它有坑,咱们再新建一个Activity,然后再来注入这个所谓已经成为“单例”的对像,再来看一下:
此时运行看一下:
这又是为啥呢?咱们分析一下关键代码,主流程其实跟之前分析的差不多,只是有些细微的差别:
而DoubleCheck也是一个Provider:
所以注入时:
它就会转到DoubleCheck.get()上来,跟一下它的实现细节:
但为啥新起了个Activity对像就不成单例了呢?其实是由于Component对像新生成了,如下:
Component都不一样,那里面的provider肯定也是新的了,所以单例就失效了。。那只要让咱们的Component在全局唯一不就可以变为单例了么?所以咱们可以将它提到Application当中,下面来试一下:
运行看一下:
多个Component的坑!
dependencies:
在实际项目中不可能只有一个Component对象,可能会分业务建立不同的Component的,比如咱们要在Activity中注入MVP的Presenter对象,将它定义在一个全新的Componet上,下面按之前的步骤来定义一下:
很easy嘛,接下来编译一下,报错了。。
这是为啥?因为一个Activity对应一个Component,在注解编译时会生成Dagger开头的一个Component文件,而目前是一个Activity对应两个Component了,所以此时编译就出错了,那怎么整?其实在Component的@Component注解还有另一个属性,如下:
可就是既然一个Activity只能对应一个Component,那如果将PresenterComponent依附于MyComponent,也就是相当于作为MyComponent的孩子,那对于Activity而言不还是只对应一个Component的嘛,所以,可以这样改装一下:
然后依赖的这个Component就不能提供注入的方法了,得要改成一个普通的方法,如下:
此时再编译一下:
在初始化Component的时还是报错了。。此时需要使用Builder的方式来构建了:
好,再来运行看一下:
这样就解决了多个Component的问题了,但是!!!还有坑,唉,说实话这玩意如果没研究过还真不敢真实将其用到项目中,坑也太多了。好下面来看一下这个坑:
多Component的单例问题:
我们让PresenterObject也设置成单例的,根据之前变单例的步骤咱们得要在它的Module和Component上加上@Singleton的注释了:
再来编译一下,又报错了。。
也就是说这个@Singleton的组件是不能依赖于一个scoped的组件,什么是scoped的组件呢?其实它是定义在这个Singleton的注解之上的:
啥意思?其实是这个意思,因为我们的MyComponent已经被@Singleton修饰了,而PresenterComponent作为它的孩子,又被标志了这个@Singleton,很明显是@Singleton里面又嵌套有一个scoped(因为@Singleton上面又标有@Scope注解)的组件,所以此时就如错误提示那样所说的情况了。那如何解决这个问题呢?实际肯定会要有多个Component需要支持单例的,此时就需要丢弃掉这个@Singleton注解来实现单例了,因为如果是通过这个实现单例有N多条规则,比较麻烦,这里改用一个自定义的注解比较简单的就能实现这样的单例的问题,同时也只要记住两个原则就可以了,下面看下如何来改装:
首先创建一个注解,然后上面的内容完全校仿@Singleton的来,如下:
然后将原来用@Singleton注解的替换成我们新建的这个注解:
同样的,MyComponent上的@Singleton注解也来替换一下:
然后对于依赖的PresenterComponent也想要单例,此时则需要再建立一个注解,它不能跟MyComponent的@AppScope同名既可,所以:
将其注解到Presenter的Module和Component上:
此时咱们再来编译运行一下:
妥妥的,为了验证我们的PresenterObject是单例,咱们在第二个页面中再来注入一个它,然后跟前一个对比看是否是同一个对像:
所以对于有依赖的Component的单例只要遵循如下两个原则肯定就不会出错,如下:
1、多个组件之间的scope不能相同。
就像上面的MyComponent和PreseneterComponent的注解是不同的,如果相同则会报错。
2、没有scope的不能依赖有scope的组件。
也就是说如果MyComponent上木有scope的注解修饰,那么它依赖的PreseneterComponent上面也不能有scope的注解修饰。
@Subcomponent的坑:
组件与组件之间除了依赖关系,还有一种子组件的关系,所以接下来看一下它的使用,咱们还是将PresenterComponent作为MyComponent的子组件,为了说明这个问题,这里将工程的代码简化一下:
scope和object都跟之前一样,这里先来定义HttpComponent:
接着来定义子组件了:
此时则有注入方法了,也就是将注入方法从主移到了子:
好,接下来编译一下,然后修改一下MyApplication的代码,此时则需要拿到子组件的引用了:
最后在每个Activity中进行注入,只需修改注入方法既可,其它代码都还是保持原样就成:
运行:
貌似妥妥的,但是它还是有坑的,下面演示一下,对于HttpModule来说可能会有多个BaseUrl的情况,所以这里模仿一下定义2个provider来对应不同的baseurl的对象,如下:
下面来调用一下:
那接下来对于注入的这俩个HttpObject它们用的baseUrl倒底是对应哪一个呢?此时就需要再加一个注解来指明一下:
此时运行:
好,这个主Component的Module可以传参,但是对于子组件的PresenterModule呢?这里是没法传参的,为啥?咱们可以分析一下源码就晓得了:
对于这种@Subcomponent的方式还是不太推荐使用,对于多个Component还是建议用dependencies的方式,为啥?因为dependencies的方式对于Module的传参都支持,如:
关于Dagger2的研究就到这了,算是一个入门吧,还是非常之晕的,对于小项目确实是没必要用它,为了解耦多出了N多个类,但是对于超大型的项目牺牲一点点类来提高代码的解耦其价值还是非常高的。 期待之后有实际项目中会用到它,把这些核心的掌握了的话对于它的使用基本上也很简单的。