如果你對(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'
}
}
在 app
的 build.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)編譯期的檢查:
- 一個(gè) Module 里只能存在一種 Scope
- 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