Android_Dagger2篇——从小白最易上手的角度 + 最新dagger.android

·  阅读 1105
原文链接: mp.weixin.qq.com

本文是来自神秘组织的「鱼满楼」的投稿,讲述了他对 Dagger2 的独家理解,个人觉得很不错,推荐阅读。别问我神秘组织是什么,我只知道叫「gsd」。

正文

Dagger2 ,这枚大名鼎鼎的匕首,相对于 Rxjava、Retrofit、 Okhttp 等可能是最难上手的的框架了;许多人都学习它不下数遍,网上的教程也都是从入门到放弃、再从入门到放弃,如此反复才最终恍然大悟;这其中也有许多人看了一两遍之后对他望而却步。

早就听说 Dagger 的大名,Dagger 是安卓界最棒的依赖注入框架,第一代由大名鼎鼎的 Square 公司共享出来,第二代则是由谷歌接手后推出的。我在 2016 年 10 月份最初接触它,也是如此数遍、断断续续历经数月最终在家过 2017 年春节的时候恍然大悟(当然此情况不代表全部人,科班出身本身就接触过依赖注入思想的肯定例外,我的朋友中就有看了一遍就会用的才子;所以大家不要喷笔者笨)。

很多小白对于 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,不然想一口吃成胖子容易噎住)

- 项目添加 dagger2 支持;

- 最简单不带 Module 的 Inject 方式;

- 带 Module Inject 方式;

Component 依赖 Component(a、dependence方式;b1、subComponent方式);

     

Scope 作用域——单例;

- 总结;

- 扩展的dagger.android最新api。

项目添加 dagger2 支持

使用 dagger2 的前提,需要先在 app 的 build.gradle 中的 dependencies 代码块里添加如下依赖:

(最新的版本号请浏览 dagger2 的 GitHub 主页中的 releases 详情 https://github.com/google/dagger)

添加 dagger2 支持

最简单不带Module的Inject方式

由我们自己定义的类,我们可以自由修改的情况下我们使用这种方式,也分为两种:带参数和不带参数的。

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 使用的。

带Module的Inject方式

直接给构造函数添加 @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 里进行管理,因为这样代码更加容易管理。 

3、Component 依赖 Component

当我们其中一个 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 的方法;

Scope 作用域——单例

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 的类型。

扩展的 dagger.android 最新 api

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 等实现类,有兴趣的小伙伴自己研究一下吧。

   长按上方二维码关注

做不完的开源,写不完的矫情

一起来看 nanchen 的成长笔记

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改