Dagger2 菜鳥(niǎo)入門(mén)

如果你對(duì) Dagger2 和 依賴注入 都完全沒(méi)有概念,也沒(méi)關(guān)系,這篇文章會(huì)從最簡(jiǎn)單的概念開(kāi)始,教你如何上手 Dagger2

從 Dagger1 到 Dagger2, 這個(gè)依賴注入框架已經(jīng)火了很久了。然而其涉及的一些概念不是那么容易理解,導(dǎo)致其不是很好上手。在參考了多篇大牛的文章(見(jiàn)文末)后,我總算基本弄懂了這個(gè)框架,所以趕緊記錄下來(lái)。如果有理解出現(xiàn)偏差的地方,懇請(qǐng)及時(shí)的指出。

0. Dagger2 和 依賴注入

這里不擺長(zhǎng)篇大論的定義,只用最簡(jiǎn)單的方法來(lái)說(shuō)清楚基本概念
Dagger2 是什么?
Dagger2 是一個(gè)依賴注入框架

什么是依賴注入?
當(dāng) A 類中包含一個(gè)屬性 B 類,就可以說(shuō) A 對(duì) B 產(chǎn)生了 依賴。
當(dāng)實(shí)例化一個(gè)對(duì)象的時(shí)候,不再需要 new 出一個(gè)實(shí)例,而是由框架自動(dòng)的幫你生成一個(gè)實(shí)例,這就是 注入。
為了實(shí)現(xiàn)自動(dòng)化的注入,需要前期的一些配置工作,后面詳細(xì)說(shuō)。

依賴注入到底有什么好處?
簡(jiǎn)單的說(shuō),就是 將類的初始化以及類之間的依賴關(guān)系集中在一處處理。你修改了一個(gè)類的構(gòu)造方法,那就要在所有調(diào)用該構(gòu)造函數(shù)的地方修改。如果使用了依賴注入框架,類的構(gòu)造都集中在一處,可以很方便的進(jìn)行更改而不影響其他部分的代碼。

依賴注入的優(yōu)勢(shì)在較小的項(xiàng)目中體現(xiàn)的不明顯,甚至?xí)驗(yàn)楸容^復(fù)雜的配置,以及反直覺(jué)的對(duì)象初始化方式而讓人望而卻步。但是在復(fù)雜的項(xiàng)目中,有大量類的初始化,以及類之間的依賴關(guān)系很復(fù)雜的時(shí)候,依賴注入就會(huì)變的十分必要。其實(shí)工廠方法也是為了解決同樣的問(wèn)題,只不過(guò)要寫(xiě)大量的 boilerplate code,而 Dagger2 只需要進(jìn)行一些配置之后,就可以在編譯后自動(dòng)的生成代碼,相比工廠方法更加智能。

1. 在 Android Studio 中配置 Dagger2

Dagger2 是一個(gè)通用的 java 庫(kù),并不只適用于 Android,這里僅以 Studio 下的 Android 開(kāi)發(fā)示例。
在項(xiàng)目的根 build.gradle 里添加:

buildscript {
    ...
    dependencies {
        ... 
        //編譯時(shí)處理注解的插件,生成代碼就靠它
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

appbuild.gradle 里添加:

apply plugin : 'com.neenbedankt.android-apt'
...

dependecies{
  ...

  //dagger2 依賴庫(kù)
  compile 'com.google.dagger:dagger:2.2' 
  provided 'com.google.dagger:dagger-compiler:2.2'

  //dagger2 中用到幾個(gè) java sdk 里的注解 @Inject,@Singleton
  provided 'org.glassfish:javax.annotation:10.0-b28'   
}

2. Dagger2 中的注解

Dagger2 使用注解來(lái)配置對(duì)象之間的依賴關(guān)系??梢园褜?duì)象之間的互相依賴想象成一個(gè)有向無(wú)環(huán)圖,而在代碼中使用這些注解,就是為了來(lái)描述這個(gè)圖。其實(shí)理論上,描述依賴關(guān)系的方法還可以有很多,比如用一個(gè)配置文件來(lái)描述(Spring 框架),只不過(guò)直接在代碼中用注解,更加的直接清晰。

理解 Dagger2 中的幾個(gè)注解的含義和用法,是掌握 Dagger2 的關(guān)鍵,下面一個(gè)一個(gè)的分析。

2.1 @Module

注解一個(gè)類,這個(gè)類用來(lái)提供依賴,或者通俗的說(shuō),完成各種對(duì)象的實(shí)例化 的一個(gè)集合。類里面可以包含很多 @Provides 注解的方法,每個(gè)方法對(duì)應(yīng)一種對(duì)象的創(chuàng)建。

2.2 @Provides

注解 Module 類里面的方法,Dagger 會(huì)在需要?jiǎng)?chuàng)建實(shí)例的時(shí)候找到這個(gè)方法并調(diào)用,完成對(duì)象的實(shí)例化。

