在只有一個(gè)Module的Android Project中,dagger2的使用相對(duì)來(lái)說(shuō)是比較順暢的,但是事實(shí)上現(xiàn)在基本上不會(huì)存在只有一個(gè)Module的項(xiàng)目了。
一個(gè)最底層的Module,上層多個(gè)功能模塊,再上層多個(gè)業(yè)務(wù)模塊,再上層一個(gè)app模塊去組織業(yè)務(wù)和功能,這是大部分android項(xiàng)目的結(jié)構(gòu),而在這種結(jié)構(gòu)中dagger2的使用就會(huì)有一些不太順手了
場(chǎng)景構(gòu)建
先確定一下dagger需要在多模塊中實(shí)現(xiàn)什么樣的場(chǎng)景
- Base模塊提供Gson對(duì)象
- Api模塊依賴基礎(chǔ)模塊,提供OkHttp和Retrofit對(duì)象
- 應(yīng)用模塊依賴網(wǎng)絡(luò)模塊
- Gson、OkHttp和Retrofit都是擁有和Application相同生命周期的局部單例
這基本是比較簡(jiǎn)單的場(chǎng)景了
正常思路下的使用
在介紹Component的繼承體系時(shí)提到過(guò)只有使用Component的dependencies屬性指定父Component的方式才能在子Component指定父Component,這是唯一的從上到下的確定依賴關(guān)系的方式
很自然的會(huì)想到在每個(gè)模塊擁有一個(gè)Component,上層的Component依賴下層Component
BaseLib
- 寫一個(gè)BaseAppComponent,指定提供Gson的Module
- BaseAppComponent暴露獲取Gson的接口,因?yàn)樯蠈覥omponent指定依賴的話需要底層Componnet顯式暴露可以提供的依賴的方法
- 在BaseApplication中創(chuàng)建并持有BaseAppComponent實(shí)例
這三步實(shí)現(xiàn)起來(lái)比較自然
ApiLib
- 寫一個(gè)ApiComponent,指定依賴BaseAppComponent,指定提供OkHttp和Retrofit的Module
- 暴露提供OkHttp和Retrofit的方法
第三步問(wèn)題來(lái)了,在什么時(shí)機(jī)去創(chuàng)建一個(gè)ApiComponent實(shí)例呢
?
作為一個(gè)處于中間層級(jí)的庫(kù),ApiLib并不適合擁有自己的Application類,BaseLib在下層訪問(wèn)不到ApiLib的代碼,所以只能將ApiComponent的創(chuàng)建邏輯放到上層的App模塊中
這樣會(huì)造成本來(lái)ApiLib不需要上層去進(jìn)行初始化,而現(xiàn)在必須依賴上層初始化才能正常使用。而且現(xiàn)在設(shè)定的場(chǎng)景是一種比較簡(jiǎn)單的情況,實(shí)際項(xiàng)目中的模塊依賴會(huì)更加復(fù)雜,所有的模塊的Component的初始化邏輯都放App模塊中需要App模塊的開(kāi)發(fā)者了解所有模塊Component的依賴關(guān)系,這不現(xiàn)實(shí)也不合理
不過(guò)有問(wèn)題暫時(shí)先擱置,想個(gè)辦法先跑起來(lái)
- 創(chuàng)建一個(gè)ApiManager單例,init方法傳入BaseAppComponent實(shí)例來(lái)創(chuàng)建ApiComponent,在單例中持有ApiComponent,使用時(shí)從ApiManager中獲取
App模塊
- 寫一個(gè)AppComponent,指定依賴ApiComponent
- 繼承BaseApplication
- 初始化ApiManager,完成ApiComponent的創(chuàng)建
- 使用ApiComponent完成AppComponent的創(chuàng)建
還是沒(méi)跑起來(lái)
按照上面的步驟應(yīng)該可以正常運(yùn)行才對(duì),但是天不遂人愿,還是有地方姿勢(shì)不對(duì)
之前的文章中提到過(guò)使用dependencies指定依賴的情況下,父Component需要顯式暴露提供依賴的方法,而且不具有傳遞性,只能為下一級(jí)提供依賴而不能為下下級(jí)提供依賴
所以ApiComponent只暴露獲取OkHttp和Retrofit的方法還不夠,還需要暴露獲取Gson的方法
添加了獲取Gson的方法之后代碼終于可以正常運(yùn)行了
方案評(píng)價(jià)
通過(guò)上面的介紹,大家應(yīng)該發(fā)現(xiàn)了這種方案在多模塊的項(xiàng)目中dagger使用不是那么順手
- 首先底層模塊需要依賴最上層模塊觸發(fā)Component的創(chuàng)建,而且需要在當(dāng)前模塊中創(chuàng)建一個(gè)單例保存創(chuàng)建的Component來(lái)實(shí)現(xiàn)生命周期的管理,這是之前不需做的額外工作
- 上層模塊需要了解下層所有的模塊的Component的創(chuàng)建和依賴關(guān)系才能完成所有Component的創(chuàng)建,這樣的要求對(duì)上層應(yīng)用的開(kāi)發(fā)者明顯是不合理的,依賴一多明顯是不可能實(shí)現(xiàn)的
- 如果上層Component需要用到底層的依賴,需要中間所有的Component顯式聲明獲取該依賴的方法,這種情況下底層添加一個(gè)依賴需要多n個(gè)方法,反而增加了開(kāi)發(fā)者的負(fù)擔(dān)
- 這個(gè)方案中好像dagger.android/Multibindings也不是很好使
- 庫(kù)的Component需要暴露提供依賴的方法上層才能用,而兩個(gè)平級(jí)的庫(kù)要同時(shí)向上暴露同一個(gè)基類元素的Map比如DispatchingAndroidInjector<Activity>好像行不通,編譯會(huì)出現(xiàn)錯(cuò)誤
優(yōu)化
前兩個(gè)痛點(diǎn)的問(wèn)題在于Application的創(chuàng)建時(shí)機(jī)只有最上層的模塊才知道,仔細(xì)分析代碼發(fā)現(xiàn)事實(shí)上這個(gè)時(shí)機(jī)并不重要,主要目的是要給Component提供一個(gè)Application實(shí)例同時(shí)保持和Application相同的生命周期
- 提供Application實(shí)例
只需要在底層提供一個(gè)獲取Application的靜態(tài)方法,則庫(kù)中根Component的創(chuàng)建就可以不需要依賴上層的Application了 - 保持和Application的生命周期
這個(gè)比較簡(jiǎn)單,用單例或者直接在類中用一個(gè)static變量保存Component實(shí)例都可以保持和Application相同的生命周期
這樣修改之后,庫(kù)的根Component的管理都變成了在庫(kù)中完成,App模塊就不需要關(guān)注下層庫(kù)中Component的組織了;當(dāng)然如果App模塊也要用依賴注入的方式使用下層庫(kù)中dagger提供的依賴,那還是需要處理自己得Component和要用到的庫(kù)的Component的依賴關(guān)系,不過(guò)這樣是用到哪個(gè)處理哪個(gè),而不是之前的不管用不用都要處理所有依賴關(guān)系
第三個(gè)痛點(diǎn)仔細(xì)考慮了一下發(fā)現(xiàn)可以在底層的模塊中的提供一個(gè)接口,暴露獲取依賴的方法,上層Component只用繼承這樣的接口,而添加依賴只用在接口中增加方法,不用在每個(gè)Component中一個(gè)個(gè)添加
最后一個(gè)痛點(diǎn)暫時(shí)還沒(méi)發(fā)現(xiàn)好的解決方式
上層Application中處理所有依賴
上面的方案經(jīng)過(guò)優(yōu)化其實(shí)基本可用了,但是dagger使用起來(lái)是能用注解的地方都用注解才好,不能跨庫(kù)使用dagger.android還是有些麻煩的
所以對(duì)自己整體把控的項(xiàng)目,對(duì)dagger使用變成了在App模塊的Component中進(jìn)行所有依賴的組織
先看看怎么實(shí)現(xiàn)
對(duì)Module中providerXXX方法的Scope進(jìn)行統(tǒng)一
之前是用多個(gè)Component去組織,所以需要多個(gè)Scope進(jìn)行標(biāo)記,現(xiàn)在是一個(gè)Component,所以只能使用一個(gè)Scope進(jìn)行標(biāo)記
在每個(gè)模塊中定義一個(gè)Module,將Application級(jí)別的依賴include進(jìn)去
這一步是為了簡(jiǎn)化App模塊的工作,一個(gè)庫(kù)的Application級(jí)別的Module自己去組織,App只需要包含一個(gè)Module,庫(kù)中添加新的Module也不需要上層的改動(dòng)
@Module(includes = {ApiServiceModule.class, OrmModule.class})
public class ApiCollectionModule {
}
將所有的庫(kù)提供的Module包含到AppComponent中
@AppScope
@Component(modules = {AppModule.class,
BaseAppCollectionModule.class,
ApiCollectionModule.class,
MvvmCollectionModule.class})
public interface AppComponent {
...
}
如何將依賴注入到庫(kù)中的對(duì)象
上面的做法完成了將依賴注入到App模塊的準(zhǔn)備工作,但是庫(kù)中的類怎么實(shí)現(xiàn)依賴注入呢?
這里提供兩種方式
使用Multibindings/dagger.android向庫(kù)中的Activity/Fragment注入依賴
在庫(kù)中創(chuàng)建AppComponent的SubComponent,用SubComponent代替AppComponent在庫(kù)中使用
dagger.android的方式和之前基本沒(méi)啥區(qū)別,所以主要說(shuō)下SubComponent的方式
為什么需要一個(gè)SubComponent
dagger.android只能注入Activity,F(xiàn)ragment等固定類型的子類,如果有一個(gè)其他的類具有生命周期(例如登錄的Manager)也需要依賴注入,那么dagger.android就做不到了,所以需要一個(gè)具有Application相同的生命周期的Component來(lái)作為庫(kù)中的初始Component。
當(dāng)然,對(duì)于其他的對(duì)象也可以用Multibindings的思路去實(shí)現(xiàn),不過(guò)沒(méi)有dagger.android的支持,需要寫更多的模板代碼,麻煩一些使用什么方式關(guān)聯(lián)庫(kù)中的SubComponent和AppComponent
因?yàn)楝F(xiàn)在AppComponent在最上層而SubComponent反而是在下層,所以只能在AppComponent聲明SubComponent或者在Module中指定subcomponents-
SubComponent何時(shí)創(chuàng)建/怎么保存/庫(kù)怎么獲取
- 還是需要在Application onCreate時(shí)創(chuàng)建,庫(kù)中沒(méi)有合適的時(shí)機(jī)
- 保存在Application中
- 庫(kù)中定義一個(gè)獲取SubComponent的接口,Application實(shí)現(xiàn)它
與上個(gè)方案的比較
上個(gè)方案實(shí)際每個(gè)庫(kù)的依賴集合都是獨(dú)立的,每個(gè)庫(kù)的依賴集合包括當(dāng)前庫(kù)提供的依賴和下層庫(kù)暴露的依賴
而當(dāng)前方案的實(shí)際是只有一個(gè)總的依賴集合,當(dāng)然因?yàn)閐agger是編譯期的依賴注入框架,所以即使注入時(shí)所有的依賴都有,但是也無(wú)法用反射這樣的方式為組件提供上層的依賴
不過(guò)因?yàn)槭且粋€(gè)整體,所以組織起來(lái)比第一種方案方便一些
優(yōu)點(diǎn)
- 可以使用dagger.android/Multibindings在平行的庫(kù)中進(jìn)行依賴注入
- 庫(kù)中需要做的事情變少了
- 庫(kù)的SubComponent保存在Application中,不需要?jiǎng)?chuàng)建一個(gè)單例來(lái)保存Componet
缺點(diǎn)
上層的App模塊也必須依賴dagger
總結(jié)
dagger在多模塊的項(xiàng)目中的使用或者說(shuō)在Library中的使用確實(shí)會(huì)有很多的不順手的地方,不同的方案有利有弊,要看具體的場(chǎng)景進(jìn)行選擇
只在App模塊使用dagger
- 所有的下層依賴都使用Module中Provider方式來(lái)提供
- 自己組織App模塊的Component
這種引入dagger的方式是代價(jià)最小的,可以漸進(jìn)的去迭代,但是有些Module是應(yīng)該放在庫(kù)中公用的,有些Component的依賴邏輯也是可以公用的,現(xiàn)在只存在于App模塊中導(dǎo)致復(fù)用度不高
這種情況適合只負(fù)責(zé)上層業(yè)務(wù)開(kāi)發(fā)的情況
只在庫(kù)模塊中使用dagger
如果要開(kāi)發(fā)一個(gè)獨(dú)立的庫(kù),想用dagger的話,只能用第一種方案了,因?yàn)閹?kù)是獨(dú)立的;如果需要Application實(shí)例的話還需要指定Application onCreate時(shí)傳入Application實(shí)例等初始化操作
個(gè)人不建議在獨(dú)立的庫(kù)中使用dagger
當(dāng)然如果你的庫(kù)并不需要和Application作為依賴同時(shí)沒(méi)有使用Multibindings來(lái)解耦的場(chǎng)景也是可以用dagger的
在全局使用dagger
這種情況才是用起來(lái)最爽的,dagger有一種用的地方越多用起來(lái)越順手的特性,而這種情況適合文章中介紹的第二種方案
當(dāng)然真實(shí)的項(xiàng)目千差萬(wàn)別,所以還是需要自己衡量項(xiàng)目中到底要不要使用要怎樣使用dagger
相關(guān)文章
dagger2從入門到放棄-概念
dagger2從入門到放棄-最基礎(chǔ)的用法介紹
dagger2從入門到放棄-Component的繼承體系、局部單例
dagger2從入門到放棄-ActivityMultibindings
dagger2從入門到放棄-dagger.android
dagger2從入門到放棄-其他用法
dagger2從入門到放棄-多模塊項(xiàng)目下dagger的使用
dagger2從入門到放棄-為何放棄
示例代碼
DaggerInAction
歡迎star
master分支上最新的代碼可能會(huì)比當(dāng)前文章的示例代碼稍微復(fù)雜點(diǎn),提交記錄里包含了每一步的迭代過(guò)程,可以順藤摸瓜