dagger2從入門到放棄-多模組專案下dagger的使用
在只有一個Module的Android Project中,dagger2的使用相對來說是比較順暢的,但是事實上現在基本上不會存在只有一個Module的專案了。
一個最底層的Module,上層多個功能模組,再上層多個業務模組,再上層一個app模組去組織業務和功能,這是大部分android專案的結構,而在這種結構中dagger2的使用就會有一些不太順手了
場景構建
先確定一下dagger需要在多模組中實現什麼樣的場景
- Base模組提供Gson物件
- Api模組依賴基礎模組,提供OkHttp和Retrofit物件
- 應用模組依賴網路模組
- Gson、OkHttp和Retrofit都是擁有和Application相同生命週期的區域性單例
這基本是比較簡單的場景了
正常思路下的使用
在介紹Component的繼承體系時提到過只有使用Component的dependencies屬性指定父Component的方式才能在子Component指定父Component,這是唯一的從上到下的確定依賴關係的方式
很自然的會想到在每個模組擁有一個Component,上層的Component依賴下層Component
BaseLib
- 寫一個BaseAppComponent,指定提供Gson的Module
- BaseAppComponent暴露獲取Gson的介面,因為上層Component指定依賴的話需要底層Componnet顯式暴露可以提供的依賴的方法
- 在BaseApplication中建立並持有BaseAppComponent例項
這三步實現起來比較自然
ApiLib
- 寫一個ApiComponent,指定依賴BaseAppComponent,指定提供OkHttp和Retrofit的Module
- 暴露提供OkHttp和Retrofit的方法
第三步問題來了,在什麼時機去建立一個ApiComponent例項呢
?
作為一個處於中間層級的庫,ApiLib並不適合擁有自己的Application類,BaseLib在下層訪問不到ApiLib的程式碼,所以只能將ApiComponent的建立邏輯放到上層的App模組中
這樣會造成本來ApiLib不需要上層去進行初始化,而現在必須依賴上層初始化才能正常使用。而且現在設定的場景是一種比較簡單的情況,實際專案中的模組依賴會更加複雜,所有的模組的Component的初始化邏輯都放App模組中需要App模組的開發者瞭解所有模組Component的依賴關係,這不現實也不合理
不過有問題暫時先擱置,想個辦法先跑起來
- 建立一個ApiManager單例,init方法傳入BaseAppComponent例項來建立ApiComponent,在單例中持有ApiComponent,使用時從ApiManager中獲取
App模組
- 寫一個AppComponent,指定依賴ApiComponent
- 繼承BaseApplication
- 初始化ApiManager,完成ApiComponent的建立
- 使用ApiComponent完成AppComponent的建立
還是沒跑起來
按照上面的步驟應該可以正常執行才對,但是天不遂人願,還是有地方姿勢不對
之前的文章中提到過使用dependencies指定依賴的情況下,父Component需要顯式暴露提供依賴的方法,而且不具有傳遞性,只能為下一級提供依賴而不能為下下級提供依賴
所以ApiComponent只暴露獲取OkHttp和Retrofit的方法還不夠,還需要暴露獲取Gson的方法
添加了獲取Gson的方法之後程式碼終於可以正常運行了
方案評價
通過上面的介紹,大家應該發現了這種方案在多模組的專案中dagger使用不是那麼順手
- 首先底層模組需要依賴最上層模組觸發Component的建立,而且需要在當前模組中建立一個單例儲存建立的Component來實現生命週期的管理,這是之前不需做的額外工作
- 上層模組需要了解下層所有的模組的Component的建立和依賴關係才能完成所有Component的建立,這樣的要求對上層應用的開發者明顯是不合理的,依賴一多明顯是不可能實現的
- 如果上層Component需要用到底層的依賴,需要中間所有的Component顯式宣告獲取該依賴的方法,這種情況下底層新增一個依賴需要多n個方法,反而增加了開發者的負擔
-
這個方案中好像dagger.android/Multibindings也不是很好使
- 庫的Component需要暴露提供依賴的方法上層才能用,而兩個平級的庫要同時向上暴露同一個基類元素的Map比如DispatchingAndroidInjector<Activity>好像行不通,編譯會出現錯誤
優化
前兩個痛點的問題在於Application的建立時機只有最上層的模組才知道,仔細分析程式碼發現事實上這個時機並不重要,主要目的是要給Component提供一個Application例項同時保持和Application相同的生命週期
-
提供Application例項
只需要在底層提供一個獲取Application的靜態方法,則庫中根Component的建立就可以不需要依賴上層的Application了 -
保持和Application的生命週期
這個比較簡單,用單例或者直接在類中用一個static變數儲存Component例項都可以保持和Application相同的生命週期
這樣修改之後,庫的根Component的管理都變成了在庫中完成,App模組就不需要關注下層庫中Component的組織了;當然如果App模組也要用依賴注入的方式使用下層庫中dagger提供的依賴,那還是需要處理自己得Component和要用到的庫的Component的依賴關係,不過這樣是用到哪個處理哪個,而不是之前的不管用不用都要處理所有依賴關係
第三個痛點仔細考慮了一下發現可以在底層的模組中的提供一個介面,暴露獲取依賴的方法,上層Component只用繼承這樣的介面,而新增依賴只用在介面中增加方法,不用在每個Component中一個個新增
最後一個痛點暫時還沒發現好的解決方式
上層Application中處理所有依賴
上面的方案經過優化其實基本可用了,但是dagger使用起來是能用註解的地方都用註解才好,不能跨庫使用dagger.android還是有些麻煩的
所以對自己整體把控的專案,對dagger使用變成了在App模組的Component中進行所有依賴的組織
先看看怎麼實現
對Module中providerXXX方法的Scope進行統一
之前是用多個Component去組織,所以需要多個Scope進行標記,現在是一個Component,所以只能使用一個Scope進行標記
在每個模組中定義一個Module,將Application級別的依賴include進去
這一步是為了簡化App模組的工作,一個庫的Application級別的Module自己去組織,App只需要包含一個Module,庫中新增新的Module也不需要上層的改動
@Module(includes = {ApiServiceModule.class, OrmModule.class}) public class ApiCollectionModule { }
將所有的庫提供的Module包含到AppComponent中
@AppScope @Component(modules = {AppModule.class, BaseAppCollectionModule.class, ApiCollectionModule.class, MvvmCollectionModule.class}) public interface AppComponent { ... }
如何將依賴注入到庫中的物件
上面的做法完成了將依賴注入到App模組的準備工作,但是庫中的類怎麼實現依賴注入呢?
這裡提供兩種方式
-
使用Multibindings/dagger.android向庫中的Activity/Fragment注入依賴
-
在庫中建立AppComponent的SubComponent,用SubComponent代替AppComponent在庫中使用
dagger.android的方式和之前基本沒啥區別,所以主要說下SubComponent的方式
-
為什麼需要一個SubComponent
dagger.android只能注入Activity,Fragment等固定型別的子類,如果有一個其他的類具有生命週期(例如登入的Manager)也需要依賴注入,那麼dagger.android就做不到了,所以需要一個具有Application相同的生命週期的Component來作為庫中的初始Component。
當然,對於其他的物件也可以用Multibindings的思路去實現,不過沒有dagger.android的支援,需要寫更多的模板程式碼,麻煩一些
-
使用什麼方式關聯庫中的SubComponent和AppComponent
因為現在AppComponent在最上層而SubComponent反而是在下層,所以只能在AppComponent宣告SubComponent或者在Module中指定subcomponents
-
SubComponent何時建立/怎麼儲存/庫怎麼獲取
- 還是需要在Application onCreate時建立,庫中沒有合適的時機
- 儲存在Application中
- 庫中定義一個獲取SubComponent的介面,Application實現它
與上個方案的比較
上個方案實際每個庫的依賴集合都是獨立的,每個庫的依賴集合包括當前庫提供的依賴和下層庫暴露的依賴
而當前方案的實際是隻有一個總的依賴集合,當然因為dagger是編譯期的依賴注入框架,所以即使注入時所有的依賴都有,但是也無法用反射這樣的方式為元件提供上層的依賴
不過因為是一個整體,所以組織起來比第一種方案方便一些
優點
- 可以使用dagger.android/Multibindings在平行的庫中進行依賴注入
-
庫中需要做的事情變少了
- 庫的SubComponent儲存在Application中,不需要建立一個單例來儲存Componet
缺點
上層的App模組也必須依賴dagger
總結
dagger在多模組的專案中的使用或者說在Library中的使用確實會有很多的不順手的地方,不同的方案有利有弊,要看具體的場景進行選擇
只在App模組使用dagger
- 所有的下層依賴都使用Module中Provider方式來提供
- 自己組織App模組的Component
這種引入dagger的方式是代價最小的,可以漸進的去迭代,但是有些Module是應該放在庫中公用的,有些Component的依賴邏輯也是可以公用的,現在只存在於App模組中導致複用度不高
這種情況適合只負責上層業務開發的情況
只在庫模組中使用dagger
如果要開發一個獨立的庫,想用dagger的話,只能用第一種方案了,因為庫是獨立的;如果需要Application例項的話還需要指定Application onCreate時傳入Application例項等初始化操作
個人不建議在獨立的庫中使用dagger
當然如果你的庫並不需要和Application作為依賴同時沒有使用Multibindings來解耦的場景也是可以用dagger的
在全域性使用dagger
這種情況才是用起來最爽的,dagger有一種用的地方越多用起來越順手的特性,而這種情況適合文章中介紹的第二種方案
當然真實的專案千差萬別,所以還是需要自己衡量專案中到底要不要使用要怎樣使用dagger