這樣說(shuō)可能還是太抽象,看一個(gè)實(shí)例:

@Module
public class AppModule {
    private final MyApplication mApplication;

    public AppModule(MyApplication application) {
        this.mApplication = application;
    }

    @Provides @Singleton
    MyApplication provideApplication() {
        return mApplication; 
    }
}

去掉注解,AppModule 就是一個(gè)普通的類,@Module 注解的含義很簡(jiǎn)單,它就是告訴 Dagger 框架,這個(gè)類里面有可以提供對(duì)象創(chuàng)建的方法。所以 Module 類可以理解成一個(gè)用來(lái)組織對(duì)象創(chuàng)建方法的容器,關(guān)鍵是其內(nèi)部的方法。

2.3 @Inject

當(dāng)注解一個(gè)屬性的時(shí)候,表示該屬性需要依賴(需要被注入一個(gè)對(duì)象)。
當(dāng)注解一個(gè)構(gòu)造函數(shù)的時(shí)候,表示該構(gòu)造函數(shù)可以提供依賴。需要注意的是,如果被 @Inject 注解的構(gòu)造函數(shù)是帶參數(shù)的,比如這樣:

public class AClass{
    @Inject
    public AClass(BClass bClass){
        //do something
    }  
}

那么 Dagger 框架會(huì)在編譯期檢查, 是否在 Module類 中有返回值是 BClass 的方法,或者 BClass 是否有被 @Inject 注解的構(gòu)造函數(shù)。如果未能找到,就會(huì)在編譯期報(bào)錯(cuò)。這就在編譯期確保了對(duì)象之間的依賴一定會(huì)被滿足

2.4 @Component

前面說(shuō)了 @Module 提供依賴, @Inject請(qǐng)求依賴,而@Component 就是聯(lián)系這兩者的紐帶。
Component 主要說(shuō)明4件事:

  • 誰(shuí)來(lái)提供依賴
  • 該 Component 依賴哪些其他的 Component
  • 該 Component 為誰(shuí)提供依賴注入
  • 該 Component 可以提供那些依賴
    @Component 注解的是一個(gè)接口,比如下面這個(gè)接口,表示它可以提供一個(gè) MyApplication 類型的依賴。
@Singleton
@Component(modules = {AppModule.class},dependencies = {xxxComponent.class})
public interface AppComponent{
    void inject(MyActivity myActivity);
    public MyApplication getApplication();
}

對(duì)應(yīng)前面說(shuō)的4點(diǎn):

  • module 參數(shù){}里的類就表示提供依賴的Module類(可以有多個(gè)),也就是用 @Module 注解的類
  • dependencies 表示該 Component 提供的注入類的構(gòu)造函數(shù)中,還依賴其他 @Component 提供的一些類。
  • 有參無(wú)反的方法指明該 Component 注入的目標(biāo),比如本例中,就說(shuō)明 MyActivity 中需要 AppComponent 提供的依賴
  • 有反無(wú)參的方法指明該 Component 可以提供哪些依賴,或者說(shuō)暴露哪些依賴。因?yàn)檫@里可能會(huì)有疑問(wèn),Component 可提供的依賴不就是 Module 里的那些嗎?確實(shí)沒(méi)錯(cuò),但是 Component 也可以有選擇的只對(duì)外界暴露它的一部分能力,并不一定會(huì)聲明所有的在 Module 里定義的類。

這個(gè)接口可以理解成一份聲明,告訴Dagger哪些對(duì)象可以被注入,以及誰(shuí)(Module類中被@Provides注解的方法)來(lái)提供這些依賴。需要注意,只有在 Component 接口中聲明了的類,才會(huì)被暴露給依賴它的其他 Component。也就是說(shuō),Module 類中提供的依賴,并不一定都會(huì)在 Component 中聲明。

最后,這個(gè)接口的實(shí)現(xiàn)會(huì)由 Dagger 框架自動(dòng)生成,生成類的名字滿足 Dagger + 你的接口名 的格式。可以在 項(xiàng)目的 app-buile 目錄下找到生成的代碼。后面的使用也主要是跟 Component 的實(shí)現(xiàn)類打交道。

2.5 @Named

