Android 中的依賴注入框架
來自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2016/0226/3998.html
09年我剛開發 App 那會,情況和現在不太一樣。App 作為新生的 IT 領域,一切事物都處于從低級向高級演化的階段。那會兒哪有人會把開發 App 當成吃飯的家伙呀,大家都只是想打發打發時間,找點樂子。
而今天,移動 App 已經發生了翻天覆地的變化。App 不但在當今互聯網時代和 PC 分庭抗禮,甚至在許多公司中成為戰略發展的核心,而 JUST EAT 公司就是其中之一。我們知道用戶都很喜歡我們的 App,所以我們一直很重視我們的 App,這也促使我們的 App 在保證性能表現的同時提供了絕佳的用戶體驗。
某種程度上你也可以說我們使用專業的軟件開發工具和技術嚴肅地對待著開發我們 App 的每一項事業。而我們用于開發的其中一項技術就是依賴注入。
依賴注入是一種設計模式
雖然依賴注入早已作為一種設計模式為人所知,但到了近段時間它才被廣泛應用到 Android 應用的開發中,主要是因為最近才有了各種各樣依賴注入框架的優秀實現。依賴注入讓開發者的代碼低耦合,且能夠容易地進行測試。開發者開發的 Android 應用存活的時間越長,應用易于被高效地進行測試就顯得越重要。在開發 JUST EAT 的時候,我們認為:讓代碼能夠被配置因而能被測試的關鍵就是依賴注入,這樣才能在應用變得越來越龐大的時候代碼依然可靠。正因為我們通過依賴注入使我們的測試變得有效,魯棒性高,才能定期更新應用。JUST EAT 的成功讓我們確信依賴注入是一個有用的工具,但在繼續吹B之前,我還是介紹一下以來注入把。
依賴注入的基礎
我們寫代碼的時候,我們的類總會不可避免地擁有其他類的依賴。這樣類 A 就可能需要一個類 B 的引用或依賴。為了簡化介紹的難度,我下面就舉個車的例子來解釋吧:
public class Car { private Engine engine; public Car() { engine = new PetrolEngine(); } }
這段代碼毫無疑問能正常地運作,但我們會發現 Car 類和 Engine 類高度耦合。Car 類一旦被實例化,就會伴隨著 Engine 類的實例化,也就使得 Car 類在實例化時必須知道 Engine 類的實例化需要哪些條件,在我們的例子中就是 PetrolEngine 類實例化需要的條件。為了降低耦合,我們可以把代碼改成下面這樣:
public class Car { private Engine engine; public Car(Engine engine) { this.engine = engine; } }
現在我們只需要在實例化 Car 對象的時候傳入一個 Engine 對象的引用,這就意味著 Car 類和 Engine 類的耦合度降低了。Car 類不再需要知道 Engine 類的實例化條件是什么,Car 類能調用的任何類型的 Engine 類。在這個例子中,因為我們通過 Car 類的構造方法把 Engine 類傳遞(或者稱為注入)給 Car 實例,使得我們完成了構造器注入的實現,我們當然還可以通過使用依賴注入框架的方法直接注入到類的域里。上面就是依賴注入的概念了。它的思想就是將依賴直接傳遞給類,而不是由類來初始化依賴。
如果依賴注入這么簡單,為什么需要專門開發一個框架?
現在我們知道依賴注入是什么了,也就知道要怎么在代碼中應用依賴注入了,所以我們就看看在我們的構造方法或者調用的方法中需要傳遞哪些依賴把。對于一些簡單的依賴,這部分工作確實很好完成,但依賴越復雜,我們需要完成的工作就越繁復。
還是回到剛剛的例子吧,假設 Engine 類也有它所需要的依賴集,例如:曲柄軸,活塞,塊和頭。如果我們遵循依賴注入的原則,就會將這些類的實例傳遞給 Engine 類,這樣的情況倒還好,我們只需要先創建這些對象,然后在創建 Engine 類實例的時候把它們傳遞給 Engine 對象就好了,最后我們還是可以將 Engine 類的實例傳遞給 Car 類。
現在我們讓這個例子變得更復雜些,如果我們想為 Engine 類的每一個部件創建類,那么我們很容易就會因此創建幾百個類,這些類甚至呈現為一顆復雜的樹狀圖(準確來說是一張圖)結構的依賴關系。
上面是這種情況的簡化圖,有圖可知,為了得到根對象,我們必須創建葉接對象,并把它們傳遞給各自的父對象,而且要以正確的順序去創建,要不然肯定會出現問題。
換句話說,為了創建依賴,我們如履薄冰,一旦順序搞錯了就會代碼爆炸。
所以現在你就會發現情況已經變得糟糕起來了,如果我們使用了諸如工廠模式或者建造者模式這樣的設計模式去創建我們的類,我們很塊就會發現代碼變得很復雜、臃腫,充斥著依賴的傳遞,而且大量的代碼都是無意義的、反復的模板代碼,這樣的代碼無疑是開發者不應該寫出來的。
從我們的例子里就可以了解到以簡單的方式實現依賴注入的壞處在哪里了:復雜的依賴關系、大量的模板代碼。也正是如此,依賴注入之前沒有流行起來。但不可否認,依賴注入確實是值得使用的,也正因如此,有幾個大牛開發了依賴注入框架來解決傳統依賴注入用法存在的問題。這些框架大大簡化了配置依賴以及生成工廠和建造者對象的過程,是之變得直觀和簡單。
在 Android 中應該使用什么依賴注入框架呢?
因為依賴注入這個概念是有一段歷史的,所以有一些依賴注入框架可以用很正常,在 Java 中,有著 Spring,Guice 和 Dagger 這些依賴注入框架,那么我們該如何選擇呢?
Spring 已經有一段時日了,為了解決聲明依賴和初始化對象,Spring 運用了 XML。但這樣做有一個問題,就是我們必須寫下冗長的 XML 代碼來完成這部分工作,而且要在運行時確保它完成了這些工作。所以 Spring 在嘗試解決因此衍生的種種問題時也提出了使用依賴注入存在的問題。
在 Java 依賴注入框架的發展歷程中,Guice 確實能算作對 Spring 的革新。Guice 不需要通過 XML 來配置類的成員域,而是直接通過注解完成了配置的工作,例如 @Inject 和 @Provides。這樣看起來依賴注入框架確實要變得更好用了,但 Guice 還是存在一些問題:Debug 和追蹤錯誤有些困難。另外,Guice 使用了大量的反射,雖說對服務器來說這些都不是太大的問題,但在追求用戶體驗的客戶端就會造成很大的開銷,影響應用的性能表現從而降低用戶體驗。
雖說 Guice 在依賴注入框架的發展史上踏出了一大步,但它沒有解決任何實際的問題,而且它的設計也不是特別適合在移動端開發中使用。因此,Square 開發了 Dagger,造福萬千 Android 開發者。
Dagger 這個名字的靈感來源于我們依賴的樹狀關系,依賴關系呈現的圖實際上是有向非循環圖,英語簡稱為:DAG,而在我們的場景中,圖呈現上尖下寬的形狀,有點像蒙古的圓頂帳篷,所以就叫作 DAGger―― Dagger了。Dagger 的目的是解決將 Guice 應用到移動端存在的問題。
Dagger 將大部分工作負擔投到編譯時完成而不是運行時,而且嘗試盡可能不使用反射,這兩部分工作能完成的話就能極大提高依賴注入框架的性能表現。最后 Dagger 完成了這樣的工作,但為此也犧牲了一些 Guice 中的特性,但總體來說還是值得的。但 Google 認為這部分工作還能完成地更好,所以他們在嘗試開發 Dagger 2。
Dagger 2 在編譯時完成更多的工作,而且將移除反射這部分工作完成地更好,最終完成的 Dagger 2 在 Debug 中的表現也比 Dagger 要好。我真心覺得 Dagger 2 是 Android 中優秀的依賴注入方案了,所以如果你有使用依賴注入框架的話,或者正想要選一個依賴注入框架使用,我認為 Dagger 2 是你的最佳選擇。
在 Android 開發中使用
那么要怎么用它們呢?Dagger 和 Dagger 2 都有豐富的教程能幫助你快速入門。
Dagger 2 的首頁有豐富的 Dagger 2 的相關概覽和特性,這都得感謝 Jake Wharton。上面告訴你要怎么用 Dagger 2 完成依賴注入中的各種工作。下面是一些簡單的教程:
什么是 Dagger 2 以及如何使用:
http://konmik.github.io/snorkeling-with-dagger-2.html
Dagger 2 中的域:
http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
結合使用Dagger、Espresso 和 Mockito 來完成測試:
http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html