自從 Dagger2 出現(xiàn),個(gè)人對(duì) Dagger2 的學(xué)習(xí)過(guò)程也是斷斷續(xù)續(xù)的,一直沒(méi)有系統(tǒng)的總結(jié)過(guò),所以也談不上掌握,還有就是沒(méi)有投入到項(xiàng)目的使用。
本文的目的:希望能盡量詳細(xì)的整理總結(jié)一下 Dagger2 的相關(guān)知識(shí),盡量避免有所遺漏,造成大家需要查找大量的文章進(jìn)行查漏補(bǔ)缺。以及用由簡(jiǎn)到繁的幾個(gè)例子來(lái)介紹一下如何使用,以方便日后翻閱。
簡(jiǎn)介
Dagger2 是什么?為什么要使用?這也是我接觸 Dagger2 以來(lái)一直有思考的事,在沒(méi)有一個(gè)大概的認(rèn)識(shí)之前,也疑惑過(guò)是不是有必要去使用,但是這里我不會(huì)說(shuō)有沒(méi)有必要,我希望大家能在看完本文之后對(duì) Dagger2 有一個(gè)大概的認(rèn)識(shí)。
A fast dependency injector for Android and Java.
Dagger2 是一個(gè) Android 和 Java 的依賴注入框架。而「依賴注入」(Dependency Injection)則是實(shí)現(xiàn)「控制反轉(zhuǎn)」(Inversion of Control)的一種方式,是一種面向?qū)ο蟮木幊棠J?,主要目的就是為了降低耦合?/p>
Dagger2 和其他依賴注入框架的區(qū)別是:Dagger2 是在編譯階段生成注入代碼,不再使用反射,所以不會(huì)影響到性能。在應(yīng)用運(yùn)行的時(shí)候其實(shí)運(yùn)行的是真正的 Java 代碼,可以直接斷點(diǎn)調(diào)試。
使用前需要了解的原則
依賴倒置原則
- 高層次的模塊不應(yīng)該依賴于低層次的模塊,他們都應(yīng)該依賴于抽象。
- 抽象不應(yīng)該依賴于具體實(shí)現(xiàn),具體實(shí)現(xiàn)應(yīng)該依賴于抽象。
依賴注入的方式
- 構(gòu)造函數(shù)注入
- setter注入
- 接口注入
如果對(duì)這些原則和「控制反轉(zhuǎn)」有所疑問(wèn)的話強(qiáng)烈推薦大家看下下面幾篇文章,文章不長(zhǎng)但是比較益于我們理解。這里也不再舉其他的例子了,感覺(jué)也舉不出更好的例子,但是不建議直接跳過(guò)。
使用 Dagger2 前你必須了解的一些設(shè)計(jì)原則
依賴注入原理
IoC 模式
配置
在 project 的 build.gradle 中添加
dependencies {
//...其他
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'//Android gradle plugin 版本是2.2以上可以省略
}
在 module 的 build.gradle 中添加
apply plugin: 'com.neenbedankt.android-apt'//Android gradle plugin 版本是2.2以上可以省略
dependencies {
compile 'com.google.dagger:dagger:2.10'
apt 'com.google.dagger:dagger-compiler:2.10'//Android gradle plugin 版本是2.2以上,apt 替換成 annotationProcessor
provided 'org.glassfish:javax.annotation:10.0-b28'
}
基本使用
@Inject
該注解是在「需要依賴的類的成員變量」上和「被依賴的成員變量的類的構(gòu)造方法」上標(biāo)注的。讓 Dagger2 知道這個(gè)字段需要依賴注入,這樣它就會(huì)去構(gòu)造一個(gè)類的實(shí)例來(lái)滿足依賴。注意:標(biāo)注的成員變量不能為 private;如果有多個(gè)構(gòu)造函數(shù)只能標(biāo)注一個(gè)。
public class A {
B mB;
public A(){
mB = new B();
}
}
原來(lái)上面的代碼就可以改寫成,下面這樣。
public class A {
@Inject
B mB;
public A(){
//「1」...這里省略了注入的過(guò)程,先跳過(guò)
}
}
public class B {
@Inject
public B() {
}
}
}
單單上邊的改寫其實(shí)是不夠的,因?yàn)槲覀兪÷粤俗⑷氲倪^(guò)程,所以要介紹 @Component。
@Component
該注解是在接口或者抽象類上標(biāo)注的。它的作用就是把「需要被注入的類的實(shí)例」注入到「需要依賴注入的目標(biāo)類」中。
開始編寫Component,來(lái)實(shí)現(xiàn)「1」中省略的注入過(guò)程。
@Component
public interface AComponent {
void inject(A a);
}
//----------分割線-------
//修改 A
public class A {
@Inject
B mB;
public A() {
DaggerAComponent.create().inject(this);
}
}
到這里其實(shí)我們已經(jīng)成功的初始化了 A 中的成員變量 mB 了,有的同學(xué)可能疑問(wèn):我們僅僅是創(chuàng)建了一個(gè)帶 @Component 注解的 interface,這個(gè) DaggerAComponent 是什么?這里的代碼和最初的代碼看上去麻煩了不少,不是用 new B() 顯得更簡(jiǎn)單嘛,而且也并沒(méi)有去除 A 類對(duì) B 類的依賴,說(shuō)好的「依賴倒置原則」呢?
注意: inject(A a) 方法只能傳入「需要注入的目標(biāo)類」,而不能傳入「注入目標(biāo)類的基類」,因?yàn)樽罱K Dagger2 會(huì)在 inject 方法傳入的類中查找 @inject 標(biāo)注的成員變量進(jìn)行初始化。
這里我先回答第一個(gè)問(wèn)題,DaggerAComponent 是在代碼編譯時(shí)期 apt 給我們生成的代碼,所以我們 rebuild 一下項(xiàng)目就會(huì)生成以 Dagger 為前綴的 Component;至于第二個(gè)問(wèn)題就先帶著疑問(wèn)往下看吧!
@Module
被該注解標(biāo)注的類主要用于提供依賴,Module 類其實(shí)就是一個(gè)簡(jiǎn)單工廠,里面的方法基本上就是用于創(chuàng)建類的實(shí)例的方法,Component 可以在指定的 Module 中查找依賴。
單單用 @Inject 配合 @Component 是不夠滿足我們的開發(fā)需求的,因?yàn)椋?/p>
- 我們沒(méi)有辦法給三方類庫(kù)添加 @Inject;
- @Inject 所標(biāo)注的構(gòu)造方法需要我們自己傳入?yún)?shù)的情況;
- 需要被注入的類有多個(gè)構(gòu)造方法的情況。
所以需要用 @Module。
在舉例使用 @Module 之前,我們需要了解下 @Provides。
@Provides
該注解在 Module 中創(chuàng)建類的實(shí)例的方法上標(biāo)注。Component 在注入的目標(biāo)類中找到用 @Inject 標(biāo)注的成員變量后,Component 就會(huì)去 Module 中查找用 @Provides 標(biāo)注的對(duì)應(yīng)的創(chuàng)建類的實(shí)例方法來(lái)實(shí)現(xiàn)注入。
現(xiàn)在就來(lái)舉例講下 @Module 的使用了:
舉例1、對(duì)應(yīng)「注入第三方類」的情況
現(xiàn)在有一個(gè)第三方類 C ,我們同樣需要把 C 注入到 A 中,現(xiàn)在我們繼續(xù)修改代碼。首先我們需要添加一個(gè) AModule 類;
@Module
public class AModule {
@Provides
C provideC(){
return new C();
}
}
然后修改 A 類和之前已經(jīng)定義好的 AComponent;
@Component(modules={AModule.class})//在指定的 AModule 中查找依賴,可以有多個(gè) Module
public interface AComponent {
void inject(A a);
}
//----------分割線-------
//修改 A
public class A {
@Inject
B mB;
@Inject
C mC;
public A() {
DaggerAComponent.create().inject(this);
//DaggerAComponent.builder().aModule(new AModule()).build().inject(this);
//兩種寫法都是可以的,只不過(guò) AModule() 本身不需要參數(shù),所以 create() 中直接 builder().build() 的操作
}
}
舉例2、對(duì)應(yīng)「被注入的類的構(gòu)造方法需要傳參的」的情況
這里又分兩種情況:
- 需要被依賴注入的類的構(gòu)造函數(shù)已經(jīng)用 @Inject 標(biāo)注;
添加帶參構(gòu)造的類 D,修改類 AModule 與類 A,AComponent 保持不變;
public class D {
private String name;
@Inject
public D(String name) {
this.name = name;
}
}
@Component(modules={AModule.class})
public interface AComponent {
void inject(A a);
}
@Module
public class AModule {
private String mDName;
public AModule(String dName){
mDName = dName;
}
@Provides
String provideName(){
return mDName;
}
@Provides
C provideC(){
return new C();
}
}
//----------分割線-------
public class A {
@Inject
B mB;
@Inject
C mC;
@Inject
D mD;
public A() {
DaggerAComponent.builder().aModule(new AModule("I am D")).build().inject(this);
}
}
簡(jiǎn)單分析一下,因?yàn)轭?A 中的成員變量 mD 被 @Inject 標(biāo)注,所以初始化 mD 的時(shí)候會(huì)去找同樣被 @Inject 標(biāo)注的 D 的構(gòu)造方法,但是它需要一個(gè) String 類型的 name 來(lái)初始化,這個(gè) name 從哪里找呢?
其實(shí)就是通過(guò)我們調(diào)用 DaggerAComponent 的 inject(A a) 的時(shí)候傳入的 new AModule("I am D"),然后通過(guò) provideName() 方法暴露出去,這樣 mD 的 name 屬性就被賦值成 "I am D" 了。
因?yàn)?AModule 的構(gòu)造也帶參數(shù),所以生成的 DaggerAComponent 也不再有 create() 方法,只能通過(guò) builder().aModule(new AModule("I am D")).build() 構(gòu)造。
- 需要被依賴注入的類的構(gòu)造函數(shù)沒(méi)有用 @Inject 標(biāo)注;其實(shí)這種情況和「舉例1」的情況一致,只是剛好「舉例1」中的 C 的構(gòu)造函數(shù)不帶參數(shù);這里就簡(jiǎn)單帶過(guò)一下。
稍微在上邊代碼的基礎(chǔ)上修改一下。
public class D {
private String name;
public D(String name) {
this.name = name;
}
}
//------------------分割線--------------
@Module
public class AModule {
private String mDName;
public AModule(String dName){
mDName = dName;
}
@Provides
D provideD(){
return new D(mDName);
}
@Provides
C provideC(){
return new C();
}
}
去掉了類 D 構(gòu)造方法上的注解( 不去掉的話也沒(méi)事 ),在 provideD() 中 new 出實(shí)例 return 出去。
舉例3、對(duì)應(yīng)「被注入的類有多個(gè)構(gòu)造方法的情況」
這里我們不再討論普通的情況,所謂普通的情況就是「舉例2.2」的情況,針對(duì)不同的構(gòu)造函數(shù)不外乎 new 出不同的實(shí)例?,F(xiàn)在我們要討論的是同時(shí)存在 @Inject 和在 provideXX() 方法中 new 對(duì)象的情況。我們重新修改一下上面的代碼。
public class D {
private String name;
public D(String name) {
this.name = name;
}
@Inject
public D(){
this.name = "I am default D";
}
public String getName() {
return name;
}
}
//-------------分割線------------
@Module
public class AModule {
private String mDName;
public AModule(String dName){
mDName = dName;
}
@Provides
D provideD(){
return new D(mDName);
}
@Provides
C provideC(){
return new C();
}
}
//------------分割線--------------
public class A {
@Inject
B mB;
@Inject
C mC;
@Inject
D mD;
public A() {
DaggerAComponent.builder().aModule(new AModule("I am D")).build().inject(this);
Log.d("tag", mD.getName());
}
}
看下上面的代碼,最終我們能打印出來(lái)的是 "I am default D" 還是 "I am D" 呢?答案是后者,這里就涉及到 Dagger2 依賴注入的步驟了,在說(shuō)這個(gè)步驟之前我先做一些其他方面的補(bǔ)充。
舉例4、一些補(bǔ)充
首先、回答一下上面「@Component」一節(jié)中遺留下來(lái)的一個(gè)疑問(wèn):我非要滿足「依賴倒置原則」,高層次模塊不依賴于低層次模塊,而依賴于抽象。其實(shí)也可以實(shí)現(xiàn),定義一個(gè)接口 ID,讓類 D 實(shí)現(xiàn)接口 ID,然后把類 AModule 中的 provideD() 返回值和類 A 中的成員變量 mD 的類型修改為 ID。
其次、在「舉例1」的代碼注釋中 Component 可以有多個(gè) Module,這里提一下兩種方法,注意: 這多個(gè) Module 之間不能有重復(fù)方法。
//第一種
@Component(modules={AModule.class, BModule.class})//有多個(gè) Module
public interface AComponent {
void inject(A a);
}
//第二種 ---------也可以通過(guò)下面這種方式構(gòu)建------------
@Module(includes={AModule.class, BModule.class})
public class CModule {
//...
}
@Component(modules={CModule.class})
public interface AComponent {
void inject(A a);
}
Dagger2 依賴注入的步驟
步驟1:查找 Module 中是否存在創(chuàng)建該類的方法。
步驟2:若存在創(chuàng)建類方法,查看該方法是否存在參數(shù)步驟2.1:若存在參數(shù),則按從 步驟1 開始依次初始化每個(gè)參數(shù)
步驟2.2:若不存在參數(shù),則直接初始化該類實(shí)例,一次依賴注入到此結(jié)束
步驟3:若不存在創(chuàng)建類方法,則查找 @Inject 注解的構(gòu)造函數(shù),看構(gòu)造函數(shù)是否存在參數(shù)
步驟3.1:若存在參數(shù),則從 步驟1 開始依次初始化每個(gè)參數(shù)
步驟3.2:若不存在參數(shù),則直接初始化該類實(shí)例,一次依賴注入到此結(jié)束
不難看到,查找類的實(shí)例的過(guò)程中 Module 的級(jí)別要高于 Inject。
到這里相信大家基本可以使用 @Inject、@Component、@Module、@Provides了,也包括他們?cè)?MVP 中的使用,僅僅需要把類 A 替換成 Activity 或者 Fragment,把類 D 替換成 Presenter,把需要通過(guò) AModule 傳遞給 D(Presenter) 的 String 替換成 IView 之類的 View 層的接口罷了。
完善使用
上面我們僅僅講了四個(gè)注解的用法,當(dāng)然這還遠(yuǎn)遠(yuǎn)不夠,如果累了先休息一下,完了我們?cè)倮^續(xù)介紹。
@Qualifier
該注解是為了實(shí)現(xiàn)類似 @Named 這種用于區(qū)分需要依賴注入相同類型的不同成員變量的功能的注解。簡(jiǎn)單的來(lái)說(shuō) @Named 是 @Qualifier 的一種。
舉個(gè)例子:現(xiàn)在我們需要在類 A 中注入兩個(gè) D 類型的成員變量,一個(gè)要求是默認(rèn)的,另一個(gè)要求我們自己傳入?yún)?shù),下面看具體代碼。
public class D {
private String name;
public D(String name) {
this.name = name;
}
public D(){
this.name = "I am default D";
}
public String getName() {
return name;
}
}
//-------------分割線------------
@Module
public class AModule {
private String mDName;
public AModule(String dName){
mDName = dName;
}
@Named("custom")
@Provides
D provideD(){
return new D(mDName);
}
@Named("default")
@Provides
D provideDefaultD(){
return new D();
}
}
//------------分割線--------------
public class A {
//...省略其他
@Named("default")
@Inject
D mD1;
@Named("custom")
@Inject
D mD2;
public A() {
DaggerAComponent.builder().aModule(new AModule("I am D")).build().inject(this);
Log.d("tag", "mD1:" + mD1.getName() + ”,mD2:" + mD2.getName());
}
}
這里在 A 中的成員變量 mD1 和 mD2 上以及 AModule 中添加的兩個(gè)返回值為 D 類型的 provide 方法上,分別添加了注解 @Named("default") 和 @Named("custom")。不難猜到打印出來(lái)的結(jié)果為:
mD1:I am default D,mD2:I am D
我們看下 @Named 的代碼:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
所以 @Named 后面跟著的參數(shù)為 String 類型,Component 會(huì)在指定的 Module 中尋找同時(shí)滿足類型和 @Named 的依賴進(jìn)行注入。
當(dāng)然我們可以自定義一個(gè) @Num 用 int 類型來(lái)做參數(shù):至于使用方法就不再多說(shuō)了。
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Num {
/** The name. */
int value() default 0;
}
@Qualifier 就是針對(duì)這種有多個(gè)相同類型不同成員變量的注入來(lái)進(jìn)行區(qū)分。
注意:@Qualifier 同樣可以給 Module 的中的方法的參數(shù)標(biāo)注,以區(qū)分一個(gè) Module 中有多個(gè)返回值類型相同的方法,又有另一個(gè)方法需要其中一個(gè)或者多個(gè)的返回值作為參數(shù)的情況。
@Scope
該注解表示作用域的意思,@Singleton 是它 Dagger2 中已有的一個(gè)實(shí)現(xiàn)。使用 @Scope就可以更好的管理創(chuàng)建的類實(shí)例的生命周期。
既然看到了 @Singleton 那么來(lái)實(shí)現(xiàn)一個(gè)單例吧!
目標(biāo):
我要實(shí)現(xiàn)在兩個(gè) Activity 中實(shí)現(xiàn)單例。
使用方法:
- 在 Module 對(duì)應(yīng)的 Provides 方法標(biāo)注 @Singleton
- 同時(shí)在 Component 類標(biāo)注 @Singleton
public class ActivitySingle {
}
//--------------分割線------------
@Module
public class SingleModule {
@Singleton
@Provides
ActivitySingle provideActivitySingle(){
return new ActivitySingle();
}
//--------------分割線------------
@Singleton
@Component(modules = {SingleModule.class})
public interface SingleComponent {
void inject(SingleOneActivity activity);
void inject(SingleTwoActivity activity);
}
//--------------分割線------------
public class SingleOneActivity extends AppCompatActivity {
private TextView mTv1;
private TextView mTv2;
@Inject
ActivitySingle mActivitySingle1;
@Inject
ActivitySingle mActivitySingle2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single);
mTv1 = (TextView) findViewById(R.id.tv1);
mTv2 = (TextView) findViewById(R.id.tv2);
DaggerSingleComponent.create().inject(this);
mTv1.setText(mActivitySingle1.toString());
mTv2.setText(mActivitySingle2.toString());
}
}
//SingleTwoActivity 和 SingleOneActivity 除了類名完全一致
運(yùn)行結(jié)果:


注意:這和說(shuō)好的單例不一樣!我們可以看到在同一個(gè) Activity 中我們實(shí)例化的 ActivitySingle 對(duì)象的確是單例的,但是這不是我們要的結(jié)果??梢钥吹讲⒉皇俏覀儤?biāo)注了 @Singleton Dagger2 就給我們實(shí)現(xiàn)了單例了的。
Java 中,單例通常保存在靜態(tài)域中,這樣的單例往往要等到虛擬機(jī)關(guān)閉時(shí)候,該單例所占用的資源才釋放。但是,Dagger 通過(guò) @Singleton 創(chuàng)建出來(lái)的單例并不保持在靜態(tài)域上,而是保留在Component 實(shí)例中。
原理
在講「Dagger2 依賴注入的步驟」中我們知道 Component 查找類的實(shí)例的過(guò)程中 Module 的級(jí)別要高于 Inject。
真正創(chuàng)建單例的方法
- 在 AppModule 中定義創(chuàng)建全局類實(shí)例的方法;
- 創(chuàng)建 AppComponent 來(lái)管理 AppModule;
- 保證 AppComponent 只有一個(gè)實(shí)例;
簡(jiǎn)單的來(lái)說(shuō)就是把上例的 SingleComponent 提升到 Application 中管理并保證只有一個(gè)?,F(xiàn)在有沒(méi)有體會(huì)到 @Scope 的作用域的含義?
具體實(shí)現(xiàn),后續(xù)我會(huì)拿一個(gè)貼近實(shí)際使用的例子來(lái)講,這次就先到這里了,因?yàn)楹罄m(xù)還會(huì)牽扯到 Component 之間的拓展和依賴關(guān)系以及組織 Component 篇幅會(huì)顯得比較長(zhǎng),但是我會(huì)保證在兩篇文章把 Dagger2 整理完。當(dāng)然這里 @Scope 并沒(méi)有講完,我會(huì)后續(xù)結(jié)合例子補(bǔ)充完整。
其實(shí)主要還是靠大家自己動(dòng)手體會(huì),如果有寫的不對(duì)的地方請(qǐng)?zhí)嵝盐?,避免誤導(dǎo)了后來(lái)的同學(xué),如果覺(jué)得我整理的還不錯(cuò)請(qǐng)為我點(diǎn)個(gè)贊,謝謝!
更新
參考資料
等不及的同學(xué)可以看下下面的文章,盡量按順序來(lái)看。
使用 Dagger2 前你必須了解的一些設(shè)計(jì)原則
依賴注入原理
IoC 模式
Android:dagger2 讓你愛(ài)不釋手-基礎(chǔ)依賴注入框架篇
Android:dagger2 讓你愛(ài)不釋手-重點(diǎn)概念講解、融合篇
Android:dagger2 讓你愛(ài)不釋手-終結(jié)篇
從零開始搭建一個(gè)項(xiàng)目(rxJava + Retrofit + Dagger2) --完結(jié)篇
Android 常用開源工具(1)-Dagger2 入門
Android 常用開源工具(2)-Dagger2 進(jìn)階
詳解 Dagger2
Android 單排上王者系列之 Dagger2 注入原理解析