有時(shí)候我們需要生成同一個(gè)類的兩個(gè)不同的實(shí)例,這時(shí)候就要用到 @Named。它的用法很簡(jiǎn)單,直接看代碼:

@Provides 
@Named(“default”) 
SharedPreferences provideDefaultSharedPrefs() { … }

@Provides 
@Named(“secret”)
SharedPreferences provideSecretSharedPrefs() { … }  

提供兩種不同的 SharedPreferences 依賴,在需要注入的地方這樣標(biāo)記:

@Inject @Named(“default”) 
SharedPreferences mDefaultSharedPrefs;
@Inject @Named(“secret”) 
SharedPreferences mSecretSharedPrefs;

含義應(yīng)該不言自明了吧,概括一下就是根據(jù) @Named 后面字符串來(lái)匹配需要注入哪種實(shí)例

2.6 @Singleton & @Scope

你可能注意到前面的 Module 類和 Component 接口都出現(xiàn)了 @Singleton,它代表了 Dagger 框架里一種叫做 Scope 的概念。這個(gè)概念主要是為了解決不同的對(duì)象生命周期不同的問(wèn)題。有一些對(duì)象比如 Application,DBManager 等存在于應(yīng)用的整個(gè)周期,而像 Adapter,Presenter 等對(duì)象則隨著 Activity 的銷(xiāo)毀而死去,還有一些比如像數(shù)據(jù)庫(kù)連接,會(huì)隨著用戶的切換而創(chuàng)建和銷(xiāo)毀。

除了@Singleton,還可以自定義自己的 Scope:

@Scope
public @interface MyScope{}

然后你可以把前面例子中出現(xiàn) @Singleton 的地方都換成 @MyScope,效果是一樣的。

不要被 @Singleton 的表面含義所迷惑,而認(rèn)為只有用 @Singleton 注解的 Component 提供的依賴才是單例,自定義的 Scope 就不是單例。實(shí)際上,只要加了 Scope 注解的 Component 提供的依賴都是單例,不加 Scope 注解的就是每次注入的都是新的實(shí)例。這里一定要明確一個(gè)概念,那就是

Dagger 框架不會(huì)幫你管理對(duì)象的生命周期,需要自己來(lái)控制?。?!

具體的說(shuō)來(lái),如果你需要一個(gè)對(duì)象的隨著用戶的切換而改變,那么就在注銷(xiāo)用戶的時(shí)候銷(xiāo)毀對(duì)應(yīng)的 Component 及注入的對(duì)象,在登錄用戶的時(shí)候生成 Component 并進(jìn)行對(duì)象的注入。

這是我一直很難理解 Scope 概念的一個(gè)主要誤區(qū)。自定義的 Scope 標(biāo)記只是起到一個(gè)標(biāo)識(shí)作用,讓 Coder 不要忘了按照對(duì)象生命周期的不同來(lái)組織依賴。 Dagger 只提供了兩點(diǎn)編譯期的檢查:

  1. 一個(gè) Module 里只能存在一種 Scope
  2. Scope 標(biāo)記的 Component 跟其所依賴的 Component 的 Scope 不能相同

如果一時(shí)半會(huì)兒理解不了,沒(méi)關(guān)系,先記下來(lái),以后在使用中再慢慢理解為什么要有這樣的設(shè)定。

3 Dagger 上手 N 步走

上面講了這么多概念,可能都看懂了,但是到自己實(shí)際上手的時(shí)候,還是不知道該怎么使用。下面用一個(gè)例子來(lái)說(shuō)明如何在項(xiàng)目中應(yīng)用 Dagger2 框架實(shí)現(xiàn)依賴注入。
這里虛構(gòu)一個(gè)場(chǎng)景,假設(shè)在 MyActivity 需要一個(gè)建立一個(gè)數(shù)據(jù)庫(kù)連接 DBSession 去讀取數(shù)據(jù)。(實(shí)際應(yīng)用中使用 MVP 架構(gòu)的話不會(huì)在 Activity 中直接進(jìn)行數(shù)據(jù)讀取操作,這里簡(jiǎn)單起見(jiàn),暫不考慮架構(gòu)問(wèn)題)為了說(shuō)明 Component 間的依賴,我們假定 DBSession 的構(gòu)造方法依賴一個(gè) User 對(duì)象。

3.1 確定需要依賴的類

這個(gè)例子中很簡(jiǎn)單, MyActivity 依賴 DBSession,所以在 MyActivity 標(biāo)記:

public class MyActivity{
  @Inject
  DBSession dbSession;
  
  ...
}

3.2 定義Module

