Dagger2 in Android(一)通俗基礎

系列文章java

背景知識

Dagger2 是一個由 Google (以前是 Square)維護的開源依賴注入框架。我曾兩次試圖學習 Dagger 最終被亂七八糟的名詞弄得暈頭轉向,連個 demo 都沒寫出來就放棄了。因此本文也會重點解釋 Dagger 的各個名詞,只有熟悉了它們的做用,才能順暢無阻地使用,也才能看懂別人的 demo。android

雖然標題叫 Dagger2 in Android,可是前幾節都是 Dagger 通用的基礎知識,與 Android 沒有關係。git

本系列使用 Kotlin 語言,其與 Java 100% 兼容,僅僅是語法不一樣而已,不會形成太大影響。Kotlin 最終依然會被編譯成 jvm 字節碼。github

例如 java 定義函數: public String getName() {return "David"}數組

kotlin 版本:fun getName(): String {return "David"},甚至能夠更簡潔:fun getName() = "David"框架

依賴注入

那麼首先咱們得先清楚什麼是依賴注入。依賴注入是實現控制反轉(IoC)的一個方案。 那什麼叫控制反轉?別急,我儘可能用最通俗的方式解釋所涉及的全部概念。jvm

一般在面嚮對象語言中,一個類每每依賴其餘類,一個對象依賴其餘對象。那麼最直接的方案就是 new 一個出來。這很好理解,我依賴計算機,因此我就本身造一個出來。那麼造出的計算機固然是我本身控制的,這就是「控制正轉」。那麼反轉就是,我須要計算機,可是我不本身造,而是廠家造好以後交給我。這就是控制反轉。由於我再也不控制計算機的生產,此時廠家就叫作 IoC 容器,它負責生產維持對象,而我只負責拿來用。因此 IoC 不是技術,而是思想,利用這種思想能夠下降對象間的耦合,提升代碼重用率。 假若計算機的配置發生了變動,在以前每一個人都要本身更新圖紙,可是如今只須要廠家更新就行了,我總能夠拿到最新款的計算機。ide

爲了實現控制反轉,有不少方案,常見的有 依賴注入依賴查找服務定位器。這裏咱們講依賴注入。函數

其實依賴注入不是什麼新東西,咱們每天都在用。依賴注入有三種常見的手段:構造函數Setter註解。舉個廚師的例子:廚師依賴爐子。那麼代碼能夠這麼寫:post

// 經過構造函數注入爐子
class Chef(val stove: Stove) {
}

// 經過 Setter 注入
class Chef() {
	var stove: Stove
	
	// 其實 Kotlin 默認實現了 setter,爲了更加清晰我手動寫了一個。
	fun setStove(stove: Stove) {
		this.stove = stove
	}
}
複製代碼

看到沒,依賴注入就在咱們身邊,咱們一直都在用。廚師須要爐子,但他不本身造爐子,而是別人造好後傳給他用,也就是「注入」。

Dagger 優點

既然徹底能夠經過構造函數注入,那爲何要 dagger 呢?固然是由於 dagger 更方便哈哈。

繼續廚師的例子,咱們知道爐子必須依賴燃料。那麼爲了獲得一個廚師,咱們必須前後獲得一個燃料、爐子,看下面的代碼:

class Stove(val fuel: Fuel) {}

class Fuel(){}

// 建立一個廚師
val fuel = Fuel()
val stove  = Stove(fuel)
val chef = Chef(stove)
複製代碼

看到沒,爲了獲得一個廚師,咱們須要建立一堆東西。若是你以爲還不夠,其實廚師還依賴菜刀,燃料還依賴自然氣。總有一天你會不耐煩。

事實上,有時候2個廚師能夠共用1個爐子,而3個爐子能夠共用1瓶燃料。這些問題 Dagger 統統能夠優雅地解決。怎麼樣 是否是有點感受了<( ̄︶ ̄)↗

進入主題

引入 Dagger

Dagger 能夠經過多種方式引入,詳見 README。做爲 Android 咱們可使用 Gradle 聲明依賴(kotlin):

dependencies {
	def dagger_version = "2.23.1"
	implementation "com.google.dagger:dagger:$dagger_version"
    kapt "com.google.dagger:dagger-compiler:$dagger_version"
	
	// 若是須要使用 Android 的特有 Dagger 功能,還要引入下面的庫
	implementation "com.google.dagger:dagger-android:$dagger_version"
    implementation "com.google.dagger:dagger-android-support:$dagger_version"
    kapt "com.google.dagger:dagger-android-processor:$dagger_version"
}
複製代碼

@Inject

@Inject 是咱們接觸到的第一個 Dagger 註解。它有兩個做用:① 標註哪些東西須要注入。② 標註這些東西怎麼建立。

