dagger2從入門到放棄-多模塊項(xiàng)目下dagger的使用

在只有一個(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ò)程,可以順藤摸瓜

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容