定義 Module 類,提供對(duì)象依賴。此時(shí)也要明確對(duì)象的生命周期,我們這里就假定 DBSession 的生命周期跟用戶的切換相關(guān),而 User 的生命周期跟應(yīng)用一樣。這里我們構(gòu)造兩個(gè) Module:

@Module
public class DBModule{
    @Provides @Peruser
    DBSession provideDBSession(User user){ return new DBSession(user); }
}
@Module
public class UserModule{
    @Provides @Singleton
    User provideUser(){ return new User(); }
}

3.3 定義 Component

Module 定義好之后,就需要 Component 來(lái)把他們之間的依賴關(guān)系連接起來(lái)了。這里把 Component 依賴以及 Scope 的概念都應(yīng)用上了

@Singleton
@Component(modules = {UserModule.class})
public interface UserComponent{
    public User getUser();
}
@PerUser
@Component(modules = {DBModule.class}, dependencies = {UserComponent.class})
public interface DBComponent{
    void inject(MyActivity myActivity);
    public DBSession getDBSession();
}

這里注意,因?yàn)?UserComponent 不直接為其他對(duì)象注入依賴,所以里面就不需要定義 void inject(Target target)方法

3.4 初始化 Component

經(jīng)過(guò)上面三步之后,編譯一下項(xiàng)目,會(huì)生成 DaggerUserComponent 和 DaggerDBComponent 兩個(gè)實(shí)現(xiàn)類。初始化 Component 的代碼如下:

userComponent = DaggerUserComponent.builder()
        .userModule(new UserModule())
        .build();

dbComponent = DaggerDBComponent.builder()
        .userComponent(userComponent)
        .dbModule(new DBModule())
        .build();

這里有幾點(diǎn)需要說(shuō)明:

  • DBComponent 依賴 UserComponent,所以在初始化的時(shí)候要傳入一個(gè) UserComponent 對(duì)象
  • 如果 Module 沒(méi)有定義構(gòu)造函數(shù),也就是只有隱含的無(wú)參構(gòu)造函數(shù)的話,那么可以省掉.userModule(new UserModule()).dbModule(new DBModule()) 這兩句。但如果定義了有參的構(gòu)造函數(shù),則要么再自己定義一個(gè)無(wú)參構(gòu)造函數(shù),要么就不能省去創(chuàng)建 Module 的這句了。至于原因,去看生成的 Component 實(shí)現(xiàn)類就知道了。
  • Component 初始化的地方,以及何時(shí)初始化,這個(gè)需要自己來(lái)控制,這里寫(xiě)在一起不代表實(shí)際中它們都會(huì)在一起初始化,可能有的在 Application 里初始化,有的在 Activity 里初始化,有的在滿足一定條件下初始化。說(shuō)到底,是跟其 Scope ,也就是生命周期有關(guān)。

3.5 最后一步注入

萬(wàn)里長(zhǎng)征終于要結(jié)束了,前面鋪墊了那么多,就為了最后這一句

public class MyActivity{
  @Inject
  DBSession dbSession;

  @Override 
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    //這里假設(shè) dbComponent 已經(jīng)完成初始化
    dbComponent.inject(this);
  }
}

在 inject 之后,依賴于這個(gè) Component 的對(duì)象就都完成了實(shí)例化的動(dòng)作了。再也見(jiàn)不到滿屏的 new 方法了!

4 未完待續(xù)

不得不說(shuō),這應(yīng)該是我接觸過(guò)的最難上手的庫(kù)了,難就難在你就算看懂了每個(gè)注解的含義,但是到自己寫(xiě)的時(shí)候還是感覺(jué)無(wú)從下手。希望看完這篇之后,你能知道如何開(kāi)始在項(xiàng)目中用 Dagger2 進(jìn)行一些簡(jiǎn)單的依賴注入。
其實(shí)還有一些概念沒(méi)有講,比如 SubComponent ,一方面是因?yàn)槎鄶?shù)情況下用 dependencies 就夠了,另一方面是我自己也還沒(méi)有完全搞懂,所以等到更多的應(yīng)用之后,再來(lái)寫(xiě)一篇關(guān)于 Dagger2 應(yīng)用中需要注意的問(wèn)題的文章吧。

參考文獻(xiàn)

1. Tasting Dagger 2 on Android
2. Dagger 2: Even sharper, less square
3. Dependency injection with Dagger 2 - Custom scopes
4. Making a Best Practice App #4?—?Dagger 2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,104評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,884評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,130評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,366評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,586評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,827評(píng)論 1 283
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,608評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容