假設 Dagger 是後勤管理部,那麼做爲廚師,你必須告訴 Dagger 須要哪些東西,而後還要告訴他這些怎麼造,這樣 Dagger 才能注入給你。因此廚師和爐子(簡單起見忽略燃料)咱們能夠這樣改寫:

// 告訴 Dagger 爐子能夠經過一個無參的構造函數造出來
class Stove @Inject constructor() {}

class Chef() {
	@Inject // 告訴 Dagger 我須要一個爐子
	val stove: Stove
}
複製代碼

就是這麼簡單。如今 Dagger 已經知道咱們須要什麼、這個東西怎麼建立了。這裏我把廚師稱爲目標類,也就是「爐子要往哪注入」,這就是目標。

@Component

以前咱們已經讓廚師和爐子創建了無形的聯繫。可是要真正得到爐子,可是還得讓這個聯繫直接一點。畢竟以前那都是泛泛而談,針對具體的一個廚師(實例)咱們得具體操做。這個橋樑就是 @Component。Component 將具體鏈接廚師所依賴的爐子,和爐子的構造函數。

Component 是一個註解類,且是一個接口或抽象類。所以必須對一個接口標記 @Component 才能得到一個 Component。它將得到一個目標類實例的引用(也就是一個活生生的廚師),而後查找這個類所依賴的類(詢問廚師你須要啥)。獲得答案以後它會去查找是否有已知的構造函數(以前 @Inject 標註過的),而後實例化並注入到目標類(造個爐子交給廚師)。

根據上面的解釋,咱們能夠輕鬆寫出一個 Component:

@Component
interface MainComponent {
	// 定義一個函數,以便拿到目標類實例的引用
	fun inject(chef: Chef)
}
複製代碼

其實到目前爲止咱們已經實現一個完整的依賴注入了:告訴 Dagger 廚師須要爐子、告訴爐子應該怎麼造,而且創建了一個直接的橋樑。

恭喜!(。⌒∇⌒)

@Module

這又是什麼鬼呢?咱們來想一想,若是廚師也不會造爐子咋辦。 反應到項目中就是,咱們引入了第三方庫,這個庫沒有在構造函數上標記 @Inject,總不能修改源碼本身加上吧。這時候就要 Module 出場拉。Module 至關於給第三方庫套一層封裝,給他從外面包裹一個可以通知 Dagger 的建立方法。好比廚師不會造爐子,可是他知道去哪買,那麼這也是OK的。

按照這個思路咱們修改一下廚師爐子的代碼:

// 如今去掉爐子的 @Inject 表明廚師不會造爐子
// 假設爐子是第三方庫,咱們無權添加 @Inject
class Stove(){}

@Module
class MainModule() {
	// 雖然我不會造,但我可去 [MainModule] 這個商店買
	provideStove():Stove {
		return Stove()
	}
}
複製代碼

Module 就像工廠模式,裏面提供了各類建立實例的方法。

如今咱們必須讓 @Component 知道 Module(商店)的存在。很是簡單:

// 直接傳入 Mudoule 類數組就好啦
@Component(modules = [MainModule::class])
interface MainComponent {
	fun inject(chef: Chef)
}
複製代碼

而後有一個新問題,以前利用 @Inject 標註了類的構造方法,如今 @Module 只標註了一個商店,並無指明某個具體的類(爐子)到底怎麼得到。就好像如今後勤管理處知道去家樂福能夠買個爐子,可是不知道具體在哪一個區域。

[注] 一個 Module 能夠被多個 Component 引用。由於有可能多家超市都賣爐子。

@Provides

Provides 將最終解決第三方庫的問題。咱們把 Module 中全部建立實例的函數都用 Provides 標註。那麼這些函數就是會 Dagger 所識別而後選擇一個返回值類型匹配的進行調用。

如今對於廚師依賴的第三方爐子,Dagger 將這樣處理:首先橋樑告訴他有個商店(MainModule)可能有爐子,因而到商店篩選全部貨架(Provides),找到匹配的商品帶回來便可。

@Module
class MainModule() {
	@Provides // 加上一個註解代表這個函數能夠提供一個爐子(或其餘物品)
	provideStove():Stove {
		return Stove()
	}
}
複製代碼

到目前爲止,咱們已經瞭解了 Dagger 的基礎內容。咱們已經學會了如何通知 Dagger 所需的依賴、依賴類如何建立,以及對於第三方提供的類該如何包裝並使 Dagger 識別。

但願廚師的類比能幫助你更好地理解相關概念,下面咱們將繼續學習其餘註解。

相關文章
相關標籤/搜索